Skip to content

AI Meetings API

Endpoints for spawning meeting bots, inspecting their lifecycle, retrieving recordings + transcripts, and configuring webhook delivery. See AI Meetings for the conceptual overview and a complete worked example.

All endpoints are app-scoped — the app_id lives in the URL path, and the call is authenticated with a Butterbase service-key (bb_sk_...) belonging to that app’s owner.

MethodPathPurpose
POST/v1/{app_id}/ai/meetingsSpawn a meeting bot
GET/v1/{app_id}/ai/meetings/{bot_id}Get one bot — status, duration, URLs
DELETE/v1/{app_id}/ai/meetings/{bot_id}Force the bot to leave the call
GET/v1/{app_id}/ai/meetingsList this app’s bots
GET/v1/{app_id}/ai/meetings/_estimatePredict the USD charge for a session
GET/v1/{app_id}/ai/meetings/usageRecent actor_usage_logs rows for the app
PUT/v1/{app_id}/ai/meetings/webhookConfigure forward URL + (re)mint signing secret
GET/v1/ai/meetings/_statusPublic — { "available": true | false }
POST /v1/{app_id}/ai/meetings
Authorization: Bearer {token}
Content-Type: application/json
{
"meetingUrl": "https://zoom.us/j/12345...",
"transcript": true,
"recording": "mp4",
"botName": "Acme Notetaker",
"metadata": { "session_id": "abc123" }
}
FieldTypeDefaultNotes
meetingUrlstringrequiredAny Zoom / Meet / Teams / Webex URL
transcriptbooleantrueTranscribe in addition to recording
recording"mp4" | "audio_only" | false"mp4"false skips recording entirely
botNamestring"Butterbase Notetaker"Display name the bot uses when it joins the call. 1–64 chars
metadataRecord<string,string>{}Arbitrary string→string map. Keys may not start with bb_ (reserved)

Response:

{
"id": "c086e720-d319-44b8-82d8-3a363f2cd9f4",
"status": "joining",
"startedAt": null,
"completedAt": null,
"durationSeconds": null,
"recordingUrl": null,
"transcriptUrl": null,
"botName": "Acme Notetaker",
"metadata": { "session_id": "abc123" }
}

botName is echoed back so callers can confirm what was sent. Bots created before this field was supported come back with "Butterbase Notetaker".

startedAt / completedAt / URLs populate as the bot progresses. Poll GET /v1/{app_id}/ai/meetings/{id} or rely on webhooks.

GET /v1/{app_id}/ai/meetings/{bot_id}
Authorization: Bearer {token}

Returns the same shape as POST. The status field moves through:

StatusMeaning
joiningBot is dialing in
waiting_roomBot is in the waiting room awaiting host admit
in_callBot is in the call (recording may not have started yet)
recordingRecording in progress
endedCall ended; artifacts being finalised
doneTerminal — call finished cleanly, recordingUrl + transcriptUrl available
fatalTerminal — bot failed to join, was kicked, or had a fatal error
GET /v1/{app_id}/ai/meetings?status={status}&limit={n}&cursor={cursor}
Authorization: Bearer {token}

Query params (all optional):

ParamTypeDefaultNotes
statusstringFilter to one lifecycle phase
limitinteger201–100
cursorstringFrom nextCursor in a prior response

Response:

{
"bots": [ /* MeetingBot[] */ ],
"nextCursor": "..." | null
}
DELETE /v1/{app_id}/ai/meetings/{bot_id}
Authorization: Bearer {token}

Forces the bot to leave the call. Returns 204 on success. Idempotent — a stopped or already-done bot still returns 204.

GET /v1/{app_id}/ai/meetings/_estimate?durationMinutes={n}&transcript={bool}
Authorization: Bearer {token}
ParamTypeDefaultNotes
durationMinutesintegerrequired1 – 1440
transcriptbooleantrueWhether to include transcription cost

Response:

{ "usd": 0.39 }
PUT /v1/{app_id}/ai/meetings/webhook
Authorization: Bearer {token}
Content-Type: application/json
{
"forward_url": "https://your-app.example.com/recall/events",
"rotate_secret": true
}
FieldTypeNotes
forward_urlstring (URL)Where Butterbase will POST forwarded events
rotate_secretbooleanIf true (or no row exists yet), mint a fresh per-app secret

Response:

{
"ok": true,
"app_id": "app_abc123",
"forward_url": "https://your-app.example.com/recall/events",
"secret": "wsec_..."
}

secret is returned once — only on initial create and when rotate_secret: true. Store it immediately. On subsequent calls without rotate_secret, secret is null. Butterbase stores the value AES-256-GCM-encrypted; the platform decrypts it transiently each time it signs an outbound forward.

When the bot’s status advances or an artifact becomes ready, Butterbase POSTs to your forward_url:

POST /your/configured/path
content-type: application/json
x-bb-event: bot.done
x-bb-signature: v1,<base64 HMAC-SHA256>
{
"event": "bot.done",
"data": {
"bot": { "id": "c086e720-...", "metadata": { ... } },
"data": { "code": "done", "sub_code": null, "updated_at": "..." }
}
}

Recompute base64(HMAC-SHA256(<your wsec_>, <raw request body>)), prefix with v1,, and compare to x-bb-signature in constant time. The full Stripe / GitHub / Recall.ai webhook pattern — see the worked example.

Each new app gets these events forwarded by default:

  • bot.in_call_recording
  • bot.done
  • bot.fatal
  • recording.done
  • transcript.done
  • transcript.failed

recording.done and transcript.done events deliver only the artifact id and metadata. To get a downloadable URL, follow up with GET /v1/{app_id}/ai/meetings/{bot_id} and read recordingUrl / transcriptUrl from the response. URLs are short-lived (Recall re-mints them on demand), which is why they’re not baked into the webhook payload.

GET /v1/{app_id}/ai/meetings/usage
Authorization: Bearer {token}

Returns the last 100 rows from actor_usage_logs:

{
"rows": [
{
"id": "5",
"dimension": "recording",
"seconds": 44,
"usd_charged": "0.007333",
"created_at": "2026-06-11T19:18:53.734Z"
},
{
"id": "4",
"dimension": "transcription",
"seconds": 44,
"usd_charged": "0.002200",
"created_at": "2026-06-11T19:18:53.485Z"
}
]
}

One row per dimension (recording and, when transcription was enabled, transcription) per completed session.

GET /v1/ai/meetings/_status

No auth — public. Returns:

{ "available": true }

false means the provider isn’t registered on this deployment (e.g. an OSS-mode deployment without MEETINGS_API_KEY set). Use this to gate UI affordances client-side.

OpenAI-shaped: { "error": { "message", "type", "code" } }.

Statuserror.typeerror.codeWhen
400invalid_request_errorinvalid_requestRequest body failed validation. error.details has the zod issues.
401authentication_errormissing_credentialsNo / invalid Authorization header.
402billing_errorinsufficient_creditsAI credits balance can’t cover the up-front lease. error.required_usd + error.available_usd included.
403permission_errornot_authorizedAuthenticated user doesn’t own this app.
404invalid_request_errorapp_not_foundapp_id doesn’t exist.
501api_errorprovider_unavailableMeetings adapter isn’t registered on this deployment.
5xxapi_error(varies)Upstream provider error. Retry with backoff.