Skip to content

API Reference

The Bubbles API is a REST API served under the /v1 prefix. Responses are JSON. Set the two session-stable values once in your shell; per-call ids (like a bubble id) are inline placeholders you substitute as you go:

Terminal window
export BASE_URL="https://api-tom.usebubbles.com" # the origin serving the API
export BUBBLES_TOKEN="bbls_pat_…" # a personal access token (see Authentication)

Endpoints

Method Path Auth Description
POST /v1/api-keys session Create a personal access token (shown exactly once). Requires a workspace with paid-feature access (403 pro_plan_required otherwise); at most 10 active keys per user (409 invalid_request).
GET /v1/api-keys session List the caller’s active API keys (previews only; the secret is never returned).
DELETE /v1/api-keys/{id} session Revoke an API key. A revoked token yields the distinct 401 token_revoked envelope on use.
GET /v1/me pat The authenticated user (id, email, name, picture_url).
GET /v1/teams pat The caller’s teams with plan, the caller’s role, and the channels visible to them. Matches in-product membership: archived teams and hidden guest memberships (joined via a public bubble link) are excluded.
GET /v1/bubbles pat The caller’s bubbles, newest first — the bubbles they participate in (same completeness as the product home screen; public-but-unjoined bubbles do not list but are fetchable by id). Excludes archived, draft, template, and expired-hidden bubbles. Each row includes the bubble’s AI meeting-notes/summary text (summary, capped at 1000 characters; null when there are no notes) — the same text GET /v1/bubbles/{id} returns. Unknown query parameters are ignored; malformed values yield 400 invalid_request.
GET /v1/bubbles/search pat Search the caller’s bubbles by keyword, returning matching bubble summaries ranked by relevance. Matches fuzzily (prefix/typo-tolerant) against each bubble’s title, its author’s name, and its AI-generated meeting notes/summary — so a keyword that appears only inside the notes still finds the bubble; title matches rank highest, then author, then notes. Searches the SAME readable set as GET /v1/bubbles — a bubble the caller cannot read is never returned (a notes-only match on an unreadable bubble never surfaces). Each result row includes the bubble’s AI meeting-notes/summary text (summary, capped at 1000 characters; null when there are no notes) so an agent can read why it matched inline. q is required (non-empty, ≤256 chars); an empty or overlong q yields 400 invalid_request. Supports the same team_id/space_id/type/created_after/created_before filters as the list endpoint. Pagination is by opaque cursor (relevance-ordered); rows exclude transcripts and comments (fetch a bubble by id for those).
GET /v1/bubbles/{id} pat A single bubble by full uuid or by the short id used in app URLs. Nonexistent, archived, and unreadable bubbles are indistinguishable: all answer the identical 404 not_found envelope.
GET /v1/bubbles/{id}/transcript pat The bubble’s diarized transcript. 404 not_found when the bubble has no transcript (yet); the same uniform 404 for unreadable/nonexistent bubbles.
GET /v1/bubbles/{id}/comments pat The bubble’s comments in reading order (oldest first). Archived and draft threads/comments are excluded, matching what a reader sees in the product.
GET /v1/openapi.json none The OpenAPI 3.1 document describing this API, generated from this same registry. Unauthenticated; its servers entry is derived from the request host at runtime (no hardcoded base URL).
POST /v1/webhooks pat Create a webhook subscription for recording-ready events; the signing secret is returned exactly once. Requires the webhooks:manage scope (PATs carry it implicitly) and paid-feature access. Also accepts an app session token (the settings UI).
GET /v1/webhooks pat List the caller’s webhook subscriptions (the signing secret is omitted). Requires the webhooks:manage scope.
GET /v1/webhooks/{id} pat Fetch one webhook subscription by id (the signing secret is omitted); 404 not_found if it does not exist or is not the caller’s. Requires the webhooks:manage scope.
PATCH /v1/webhooks/{id} pat Update a webhook subscription’s url, events, filters, or status. Each field you include is replaced wholesale (a partial update at the top level, not a deep merge): sending filters replaces the entire filters object — e.g. PATCH { "filters": { "only_my_bubbles": false } } drops a previously-set bubble_types — and an empty { "filters": {} } clears all filters. Omit a field to leave it unchanged. status accepts "active" only — used to re-enable a subscription auto-disabled after repeated delivery failures; there is no manual disable. Requires the webhooks:manage scope.
DELETE /v1/webhooks/{id} pat Delete a webhook subscription. Requires the webhooks:manage scope.
POST /v1/webhooks/{id}/test pat Queue a synthetic webhook.test delivery through the signed delivery path (202 queued), to verify a receiver end-to-end. Requires the webhooks:manage scope.
POST /v1/meetings/join pat Send the Bubbles notetaker to join and record a meeting by link. Requires the meetings:join scope (PATs carry it implicitly). meeting_url must be a Google Meet, Microsoft Teams, Zoom, Webex, or GoTo link — any other link is rejected with 400 invalid_request and no notetaker is sent. team_id is the workspace the recording is saved into. Returns the created meeting-join resource; poll GET /v1/meetings/join/{id} for the live status and GET /v1/bubbles for the recording as it materializes.
GET /v1/meetings/join/{id} pat Read a dispatched join's live status. {id} is the join id returned by POST /v1/meetings/join. Requires the meetings:join scope. Returns { id, type:"meeting_join", status, bot_in_call, fatal_reason, team_id, updated_at } where status is one of dispatched | joining | in_waiting_room | in_call | recording | done | fatal (fatal_reason carries the reason, e.g. meeting_requires_sign_in). A join you do not own, or that does not exist, returns 404 not_found (indistinguishable). The meeting URL / passcode is never returned.
POST /v1/signup/start none Begin an agent-driven signup: submit an email, and Bubbles emails a verification code. The response is identical whether or not an account already exists (it never reveals whether the email is registered before the code is verified — the unified flow logs in existing users and signs up new ones). Returns a signup_session to pass to the next steps and relay_to_user text to show the user. Call again to resend. A malformed/disallowed/temporary email is rejected with 400 invalid_request. Per-IP throttled.
POST /v1/signup/verify none Verify the emailed code for a signup_session from POST /v1/signup/start. On success returns a new signup_session advanced to the workspace step (logging in an existing account or signing up a new one — the outcome is only revealed here, after the code is verified). A wrong or expired code returns 401 unauthorized (same rules as the web flow).
POST /v1/signup/workspaces none List the existing workspaces the verified email may join by its domain — only after the emailed code is verified (a not-yet-verified session returns 409). Only the domain-matched workspaces the verified user is allowed to see are returned; nothing workspace-related is exposed before verification.
POST /v1/signup/workspace none Join a listed existing workspace (action:"join", team_id) or create a new one (action:"create", workspace_name) — the same createTeamSnapshot / self-invite path the web flow uses. A team_id that is not joinable by the verified email domain is refused with 403 forbidden. On create, the response also carries the new workspace's free Pro-trial status with its benefits (pro_trial) and, when the verified email is a non-public company domain, a domain_join offer; the agent may then call action:"enable_domain_join" to let anyone with that verified email domain auto-join the workspace (only the user's own verified non-public company domain is ever accepted — never a public or consumer email domain). Returns the resulting team_id and advances to the calendar step.
POST /v1/signup/calendar none Return the Google and Microsoft work-calendar connection links plus the benefit explanation, so the agent can relay them to the user (connecting a calendar lets Bubbles auto-join and take notes). Signup is complete with or without connecting — the user can skip.
GET /v1/signup/start none GET alias of POST /v1/signup/start — same handler, same validation, same JSON. Begin an agent-driven signup by passing the email in the query string; Bubbles emails a verification code and returns a signup_session. Per-IP throttled.
GET /v1/signup/verify none GET alias of POST /v1/signup/verify — verify the emailed code for a signup_session via the query string. Same 401 on a wrong/expired code as the POST.
GET /v1/signup/workspaces none GET alias of POST /v1/signup/workspaces — list the domain-matched joinable workspaces for a verified signup_session (409 before the code is verified). Nothing workspace-related is exposed before verification.
GET /v1/signup/workspace none GET alias of POST /v1/signup/workspace — join a listed workspace (action=join&team_id=…), create one (action=create&workspace_name=…), or (after create, for a non-public verified domain) open it to domain-join (action=enable_domain_join). Same authz + refusals as the POST (a non-joinable team_id → 403; only the user's own verified non-public company domain is ever accepted).
GET /v1/signup/calendar none GET alias of POST /v1/signup/calendar — the calendar-connect links + the MCP connect handoff for a verified signup_session. Signup is complete with or without connecting.

The machine-readable contract is served at GET /v1/openapi.json (OpenAPI 3.1, unauthenticated), generated from the same route registry that serves these endpoints.

Authentication header

Every data endpoint takes a personal access token as a bearer token:

Authorization: Bearer $BUBBLES_TOKEN

The API-key management endpoints (/v1/api-keys) are internal to the Bubbles app — they authenticate with the app’s signed-in session token (Authorization: Bearer), not a PAT; create and revoke keys from Settings → API keys. See Authentication.

The authenticated user

GET /v1/me — the token owner

Terminal window
curl "https://api-tom.usebubbles.com/v1/me" \
-H "Authorization: Bearer $BUBBLES_TOKEN"
{
"id": "be0c0582-…",
"email": "you@example.com",
"name": "Ada Lovelace",
"picture_url": null
}

Teams

GET /v1/teams — your teams, plans, roles, and channels

Terminal window
curl "https://api-tom.usebubbles.com/v1/teams" \
-H "Authorization: Bearer $BUBBLES_TOKEN"
{
"data": [
{
"id": "6114bc38-…",
"name": "Acme",
"plan": "free",
"role": "admin",
"channels": [{ "id": "955e6f13-…", "name": "General" }]
}
]
}

Bubbles

GET /v1/bubbles returns your bubbles newest-first. It is paginated (see below) and accepts these filters: team_id, space_id, type (meeting_recording | screen_recording | custom_image), created_after, and created_before (both exclusive ISO-8601 instants). Unknown parameters are ignored; malformed values return 400 invalid_request. To find bubbles by keyword rather than by recency, use Search.

GET /v1/bubbles — first page

Terminal window
curl "https://api-tom.usebubbles.com/v1/bubbles?limit=2" \
-H "Authorization: Bearer $BUBBLES_TOKEN"

GET /v1/bubbles — filter by type

Terminal window
curl "https://api-tom.usebubbles.com/v1/bubbles?type=meeting_recording&limit=2" \
-H "Authorization: Bearer $BUBBLES_TOKEN"
{
"data": [
{
"id": "1dd21223-…",
"type": "meeting_recording",
"title": "Weekly sync",
"url": "https://tom.usebubbles.com/…",
"created_at": "2026-07-03T15:45:49.193Z",
"team_id": "6114bc38-…",
"channel_id": "955e6f13-…",
"author_id": "ceaf1bf2-…",
"thumbnail_url": "https://…/weekly-sync.thumb.png",
"duration_seconds": 1834.5,
"comment_count": 0,
"expires_at": null,
"has_summary": true
}
],
"has_more": true,
"next_cursor": "eyJ2IjoxLCJjcmVhdGVkX2F0Ijo…"
}

GET /v1/bubbles/search finds your bubbles by keyword, ranked by relevance — use it when you know a topic, project, person, or something discussed rather than a bubble’s position in the list. It matches fuzzily (prefix- and typo-tolerant) against each bubble’s title, its author’s name, and its AI-generated meeting notes/summary, so a keyword that appears only inside the notes still finds the bubble (title matches rank highest, then author, then notes). It searches the same readable set as GET /v1/bubbles, so a bubble you can’t read is never returned. Each row carries the bubble’s notes text in summary (capped at 1,000 characters; null when there are no notes) so you can read why it matched without a second request. q is required (non-empty, ≤256 characters); an empty or overlong q returns 400 invalid_request. It accepts the same team_id, space_id, type, created_after, and created_before filters as the list endpoint and paginates by the same opaque cursor.

GET /v1/bubbles/search — ranked matches with notes text

Terminal window
curl "https://api-tom.usebubbles.com/v1/bubbles/search?q=weekly" \
-H "Authorization: Bearer $BUBBLES_TOKEN"
{
"data": [
{
"id": "1dd21223-…",
"type": "meeting_recording",
"title": "Weekly sync",
"url": "https://tom.usebubbles.com/…",
"created_at": "2026-07-03T15:45:49.193Z",
"team_id": "6114bc38-…",
"channel_id": "955e6f13-…",
"author_id": "ceaf1bf2-…",
"thumbnail_url": "https://…/weekly-sync.thumb.png",
"duration_seconds": 1834.5,
"comment_count": 0,
"expires_at": null,
"has_summary": true,
"summary": "1. Shipped the read endpoints.\n2. Adversary probes next.\n3. PR against dev."
}
],
"has_more": false,
"next_cursor": null
}

A single bubble

Fetch by full uuid or by the short id from an app URL. Nonexistent, archived, and unreadable bubbles all return the identical 404 not_found envelope, so ids cannot be probed.

GET /v1/bubbles/{id}

Terminal window
curl "https://api-tom.usebubbles.com/v1/bubbles/$BUBBLE_ID" \
-H "Authorization: Bearer $BUBBLES_TOKEN"
{
"id": "1dd21223-…",
"type": "meeting_recording",
"title": "Weekly sync",
"url": "https://tom.usebubbles.com/…",
"created_at": "2026-07-03T15:45:49.193Z",
"team_id": "6114bc38-…",
"channel_id": "955e6f13-…",
"channel_name": "General",
"author": { "id": "be0c0582-…", "name": "Ada Lovelace", "email": "you@example.com" },
"participants": [{ "id": "be0c0582-…", "name": "Ada Lovelace", "email": "you@example.com" }],
"media": {
"video_url": "https://…/weekly-sync.mp4",
"thumbnail_url": "https://…/weekly-sync.thumb.png",
"screenshot_url": null,
"duration_seconds": 1834.5,
"is_processed": true
}
}

Transcript

The diarized transcript. format=json (default) returns one segment per diarized entry with speaker and start/end seconds; format=text returns the plain-text, speaker-grouped rendition. A bubble with no transcript returns the same uniform 404 not_found.

GET /v1/bubbles/{id}/transcript (json)

Terminal window
curl "https://api-tom.usebubbles.com/v1/bubbles/$BUBBLE_ID/transcript" \
-H "Authorization: Bearer $BUBBLES_TOKEN"
{
"bubble_id": "1dd21223-…",
"format": "json",
"segments": [
{
"speaker": { "id": "be0c0582-…", "name": "Ada Lovelace" },
"text": "Morning everyone, thanks for joining.",
"start_time_seconds": 0,
"end_time_seconds": 6
}
]
}

Comments

Comments in reading order (oldest first); archived and draft threads are excluded. Paginated.

GET /v1/bubbles/{id}/comments

Terminal window
curl "https://api-tom.usebubbles.com/v1/bubbles/$BUBBLE_ID/comments?limit=2" \
-H "Authorization: Bearer $BUBBLES_TOKEN"
{
"data": [
{
"id": "912b19a1-…",
"thread_id": "5084a9d4-…",
"bubble_id": "1dd21223-…",
"kind": "text",
"body": "Great progress — shipping today.",
"video_url": null,
"image_url": null,
"created_at": "2026-07-03T15:12:30.193Z",
"author": { "id": "be0c0582-…", "name": "Ada Lovelace" },
"is_pinned": true,
"timestamp_seconds": 12.5,
"url": "https://tom.usebubbles.com/…"
}
],
"has_more": false,
"next_cursor": null
}

Meetings

Send the Bubbles AI notetaker to join and record a live meeting by its link. This is a write endpoint — it dispatches the notetaker to join the call on your behalf and saves the resulting recording into a workspace you choose.

Method & pathPurpose
POST /v1/meetings/joinSend the notetaker to join and record a meeting by link.
GET /v1/meetings/join/{id}Read a dispatched join’s live status.

Requires the meetings:join scope. Personal access tokens carry it implicitly; an OAuth token must have been granted it on the consent screen. Supported meeting links are Google Meet, Microsoft Teams, Zoom, Webex, and GoTo — any other link is rejected with 400 invalid_request and no notetaker is sent.

Terminal window
curl -X POST "https://api-tom.usebubbles.com/v1/meetings/join" \
-H "Authorization: Bearer $BUBBLES_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"meeting_url": "https://meet.google.com/abc-defg-hij",
"team_id": "6114bc38-…"
}'

A 201 Created returns the dispatched meeting-join resource (never a bare 2xx). team_id is required — the workspace to record into (see GET /v1/teams). The returned id is the join id you poll for status (below); poll GET /v1/bubbles for the recording itself as it materializes after the meeting:

{
"id": "d49615f3-…",
"type": "meeting_join",
"status": "dispatched",
"meeting_url": "https://meet.google.com/abc-defg-hij",
"team_id": "6114bc38-…",
"created_at": "2026-07-05T16:31:14.317Z"
}

Other responses: 403 forbidden if you cannot record for that workspace, or your token lacks the meetings:join scope; 429 rate_limited if you exceed the hourly dispatch limit or your team’s free-plan weekly recording cap.

Poll the join status

Read the live status of a dispatched join with its id. Requires the same meetings:join scope.

Terminal window
curl "https://api-tom.usebubbles.com/v1/meetings/join/{id}" \
-H "Authorization: Bearer $BUBBLES_TOKEN"
{
"id": "d49615f3-…",
"type": "meeting_join",
"status": "recording",
"bot_in_call": true,
"fatal_reason": null,
"team_id": "6114bc38-…",
"updated_at": "2026-07-05T16:32:03.114Z"
}

status advances through dispatchedjoiningin_waiting_roomin_callrecordingdone, or fatal if the notetaker could not record (for example a meeting that requires signing in — fatal_reason carries the reason). bot_in_call is true while the notetaker is present in the call. The meeting URL and any passcode are never returned by this endpoint. A join you do not own, or one that does not exist, returns the identical 404 not_found — the two are indistinguishable.

Signing up (agent-driven)

The recommended way to onboard through an assistant is the MCP connector at https://api-tom.usebubbles.com/mcp (see MCP Server), which signs the user in inside the conversation; this HTTP flow is the fallback for clients that cannot add an MCP connector. It lets an AI assistant create an account for a user over plain HTTP — no dashboard needed — and handles the whole flow: email verification, choosing or creating a workspace, and calendar setup. The five steps are unauthenticated and carry state in a short-lived, single-purpose signup_session token returned by each step — it is not an API credential (the read/write endpoints above reject it). Every response includes a next_step and a relay_to_user message the agent can show the user.

  1. Start — submit an email; Bubbles emails a verification code.

POST /v1/signup/start — email the verification code

Terminal window
curl -X POST "https://api-tom.usebubbles.com/v1/signup/start" \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'
  1. Verify the code — POST /v1/signup/verify with { signup_session, code }.
  2. Resolve workspacesPOST /v1/signup/workspaces with { signup_session } lists any workspaces the verified email can join by its domain.
  3. Join or createPOST /v1/signup/workspace with { signup_session, action: "join", team_id } or { signup_session, action: "create", workspace_name }.
  4. Connect a calendarPOST /v1/signup/calendar returns the Google and Microsoft connect links and the reasons to connect (so Bubbles can auto-join and take notes on your meetings); the user can skip. Signup is complete either way.

The full request/response schemas for each step are in the endpoint table and field reference above.

Pagination

List endpoints (/v1/bubbles, /v1/bubbles/{id}/comments) return an envelope of { data, has_more, next_cursor }. Pass limit (integer 1–100, default 25) to size a page. When has_more is true, pass the opaque next_cursor back as ?cursor= to fetch the next page:

Terminal window
curl "https://api-tom.usebubbles.com/v1/bubbles?cursor=$NEXT_CURSOR" \
-H "Authorization: Bearer $BUBBLES_TOKEN"

Cursors are opaque keyset positions — treat them as configuration and never parse or construct one. They stay stable across concurrent inserts and deletes.

Field reference

Every field returned by (or accepted by) these endpoints, typed. Every response field is always present — an unset value is null, never a missing key. T | null marks a nullable value; T[] an array; object-typed fields link to their own DTO. These are the same schemas published in GET /v1/openapi.json (components.schemas), so a code generator produces the exact types shown here.

BubblesPerson

A person on a bubble (author or participant).

FieldTypeDescription
idstringThe Bubbles user id.
namestring | nullDisplay name, or null when unknown.
emailstring | nullEmail, or null when unknown/withheld.

BubblesMedia

The bubble's media assets. Absolute https URLs; unset assets are null.

FieldTypeDescription
video_urlstring | nullThe processed video URL, or null (e.g. an image bubble).
thumbnail_urlstring | nullThe thumbnail/preview image URL, or null.
screenshot_urlstring | nullThe screenshot/still-image URL, or null.
duration_secondsnumber | nullMedia duration in seconds, or null.
is_processedboolean | nullWhether media processing finished, or null when unknown.

BubblesMeeting

Meeting metadata, present only for meeting_recording bubbles (else null).

FieldTypeDescription
platformstring | nullThe meeting platform (e.g. google_meet, zoom), or null.
start_timestring | nullScheduled/actual meeting start (ISO-8601 UTC), or null.

Bubble

A single bubble (GET /v1/bubbles/{id}). Every field is always present; "unset" is null.

FieldTypeDescription
idstringThe bubble uuid.
type"meeting_recording" | "screen_recording" | "custom_image"The bubble classification.
titlestring | nullThe bubble title, or null.
urlstringThe app deep link to the bubble.
created_atstring | nullCreation instant (ISO-8601 UTC), or null for legacy bubbles.
team_idstring | nullOwning team id, or null.
channel_idstring | nullOwning channel (product: space) id, or null.
channel_namestring | nullOwning channel name, or null.
authorBubblesPerson | nullThe author (for meetings, the calendar organizer), or null.
participantsBubblesPerson[]People who participated in the bubble.
mediaBubblesMediaThe bubble's media assets.
summarystring | nullThe AI-generated summary text, when generated, else null.
action_itemsstring | nullThe AI-generated action items as plain text (v1), when generated, else null.
has_transcriptbooleanWhether a transcript is available for this bubble.
comment_countintegerNumber of comments on the bubble.
is_publicbooleanWhether the bubble is shared via a public link.
embed_codestring | nullHTML embed snippet, or null when not embeddable.
expires_atstring | nullExpiry instant (ISO-8601 UTC), or null when it does not expire.
archivedbooleanWhether the bubble is archived.
creator_idstring | nullThe raw creator user id, or null.
response_requiredbooleanWhether the bubble requests a response from viewers.
has_external_participantsbooleanWhether anyone outside the team participated.
external_participant_email_domainsstring[]Email domains of external participants.
meetingBubblesMeeting | nullMeeting metadata for meeting_recording bubbles, else null.

BubblesSummary

A list-item bubble (GET /v1/bubbles), derived from summaries — no hydration.

FieldTypeDescription
idstringThe bubble uuid.
type"meeting_recording" | "screen_recording" | "custom_image"The bubble classification.
titlestring | nullThe bubble title, or null.
urlstringThe app deep link to the bubble.
created_atstring | nullCreation instant (ISO-8601 UTC), or null.
team_idstring | nullOwning team id, or null.
channel_idstring | nullOwning channel id, or null.
author_idstring | nullRaw creator id (no name — list is unhydrated), or null.
thumbnail_urlstring | nullThe thumbnail image URL, or null.
duration_secondsnumber | nullMedia duration in seconds, or null.
comment_countintegerNumber of comments on the bubble.
expires_atstring | nullExpiry instant (ISO-8601 UTC), or null.
has_summarybooleanWhether an AI meeting-notes summary exists (see summary).
summarystring | nullThe AI-generated meeting notes/summary text (the same text GET /v1/bubbles/{id} returns as summary), or null when there are no notes. Capped at 1000 characters for row size — a truncated value ends with an ellipsis; fetch the bubble by id for the full text.

User

The authenticated user (GET /v1/me).

FieldTypeDescription
idstringThe Bubbles user id.
emailstring | nullEmail, or null.
namestring | nullDisplay name, or null.
picture_urlstring | nullAvatar URL, or null.

TeamChannel

A channel (product: space) visible to the caller.

FieldTypeDescription
idstringThe channel id.
namestring | nullThe channel name, or null.

Team

A team from GET /v1/teams, with the caller's role and visible channels.

FieldTypeDescription
idstringThe team id.
namestring | nullThe team name, or null.
plan"free" | "pro"The team's plan.
role"admin" | "member" | "guest" | nullThe caller's role in this team, or null.
channelsTeamChannel[]Channels (spaces) visible to the caller.

TranscriptSpeaker

The speaker of a transcript segment.

FieldTypeDescription
idstring | nullThe Bubbles user id when the speaker is a known user, else null.
namestring | nullThe diarized speaker name when available, else null.

TranscriptSegment

One diarized transcript entry.

FieldTypeDescription
speakerTranscriptSpeaker | nullThe segment speaker, or null.
textstringThe spoken text of the segment.
start_time_secondsnumberSegment start, seconds from media start.
end_time_secondsnumberSegment end, seconds from media start.

TranscriptJson

A diarized transcript (format=json, the default).

FieldTypeDescription
bubble_idstringThe bubble uuid this transcript belongs to.
format"json"The transcript format.
segmentsTranscriptSegment[]The diarized segments in order.

TranscriptText

A plain-text transcript (format=text).

FieldTypeDescription
bubble_idstringThe bubble uuid this transcript belongs to.
format"text"The transcript format.
textstringThe plain-text rendition: `Speaker: …` blocks separated by blank lines.

Transcript

GET /v1/bubbles/{id}/transcript — json (default) or text per the format query param.

One of: TranscriptJson | TranscriptText .

CommentAuthor

The author of a comment.

FieldTypeDescription
idstringThe Bubbles user id.
namestring | nullDisplay name, or null.

Comment

A comment on a bubble (GET /v1/bubbles/{id}/comments).

FieldTypeDescription
idstringThe comment id.
thread_idstringThe containing thread id.
bubble_idstringThe bubble uuid the comment is on.
kind"text" | "video" | "image"The comment kind.
bodystring | nullText-comment body; null for video/image comments.
video_urlstring | nullVideo-comment media URL, or null.
image_urlstring | nullImage-comment media URL, or null.
created_atstringCreation instant (ISO-8601 UTC).
authorCommentAuthorThe comment author.
is_pinnedbooleanWhether the containing thread is pinned.
timestamp_secondsnumber | nullThe anchored video timestamp, or null when not anchored.
urlstringDeep link into the app opening this comment.

WebhookSubscriptionFilters

Optional post-match filters on a webhook subscription.

FieldTypeDescription
bubble_types (optional)string[]Only deliver for these bubble types.
only_my_bubbles (optional)booleanOnly deliver for bubbles the subscriber created.

WebhookSubscription

A webhook subscription (GET /v1/webhooks[/{id}]); the signing secret is omitted.

FieldTypeDescription
idstringThe subscription id (wh_…).
urlstringThe receiver URL deliveries are POSTed to.
eventsstring[]Subscribed event types (bubble.ready | meeting_recording.ready | screen_recording.ready).
team_idstring | nullTeam scope, or null (all teams).
space_idstring | nullChannel scope, or null.
filtersWebhookSubscriptionFilters | nullPost-match filters, or null.
status"active" | "disabled"Delivery status; auto-disabled after repeated failures.
created_atstringCreation instant (ISO-8601 UTC).
last_success_atstring | nullLast successful delivery instant, or null.
last_failure_atstring | nullLast failed delivery instant, or null.
disabled_atstring | nullWhen the subscription was disabled, or null.
disabled_reasonstring | nullWhy it was disabled (e.g. delivery_failures), or null.

WebhookSubscriptionCreated

POST /v1/webhooks 201 response — the same shape plus the signing secret, shown exactly once.

FieldTypeDescription
idstringThe subscription id (wh_…).
urlstringThe receiver URL deliveries are POSTed to.
eventsstring[]Subscribed event types.
team_idstring | nullTeam scope, or null.
space_idstring | nullChannel scope, or null.
filtersWebhookSubscriptionFilters | nullPost-match filters, or null.
status"active" | "disabled"Delivery status.
created_atstringCreation instant (ISO-8601 UTC).
last_success_atstring | nullLast successful delivery instant, or null.
last_failure_atstring | nullLast failed delivery instant, or null.
disabled_atstring | nullWhen disabled, or null.
disabled_reasonstring | nullWhy disabled, or null.
secretstringThe signing secret (whsec_…) for HMAC verification — returned ONCE.

ApiKeySummary

A managed API key (GET /v1/api-keys); the secret is never returned.

FieldTypeDescription
idstringThe key id.
namestringThe key name.
token_previewstringA safe-to-show token preview (bbls_pat_ab12…).
created_atstringCreation instant (ISO-8601 UTC).
last_used_atstring | nullLast-used instant, or null if never used.

ApiKeyCreated

POST /v1/api-keys 201 response — the full token is shown exactly once.

FieldTypeDescription
idstringThe key id.
namestringThe key name.
tokenstringThe full personal access token (bbls_pat_…) — shown ONCE; store it now.
token_previewstringA safe-to-show token preview.
created_atstringCreation instant (ISO-8601 UTC).

MeetingJoinCreated

POST /v1/meetings/join 201 response — the dispatched notetaker resource.

FieldTypeDescription
idstringThe join id; poll GET /v1/meetings/join/{id}.
type"meeting_join"The resource type.
statusstringInitial status (dispatched).
meeting_urlstringThe normalized meeting link the notetaker was sent to.
team_idstringThe workspace the recording is saved into.
created_atstringDispatch instant (ISO-8601 UTC).

MeetingJoinStatus

GET /v1/meetings/join/{id} — a dispatched join's live status. The meeting URL/passcode is never returned.

FieldTypeDescription
idstringThe join id.
type"meeting_join"The resource type.
status"dispatched" | "joining" | "in_waiting_room" | "in_call" | "recording" | "done" | "fatal"The live join status.
bot_in_callbooleanWhether the notetaker is currently in the call.
fatal_reasonstring | nullThe failure reason when status is fatal (e.g. meeting_requires_sign_in), else null.
team_idstring | nullThe workspace the recording is saved into, or null.
updated_atstring | nullLast status update instant (ISO-8601 UTC), or null.

BubblesList

GET /v1/bubbles — cursor-paginated list of bubble summaries.

FieldTypeDescription
dataBubblesSummary[]The page of bubbles.
has_morebooleanWhether another page exists.
next_cursorstring | nullOpaque cursor for the next page, or null when exhausted.

CommentList

GET /v1/bubbles/{id}/comments — cursor-paginated list of comments.

FieldTypeDescription
dataComment[]The page of comments.
has_morebooleanWhether another page exists.
next_cursorstring | nullOpaque cursor for the next page, or null.

TeamList

GET /v1/teams — the finite list of the caller's teams.

FieldTypeDescription
dataTeam[]The caller's teams.

ApiKeyList

GET /v1/api-keys — the finite list of the caller's active keys.

FieldTypeDescription
dataApiKeySummary[]The caller's active API keys.

WebhookSubscriptionList

GET /v1/webhooks — the finite list of the caller's subscriptions.

FieldTypeDescription
dataWebhookSubscription[]The caller's webhook subscriptions.

CreateApiKeyRequest

POST /v1/api-keys request body.

FieldTypeDescription
namestringA human-readable name for the key (1–100 chars).

CreateWebhookRequest

POST /v1/webhooks request body.

FieldTypeDescription
urlstringThe https receiver URL to POST deliveries to.
eventsstring[]Event types to subscribe to (at least one).
team_id (optional)stringRestrict to one team.
space_id (optional)stringRestrict to one channel.
filters (optional)WebhookSubscriptionFiltersOptional post-match filters.

UpdateWebhookRequest

PATCH /v1/webhooks/{id} request body — all fields optional. status accepts "active" only (re-enable).

FieldTypeDescription
url (optional)stringA new receiver URL.
events (optional)string[]A new event set.
status (optional)"active"Set to "active" to re-enable an auto-disabled subscription.

JoinMeetingRequest

POST /v1/meetings/join request body.

FieldTypeDescription
meeting_urlstringA Google Meet, Microsoft Teams, Zoom, Webex, or GoTo meeting link.
team_idstringThe workspace to save the recording into (see GET /v1/teams).

SignupStartRequest

POST /v1/signup/start request body.

FieldTypeDescription
emailstringThe work email to sign up / send the verification code to.

SignupStartResponse

POST /v1/signup/start response — a verification code was emailed. The response is identical whether or not an account already exists (it never reveals whether the email is registered before the code is verified).

FieldTypeDescription
next_step"verify"The next call to make.
agent_contextstringAgent-facing guidance for this step: what happened, what to tell the user, and what to ask/call next.
relay_to_userstringPlain-language text for the agent to show the user.
signup_sessionstringOpaque token to pass to the next step.

SignupVerifyRequest

POST /v1/signup/verify request body.

FieldTypeDescription
signup_sessionstringThe token from POST /v1/signup/start.
codestringThe verification code Bubbles emailed the user.

SignupVerifyResponse

POST /v1/signup/verify response — email verified.

FieldTypeDescription
next_step"workspaces"The next call to make.
agent_contextstringAgent-facing guidance for this step: what happened, what to tell the user, and what to ask/call next.
relay_to_userstringPlain-language text for the agent to show the user.
signup_sessionstringOpaque token advanced to the workspace step.

SignupWorkspacesRequest

POST /v1/signup/workspaces request body.

FieldTypeDescription
signup_sessionstringA verified signup token.

SignupWorkspace

A workspace the verified email may join by domain match.

FieldTypeDescription
idstringThe workspace (team) id — pass as team_id to join.
namestringThe workspace name.

SignupWorkspacesResponse

POST /v1/signup/workspaces response — the domain-matched joinable set.

FieldTypeDescription
next_step"workspace"The next call to make.
workspacesSignupWorkspace[]Joinable workspaces (may be empty → create a new one).
agent_contextstringAgent-facing guidance for this step: what happened, what to tell the user, and what to ask/call next.
relay_to_userstringPlain-language text for the agent to show the user.
signup_sessionstringOpaque token to pass to the next step.

SignupWorkspaceRequest

POST /v1/signup/workspace request body — join a listed workspace, create a new one, or (after create, if the response offered it) enable domain-join.

FieldTypeDescription
signup_sessionstringA verified signup token.
action"join" | "create" | "enable_domain_join"Join an existing workspace, create a new one, or (after creating, for a non-public verified domain the response offered) allow anyone with that verified email domain to auto-join.
team_id (optional)stringFor action:"join" — a listed, domain-matched workspace id.
workspace_name (optional)stringFor action:"create" — the new workspace name.

SignupProTrial

The new workspace's free Pro-trial status (present only when creating).

FieldTypeDescription
activebooleanWhether the new workspace is on a free Pro trial.
planstringThe workspace plan (e.g. "pro").
benefitsstring[]The Pro plan benefits to relay to the user.

SignupDomainJoin

Whether the user may open their new workspace to their verified email domain (present only when creating).

FieldTypeDescription
availablebooleanTrue when the verified email domain is a non-public company domain that can be offered for auto-join.
domainstringThe user's verified email domain.

SignupWorkspaceResponse

POST /v1/signup/workspace response — workspace ready.

FieldTypeDescription
next_step"calendar"The next call to make.
team_id (optional)stringThe workspace the user joined or created.
pro_trial (optional)SignupProTrialFree Pro-trial status (present when creating).
domain_join (optional)SignupDomainJoinDomain-join offer status (present when creating).
domain_join_enabled (optional)booleanTrue on an action:"enable_domain_join" response — the domain is now open for auto-join.
domain (optional)stringThe domain opened for auto-join (enable_domain_join response).
agent_contextstringAgent-facing guidance for this step: what happened, what to tell the user, and what to ask/call next.
relay_to_userstringPlain-language text for the agent to show the user.
signup_sessionstringOpaque token advanced to the calendar step.

SignupCalendarRequest

POST /v1/signup/calendar request body.

FieldTypeDescription
signup_sessionstringA verified signup token.

SignupMcpTool

One tool the connected MCP server exposes.

FieldTypeDescription
namestringThe tool name (e.g. "search_bubbles").
titlestringThe human-readable tool title.
kind"read" | "write"Whether the tool only reads data or performs a write action.
summarystringA one-line summary of what the tool does.

SignupMcpConnect

How to connect an AI assistant to Bubbles over MCP, plus the connected server's tools. Reuses the same OAuth 2.1 / dynamic-client-registration / RFC-9728 flow as the documented MCP server — no second connect surface.

FieldTypeDescription
endpointstringThe Bubbles MCP server URL to add to an MCP client.
oauth_protected_resourcestringThe RFC 9728 protected-resource metadata URL an MCP client discovers the authorization server from.
scopesstring[]The OAuth scopes the connected client may be granted.
how_to_connectstringProvider-agnostic, step-by-step connect instructions (add the URL, one-time browser consent) for the agent to relay.
toolsSignupMcpTool[]The tools the connected MCP server exposes.

SignupCalendarResponse

POST /v1/signup/calendar response — calendar connect links + MCP connect + skip.

FieldTypeDescription
next_step"done"Signup is complete after this step.
whystring[]The benefits of connecting a work calendar.
google_connect_urlstringLink for the user to connect Google Calendar.
microsoft_connect_urlstringLink for the user to connect Microsoft/Outlook Calendar.
skipbooleanAlways true — the user may skip connecting a calendar.
mcpSignupMcpConnectHow to connect an AI assistant to Bubbles over MCP + the connected tools (owner directive 5885769d).
agent_contextstringAgent-facing guidance for this step: what happened, what to tell the user, and what to ask/call next.
relay_to_userstringPlain-language text for the agent to show the user.
signup_sessionstringThe signup token (signup is complete).

Errors

Every error uses the envelope { "error": { "type": …, "message": … } } with one of these closed types:

Type HTTP status Meaning
invalid_request 400 The request body or parameters are malformed, or a constraint was violated (e.g. the active API key cap, returned with status 409).
unauthorized 401 No credential was provided, the credential is malformed or unknown, or the credential kind is not accepted on this endpoint.
token_revoked 401 The personal access token used has been revoked.
account_deleted 401 The account that owns this credential has been deleted/anonymized or disabled; the credential is rejected fail-closed even though the token itself is otherwise valid.
forbidden 403 The authenticated user may not perform this operation.
pro_plan_required 403 The operation requires a workspace that can access paid features (e.g. creating API keys).
not_found 404 The resource does not exist — or, for bubble reads, exists but is not readable by the caller (the response is identical to a missing resource so ids cannot be probed).
rate_limited 429 The per-credential rate limit was exceeded; retry after the number of seconds in the Retry-After header.
internal_error 500 An unexpected server error occurred; it has been reported.

An unauthenticated request:

{ "error": { "type": "unauthorized", "message": "Missing bearer token" } }

Rate limits

Requests are limited to 120 per minute per credential. Exceeding the limit returns 429 rate_limited; the Retry-After response header carries the number of seconds until the window resets. Back off for that long before retrying.