Build on Badu

For developers and app makers

Every Badu is a private assistant that runs on its owner's own server. You can build apps, gadgets and integrations that talk to it, or teach every Badu to use your product with a skill. This page covers the main ways in; the full API reference lists every endpoint.

Getting started

Each Badu has its own address, such as https://assistant.example.com. Send every request there with a bearer token. The owner can sign in to get one:

curl -X POST https://assistant.example.com/v1/login \
  -H 'content-type: application/json' \
  -d '{"password": "the owner's password"}'
# → {"token": "…"}

Then send Authorization: Bearer <token> on every call. For a gadget or another app, the owner can instead make a key under Settings, Devices; it only reaches the device endpoints below. A signed-in owner can also open the interactive docs on their own Badu at /docs.

Chat

Start a conversation, then send messages to it. The answer comes back when it's ready, or as it's written with "stream": true (Server-Sent Events: text, tool, done).

curl -X POST $BADU/v1/sessions -H "authorization: Bearer $TOKEN"
# → {"id": "…", …}

curl -X POST $BADU/v1/sessions/$SESSION/messages \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"text": "What is on my calendar tomorrow?"}'
# → {"text": "…", "toolCalls": [...], "stopReason": "end"}

Messages can carry up to 8 attachments (photos, PDFs or text files) as base64 in attachments. Read a chat back with GET /v1/sessions/{id}/messages.

Approvals

Anything that can't be undone (sending email, posting, paying, unlocking a door) never runs straight away. It waits for the owner. A tool call that needs approval comes back with a queuedApprovalId.

GET  /v1/approvals                 # what is waiting
POST /v1/approvals/{id}/approve    # run it
POST /v1/approvals/{id}/reject     # drop it

Only build approve buttons for the owner themselves. Never approve on someone else's behalf.

Webhooks

Badu can tell your app when something happens. Add an address under Settings, Webhooks, or with POST /v1/webhooks. Each delivery is a JSON POST with these headers:

x-badu-event: approval.created
x-badu-signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with the webhook secret>
EventWhen
approval.createdSomething is waiting for the owner's OK
approval.resolvedThe owner approved or declined it
routine.finishedA scheduled routine ran
job.finishedA background job finished
insight.flaggedBadu spotted something worth the owner's attention
meeting.minutes_readyMeeting minutes are saved

Always check the signature before trusting a delivery.

Devices and voice

Speakers, wearables and other gadgets use a device key from Settings, Devices, sent as x-device-key or a bearer token. Each device has its own chat with the assistant.

POST /device/ask    {"text": "Remind me to call Ama at 5"}   → {"text": "…"}
POST /device/talk   body: the recording (content-type audio/…) → spoken answer as audio/ogg

/device/talk needs a voice key on that Badu (Settings, Voice).

MCP

Other AI apps can use Badu as a tool through the Model Context Protocol at /mcp on the owner's Badu, with a device key. Anything that can't be undone still waits in the owner's Approvals.

Connect your app to Badu

If your app holds a business's records (bookings, members, orders, tickets, patients…), you can connect it so the owner's Badu keeps those records in memory, hears about changes the moment they happen, and does things in your app for them, always with the owner's OK for anything that changes data. This is how DigEvents and ConnectrixPro connect. It takes three pieces, and you can ship them one at a time.

1. Describe your app in one file

Serve /.well-known/badu-connector.json on your app's address. It lists what Badu may do, marked by risk, and what it should copy into memory.

{
  "badu_connector": 1,
  "id": "acme-bookings",
  "name": "Acme Bookings",
  "description": "Appointments and clients for salons and clinics.",
  "api_base": "/api/v1",
  "auth": {"type": "bearer"},
  "key_hint": "In Acme, open Settings, API keys, and press New key.",
  "connect_return": "/settings/badu",
  "actions": [
    {"name": "list_bookings", "method": "GET", "path": "/bookings", "risk": "read",
     "description": "Bookings, soonest first.", "input": {"type": "object", "properties": {"date": {"type": "string"}}}},
    {"name": "cancel_booking", "method": "POST", "path": "/bookings/{booking_id}/cancel", "risk": "write",
     "description": "Cancel a booking and tell the client.", "input": {"type": "object", "required": ["booking_id"], "properties": {"booking_id": {"type": "integer"}}}}
  ],
  "events": [{"type": "booking.created", "description": "A new booking", "important": true}],
  "sync": [{"action": "list_bookings", "items": "data", "kind": "booking", "name": "{client} at {time}",
            "summary": "{service} with {staff}", "fields": ["service", "staff", "status"], "link": "{app_url}/bookings/{id}"}]
}

With just this file, an owner can connect your app: on their Badu's Integrations page they pick your app, type its address and paste a key.

2. Send Badu signed updates

When something happens, post it to the owner's Badu. Badu keeps it, remembers anything in remember, and messages the owner about the important ones.

POST <badu>/connect/v1/events
Content-Type: application/json
X-Badu-Integration: <integration_id>
X-Badu-Timestamp: <seconds since 1970>
X-Badu-Signature: sha256=<hex HMAC-SHA256 of "<timestamp>.<raw body>", keyed with the signing key>

{"type": "booking.created", "id": "bk_812", "summary": "Ama Owusu booked a haircut, Friday 10:00",
 "data": {"service": "Haircut"}, "remember": {"kind": "customer", "name": "Ama Owusu", "fields": {"phone": "+233 20 000 0000"}}}
// PHP
$body = json_encode($update);
$ts = (string) time();
$sig = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $signingKey);

3. Add a Connect Badu button

So the owner doesn't copy anything, send them to their Badu with your app's address, a return page on your own site and a random state:

https://<their badu>/?connect=acme-bookings&url=https://acme.example&return=https://acme.example/settings/badu&state=<random>

The owner presses Allow, and Badu sends them back to your return page with badu_code, badu_url and state. Check state is the one you sent, make a key for Badu, then from your server:

POST <the Badu address the owner typed>/connect/v1/exchange
{"code": "<badu_code>", "app_key": "<the key you made for Badu>"}
# → {"integration_id": "…", "signing_key": "bik_…", "events_url": "…", "ask_url": "…"}

The code works once, for 10 minutes. Store the signing key encrypted; it's what signs your updates.

Optional: let staff ask Badu

If the owner allows it, your app's staff can ask the owner's Badu a question with the same signed headers: POST <badu>/connect/v1/ask with {"question": "…", "from": "Esi at the front desk"} answers {"reply": "…"}, with the owner's usual approval rules. Limits: 600 updates per 10 minutes and 30 questions an hour per app.

Text from your app was written by your users. Badu shows it and reports it, and never follows instructions inside it.

Skills: teach every Badu your product

If you make an app (accounting, HR, payments, farming, clinic records…), publish a small badu-skill.json file and owners can add your app from the Skills marketplace in one tap. Your app needs either an MCP server or an OpenAPI (REST) description.

{
  "schema": "badu-skill/1",
  "id": "acme.books",
  "name": "Acme Books",
  "author": "Acme Ltd",
  "category": "Bookkeeping",
  "description": "Invoices, customers and payments in Acme Books.",
  "instructions": "Check for an existing customer before creating one.",
  "connection": { "type": "openapi", "spec": "https://acme.example/openapi.json" },
  "credentials": [{ "key": "token", "label": "API key", "header": "Authorization", "prefix": "Bearer " }]
}

Need help?

Write to the Badu team from the contact link on our home page.