{"openapi":"3.1.0","info":{"title":"Bubbles Public API","version":"2.136.3","description":"REST API for Bubbles, authenticated with personal access tokens (bbls_pat_…) or first-party OAuth 2.1 tokens. Read endpoints require the bubbles:read scope; the /v1/webhooks write surface requires webhooks:manage and POST /v1/meetings/join requires meetings:join (PATs carry every scope implicitly). Management endpoints (/v1/api-keys) use an app session token. Error responses share the envelope { \"error\": { \"type\", \"message\" } }."},"servers":[{"url":"https://api.usebubbles.com","description":"This deployment"}],"paths":{"/v1/api-keys":{"post":{"summary":"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).","security":[{"SessionBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace cannot access paid features (pro_plan_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Constraint conflict, e.g. the active-key cap (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List the caller’s active API keys (previews only; the secret is never returned).","security":[{"SessionBearer":[]}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyList"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace cannot access paid features (pro_plan_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Constraint conflict, e.g. the active-key cap (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/api-keys/{id}":{"delete":{"summary":"Revoke an API key. A revoked token yields the distinct 401 token_revoked envelope on use.","security":[{"SessionBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Resource id (bubbles also accept the short id used in app URLs)."}],"responses":{"204":{"description":"No content."},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace cannot access paid features (pro_plan_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist or is not readable by the caller (not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Constraint conflict, e.g. the active-key cap (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/me":{"get":{"summary":"The authenticated user (id, email, name, picture_url).","security":[{"PatBearer":[]}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/teams":{"get":{"summary":"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.","security":[{"PatBearer":[]}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamList"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/bubbles":{"get":{"summary":"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.","security":[{"PatBearer":[]}],"parameters":[{"name":"team_id","in":"query","required":false,"schema":{"type":"string"},"description":"Only bubbles in this team (team id)."},{"name":"space_id","in":"query","required":false,"schema":{"type":"string"},"description":"Only bubbles in this channel (channel id)."},{"name":"type","in":"query","required":false,"schema":{"type":"string"},"description":"Only bubbles of this type (meeting_recording | screen_recording | custom_image)."},{"name":"created_after","in":"query","required":false,"schema":{"type":"string"},"description":"Only bubbles created strictly after this instant (ISO-8601 timestamp (exclusive))."},{"name":"created_before","in":"query","required":false,"schema":{"type":"string"},"description":"Only bubbles created strictly before this instant (ISO-8601 timestamp (exclusive))."},{"name":"limit","in":"query","required":false,"schema":{"type":"string"},"description":"Page size (integer 1–100 (default 25))."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Opaque pagination cursor from the previous response’s next_cursor; never construct one (opaque string)."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BubblesList"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/bubbles/search":{"get":{"summary":"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. Requires current Home, visit, or visible-Space discovery in addition to read access — 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. Search pagination covers authorized matches within the first 1,000 fuzzy-ranked candidates; has_more and next_cursor refer to that window. Pagination is by opaque cursor (relevance-ordered); rows exclude transcripts and comments (fetch a bubble by id for those).","security":[{"PatBearer":[]}],"parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"The search query — matched fuzzily against bubble title, author name, and AI meeting-notes summary (non-empty string (≤256 chars))."},{"name":"team_id","in":"query","required":false,"schema":{"type":"string"},"description":"Only bubbles in this team (team id)."},{"name":"space_id","in":"query","required":false,"schema":{"type":"string"},"description":"Only bubbles in this channel (channel id)."},{"name":"type","in":"query","required":false,"schema":{"type":"string"},"description":"Only bubbles of this type (meeting_recording | screen_recording | custom_image)."},{"name":"created_after","in":"query","required":false,"schema":{"type":"string"},"description":"Only bubbles created strictly after this instant (ISO-8601 timestamp (exclusive))."},{"name":"created_before","in":"query","required":false,"schema":{"type":"string"},"description":"Only bubbles created strictly before this instant (ISO-8601 timestamp (exclusive))."},{"name":"limit","in":"query","required":false,"schema":{"type":"string"},"description":"Page size (integer 1–100 (default 25))."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Opaque pagination cursor from the previous response’s next_cursor; never construct one (opaque string)."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BubbleSearchResults"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/bubbles/{id}":{"get":{"summary":"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.","security":[{"PatBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Resource id (bubbles also accept the short id used in app URLs)."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Bubble"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist or is not readable by the caller (not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/bubbles/{id}/transcript":{"get":{"summary":"The bubble’s diarized transcript. 404 not_found when the bubble has no transcript (yet); the same uniform 404 for unreadable/nonexistent bubbles.","security":[{"PatBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Resource id (bubbles also accept the short id used in app URLs)."},{"name":"format","in":"query","required":false,"schema":{"type":"string"},"description":"json: one segment per diarized entry with speaker and start/end seconds; text: the plain-text rendition (speaker-grouped blocks) (json | text (default json))."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transcript"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist or is not readable by the caller (not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/bubbles/{id}/comments":{"get":{"summary":"The bubble’s comments in reading order (oldest first). Archived and draft threads/comments are excluded, matching what a reader sees in the product.","security":[{"PatBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Resource id (bubbles also accept the short id used in app URLs)."},{"name":"limit","in":"query","required":false,"schema":{"type":"string"},"description":"Page size (integer 1–100 (default 25))."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Opaque pagination cursor from the previous response’s next_cursor; never construct one (opaque string)."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentList"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist or is not readable by the caller (not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/openapi.json":{"get":{"summary":"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).","security":[],"responses":{"200":{"description":"Success."},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks":{"post":{"summary":"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).","security":[{"PatBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionCreated"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential lacks the required scope, or the workspace cannot access paid features (forbidden / pro_plan_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List the caller’s webhook subscriptions (the signing secret is omitted). Requires the webhooks:manage scope.","security":[{"PatBearer":[]}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionList"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential lacks the required scope, or the workspace cannot access paid features (forbidden / pro_plan_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}":{"get":{"summary":"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.","security":[{"PatBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Resource id (bubbles also accept the short id used in app URLs)."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscription"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential lacks the required scope, or the workspace cannot access paid features (forbidden / pro_plan_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist or is not readable by the caller (not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"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.","security":[{"PatBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Resource id (bubbles also accept the short id used in app URLs)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookRequest"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscription"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential lacks the required scope, or the workspace cannot access paid features (forbidden / pro_plan_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist or is not readable by the caller (not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a webhook subscription. Requires the webhooks:manage scope.","security":[{"PatBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Resource id (bubbles also accept the short id used in app URLs)."}],"responses":{"204":{"description":"No content."},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential lacks the required scope, or the workspace cannot access paid features (forbidden / pro_plan_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist or is not readable by the caller (not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/test":{"post":{"summary":"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.","security":[{"PatBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Resource id (bubbles also accept the short id used in app URLs)."}],"responses":{"202":{"description":"Accepted (queued)."},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential lacks the required scope, or the workspace cannot access paid features (forbidden / pro_plan_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist or is not readable by the caller (not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/meetings/join":{"post":{"summary":"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.","security":[{"PatBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinMeetingRequest"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeetingJoinCreated"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential lacks the required scope, or the workspace cannot access paid features (forbidden / pro_plan_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/meetings/join/{id}":{"get":{"summary":"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.","security":[{"PatBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Resource id (bubbles also accept the short id used in app URLs)."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeetingJoinStatus"}}}},"401":{"description":"Missing, malformed, or wrong-kind credential (unauthorized / token_revoked / account_deleted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential lacks the required scope, or the workspace cannot access paid features (forbidden / pro_plan_required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The resource does not exist or is not readable by the caller (not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (rate_limited); see the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/signup/start":{"post":{"summary":"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.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupStartRequest"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupStartResponse"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"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.","security":[],"parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string"},"description":"The work email to sign up (email)."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupStartResponse"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/signup/verify":{"post":{"summary":"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).","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupVerifyRequest"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupVerifyResponse"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"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.","security":[],"parameters":[{"name":"signup_session","in":"query","required":false,"schema":{"type":"string"},"description":"The token from /v1/signup/start (opaque token)."},{"name":"code","in":"query","required":false,"schema":{"type":"string"},"description":"The code Bubbles emailed the user (numeric code)."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupVerifyResponse"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/signup/workspaces":{"post":{"summary":"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.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupWorkspacesRequest"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupWorkspacesResponse"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"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.","security":[],"parameters":[{"name":"signup_session","in":"query","required":false,"schema":{"type":"string"},"description":"A verified signup token (opaque token)."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupWorkspacesResponse"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/signup/workspace":{"post":{"summary":"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.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupWorkspaceRequest"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupWorkspaceResponse"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"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).","security":[],"parameters":[{"name":"signup_session","in":"query","required":false,"schema":{"type":"string"},"description":"A verified signup token (opaque token)."},{"name":"action","in":"query","required":false,"schema":{"type":"string"},"description":"join | create | enable_domain_join (enum)."},{"name":"team_id","in":"query","required":false,"schema":{"type":"string"},"description":"For action=join — a listed domain-matched workspace id (team id)."},{"name":"workspace_name","in":"query","required":false,"schema":{"type":"string"},"description":"For action=create — the new workspace name (string)."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupWorkspaceResponse"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/signup/calendar":{"post":{"summary":"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.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupCalendarRequest"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupCalendarResponse"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"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.","security":[],"parameters":[{"name":"signup_session","in":"query","required":false,"schema":{"type":"string"},"description":"A verified signup token (opaque token)."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupCalendarResponse"}}}},"400":{"description":"A query parameter or request body was malformed (invalid_request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server error occurred (internal_error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"PatBearer":{"type":"http","scheme":"bearer","description":"A personal access token: `Authorization: Bearer bbls_pat_…`."},"SessionBearer":{"type":"http","scheme":"bearer","description":"An app Cognito/Auth0 session token (management endpoints only)."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","message"],"properties":{"type":{"type":"string","enum":["invalid_request","unauthorized","token_revoked","account_deleted","forbidden","pro_plan_required","not_found","rate_limited","internal_error"],"description":"The stable machine-readable error type."},"message":{"type":"string","description":"A human-readable description."}}}}},"BubblesPerson":{"type":"object","description":"A person on a bubble (author or participant).","required":["id","name","email"],"properties":{"id":{"type":"string","description":"The Bubbles user id."},"name":{"type":["string","null"],"description":"Display name, or null when unknown."},"email":{"type":["string","null"],"description":"Email, or null when unknown/withheld."}},"additionalProperties":false},"BubblesMedia":{"type":"object","description":"The bubble's media assets. Absolute https URLs; unset assets are null.","required":["video_url","thumbnail_url","screenshot_url","url_expires_at","duration_seconds","is_processed"],"properties":{"video_url":{"type":["string","null"],"description":"The processed video URL, or null (e.g. an image bubble)."},"thumbnail_url":{"type":["string","null"],"description":"The thumbnail/preview image URL, or null."},"screenshot_url":{"type":["string","null"],"description":"The screenshot/still-image URL, or null."},"url_expires_at":{"type":["string","null"],"description":"ISO-8601 instant at which video_url and screenshot_url stop working, or null when media is served unsigned. Read the bubble again for fresh URLs; thumbnail_url does not expire."},"duration_seconds":{"type":["number","null"],"description":"Media duration in seconds, or null."},"is_processed":{"type":["boolean","null"],"description":"Whether media processing finished, or null when unknown."}},"additionalProperties":false},"BubblesMeeting":{"type":"object","description":"Meeting metadata, present only for meeting_recording bubbles (else null).","required":["platform","start_time"],"properties":{"platform":{"type":["string","null"],"description":"The meeting platform (e.g. google_meet, zoom), or null."},"start_time":{"type":["string","null"],"description":"Scheduled/actual meeting start (ISO-8601 UTC), or null."}},"additionalProperties":false},"Bubble":{"type":"object","description":"A single bubble (GET /v1/bubbles/{id}). Every field is always present; \"unset\" is null.","required":["id","type","title","url","created_at","team_id","channel_id","channel_name","author","participants","media","summary","action_items","has_transcript","comment_count","is_public","embed_code","expires_at","archived","creator_id","response_required","has_external_participants","external_participant_email_domains","meeting"],"properties":{"id":{"type":"string","description":"The bubble uuid."},"type":{"type":"string","enum":["meeting_recording","screen_recording","custom_image"],"description":"The bubble classification."},"title":{"type":["string","null"],"description":"The bubble title, or null."},"url":{"type":"string","description":"The app deep link to the bubble."},"created_at":{"type":["string","null"],"description":"Creation instant (ISO-8601 UTC), or null for legacy bubbles."},"team_id":{"type":["string","null"],"description":"Owning team id, or null."},"channel_id":{"type":["string","null"],"description":"Owning channel (product: space) id, or null."},"channel_name":{"type":["string","null"],"description":"Owning channel name, or null."},"author":{"anyOf":[{"$ref":"#/components/schemas/BubblesPerson"},{"type":"null"}],"description":"The author (for meetings, the calendar organizer), or null."},"participants":{"type":"array","items":{"$ref":"#/components/schemas/BubblesPerson"},"description":"People who participated in the bubble."},"media":{"$ref":"#/components/schemas/BubblesMedia","description":"The bubble's media assets."},"summary":{"type":["string","null"],"description":"The AI-generated summary text, when generated, else null."},"action_items":{"type":["string","null"],"description":"The AI-generated action items as plain text (v1), when generated, else null."},"has_transcript":{"type":"boolean","description":"Whether a transcript is available for this bubble."},"comment_count":{"type":"integer","description":"Number of comments on the bubble."},"is_public":{"type":"boolean","description":"Whether the bubble is shared via a public link."},"embed_code":{"type":["string","null"],"description":"HTML embed snippet, or null when not embeddable."},"expires_at":{"type":["string","null"],"description":"Expiry instant (ISO-8601 UTC), or null when it does not expire."},"archived":{"type":"boolean","description":"Whether the bubble is archived."},"creator_id":{"type":["string","null"],"description":"The raw creator user id, or null."},"response_required":{"type":"boolean","description":"Whether the bubble requests a response from viewers."},"has_external_participants":{"type":"boolean","description":"Whether anyone outside the team participated."},"external_participant_email_domains":{"type":"array","items":{"type":"string"},"description":"Email domains of external participants."},"meeting":{"anyOf":[{"$ref":"#/components/schemas/BubblesMeeting"},{"type":"null"}],"description":"Meeting metadata for meeting_recording bubbles, else null."}},"additionalProperties":false},"BubblesSummary":{"type":"object","description":"A list-item bubble (GET /v1/bubbles), derived from summaries — no hydration.","required":["id","type","title","url","created_at","team_id","channel_id","author_id","thumbnail_url","duration_seconds","comment_count","expires_at","has_summary","summary"],"properties":{"id":{"type":"string","description":"The bubble uuid."},"type":{"type":"string","enum":["meeting_recording","screen_recording","custom_image"],"description":"The bubble classification."},"title":{"type":["string","null"],"description":"The bubble title, or null."},"url":{"type":"string","description":"The app deep link to the bubble."},"created_at":{"type":["string","null"],"description":"Creation instant (ISO-8601 UTC), or null."},"team_id":{"type":["string","null"],"description":"Owning team id, or null."},"channel_id":{"type":["string","null"],"description":"Owning channel id, or null."},"author_id":{"type":["string","null"],"description":"Raw creator id (no name — list is unhydrated), or null."},"thumbnail_url":{"type":["string","null"],"description":"The thumbnail image URL, or null."},"duration_seconds":{"type":["number","null"],"description":"Media duration in seconds, or null."},"comment_count":{"type":"integer","description":"Number of comments on the bubble."},"expires_at":{"type":["string","null"],"description":"Expiry instant (ISO-8601 UTC), or null."},"has_summary":{"type":"boolean","description":"Whether an AI meeting-notes summary exists (see summary)."},"summary":{"type":["string","null"],"description":"The 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."}},"additionalProperties":false},"User":{"type":"object","description":"The authenticated user (GET /v1/me).","required":["id","email","name","picture_url"],"properties":{"id":{"type":"string","description":"The Bubbles user id."},"email":{"type":["string","null"],"description":"Email, or null."},"name":{"type":["string","null"],"description":"Display name, or null."},"picture_url":{"type":["string","null"],"description":"Avatar URL, or null."}},"additionalProperties":false},"TeamChannel":{"type":"object","description":"A channel (product: space) visible to the caller.","required":["id","name"],"properties":{"id":{"type":"string","description":"The channel id."},"name":{"type":["string","null"],"description":"The channel name, or null."}},"additionalProperties":false},"Team":{"type":"object","description":"A team from GET /v1/teams, with the caller's role and visible channels.","required":["id","name","plan","role","channels"],"properties":{"id":{"type":"string","description":"The team id."},"name":{"type":["string","null"],"description":"The team name, or null."},"plan":{"type":"string","enum":["free","pro"],"description":"The team's plan."},"role":{"type":["string","null"],"enum":["admin","member","guest"],"description":"The caller's role in this team, or null."},"channels":{"type":"array","items":{"$ref":"#/components/schemas/TeamChannel"},"description":"Channels (spaces) visible to the caller."}},"additionalProperties":false},"TranscriptSpeaker":{"type":"object","description":"The speaker of a transcript segment.","required":["id","name"],"properties":{"id":{"type":["string","null"],"description":"The Bubbles user id when the speaker is a known user, else null."},"name":{"type":["string","null"],"description":"The diarized speaker name when available, else null."}},"additionalProperties":false},"TranscriptSegment":{"type":"object","description":"One diarized transcript entry.","required":["speaker","text","start_time_seconds","end_time_seconds"],"properties":{"speaker":{"anyOf":[{"$ref":"#/components/schemas/TranscriptSpeaker"},{"type":"null"}],"description":"The segment speaker, or null."},"text":{"type":"string","description":"The spoken text of the segment."},"start_time_seconds":{"type":"number","description":"Segment start, seconds from media start."},"end_time_seconds":{"type":"number","description":"Segment end, seconds from media start."}},"additionalProperties":false},"TranscriptJson":{"type":"object","description":"A diarized transcript (format=json, the default).","required":["bubble_id","format","segments"],"properties":{"bubble_id":{"type":"string","description":"The bubble uuid this transcript belongs to."},"format":{"type":"string","const":"json","description":"The transcript format."},"segments":{"type":"array","items":{"$ref":"#/components/schemas/TranscriptSegment"},"description":"The diarized segments in order."}},"additionalProperties":false},"TranscriptText":{"type":"object","description":"A plain-text transcript (format=text).","required":["bubble_id","format","text"],"properties":{"bubble_id":{"type":"string","description":"The bubble uuid this transcript belongs to."},"format":{"type":"string","const":"text","description":"The transcript format."},"text":{"type":"string","description":"The plain-text rendition: `Speaker: …` blocks separated by blank lines."}},"additionalProperties":false},"Transcript":{"description":"GET /v1/bubbles/{id}/transcript — json (default) or text per the format query param.","oneOf":[{"$ref":"#/components/schemas/TranscriptJson"},{"$ref":"#/components/schemas/TranscriptText"}]},"CommentAuthor":{"type":"object","description":"The author of a comment.","required":["id","name"],"properties":{"id":{"type":"string","description":"The Bubbles user id."},"name":{"type":["string","null"],"description":"Display name, or null."}},"additionalProperties":false},"Comment":{"type":"object","description":"A comment on a bubble (GET /v1/bubbles/{id}/comments).","required":["id","thread_id","bubble_id","kind","body","video_url","image_url","url_expires_at","created_at","author","is_pinned","timestamp_seconds","url"],"properties":{"id":{"type":"string","description":"The comment id."},"thread_id":{"type":"string","description":"The containing thread id."},"bubble_id":{"type":"string","description":"The bubble uuid the comment is on."},"kind":{"type":"string","enum":["text","video","image"],"description":"The comment kind."},"body":{"type":["string","null"],"description":"Text-comment body; null for video/image comments."},"video_url":{"type":["string","null"],"description":"Video-comment media URL, or null."},"image_url":{"type":["string","null"],"description":"Image-comment media URL, or null."},"url_expires_at":{"type":["string","null"],"description":"ISO-8601 instant at which video_url and image_url stop working, or null when media is served unsigned."},"created_at":{"type":"string","description":"Creation instant (ISO-8601 UTC)."},"author":{"$ref":"#/components/schemas/CommentAuthor","description":"The comment author."},"is_pinned":{"type":"boolean","description":"Whether the containing thread is pinned."},"timestamp_seconds":{"type":["number","null"],"description":"The anchored video timestamp, or null when not anchored."},"url":{"type":"string","description":"Deep link into the app opening this comment."}},"additionalProperties":false},"WebhookSubscriptionFilters":{"type":"object","description":"Optional post-match filters on a webhook subscription.","required":[],"properties":{"bubble_types":{"type":"array","items":{"type":"string"},"description":"Only deliver for these bubble types."},"only_my_bubbles":{"type":"boolean","description":"Only deliver for bubbles the subscriber created."}},"additionalProperties":false},"WebhookSubscription":{"type":"object","description":"A webhook subscription (GET /v1/webhooks[/{id}]); the signing secret is omitted.","required":["id","url","events","team_id","space_id","filters","status","created_at","last_success_at","last_failure_at","disabled_at","disabled_reason"],"properties":{"id":{"type":"string","description":"The subscription id (wh_…)."},"url":{"type":"string","description":"The receiver URL deliveries are POSTed to."},"events":{"type":"array","items":{"type":"string"},"description":"Subscribed event types (bubble.ready | meeting_recording.ready | screen_recording.ready)."},"team_id":{"type":["string","null"],"description":"Team scope, or null (all teams)."},"space_id":{"type":["string","null"],"description":"Channel scope, or null."},"filters":{"anyOf":[{"$ref":"#/components/schemas/WebhookSubscriptionFilters"},{"type":"null"}],"description":"Post-match filters, or null."},"status":{"type":"string","enum":["active","disabled"],"description":"Delivery status; auto-disabled after repeated failures."},"created_at":{"type":"string","description":"Creation instant (ISO-8601 UTC)."},"last_success_at":{"type":["string","null"],"description":"Last successful delivery instant, or null."},"last_failure_at":{"type":["string","null"],"description":"Last failed delivery instant, or null."},"disabled_at":{"type":["string","null"],"description":"When the subscription was disabled, or null."},"disabled_reason":{"type":["string","null"],"description":"Why it was disabled (e.g. delivery_failures), or null."}},"additionalProperties":false},"WebhookSubscriptionCreated":{"type":"object","description":"POST /v1/webhooks 201 response — the same shape plus the signing secret, shown exactly once.","required":["id","url","events","team_id","space_id","filters","status","created_at","last_success_at","last_failure_at","disabled_at","disabled_reason","secret"],"properties":{"id":{"type":"string","description":"The subscription id (wh_…)."},"url":{"type":"string","description":"The receiver URL deliveries are POSTed to."},"events":{"type":"array","items":{"type":"string"},"description":"Subscribed event types."},"team_id":{"type":["string","null"],"description":"Team scope, or null."},"space_id":{"type":["string","null"],"description":"Channel scope, or null."},"filters":{"anyOf":[{"$ref":"#/components/schemas/WebhookSubscriptionFilters"},{"type":"null"}],"description":"Post-match filters, or null."},"status":{"type":"string","enum":["active","disabled"],"description":"Delivery status."},"created_at":{"type":"string","description":"Creation instant (ISO-8601 UTC)."},"last_success_at":{"type":["string","null"],"description":"Last successful delivery instant, or null."},"last_failure_at":{"type":["string","null"],"description":"Last failed delivery instant, or null."},"disabled_at":{"type":["string","null"],"description":"When disabled, or null."},"disabled_reason":{"type":["string","null"],"description":"Why disabled, or null."},"secret":{"type":"string","description":"The signing secret (whsec_…) for HMAC verification — returned ONCE."}},"additionalProperties":false},"ApiKeySummary":{"type":"object","description":"A managed API key (GET /v1/api-keys); the secret is never returned.","required":["id","name","token_preview","created_at","last_used_at"],"properties":{"id":{"type":"string","description":"The key id."},"name":{"type":"string","description":"The key name."},"token_preview":{"type":"string","description":"A safe-to-show token preview (bbls_pat_ab12…)."},"created_at":{"type":"string","description":"Creation instant (ISO-8601 UTC)."},"last_used_at":{"type":["string","null"],"description":"Last-used instant, or null if never used."}},"additionalProperties":false},"ApiKeyCreated":{"type":"object","description":"POST /v1/api-keys 201 response — the full token is shown exactly once.","required":["id","name","token","token_preview","created_at"],"properties":{"id":{"type":"string","description":"The key id."},"name":{"type":"string","description":"The key name."},"token":{"type":"string","description":"The full personal access token (bbls_pat_…) — shown ONCE; store it now."},"token_preview":{"type":"string","description":"A safe-to-show token preview."},"created_at":{"type":"string","description":"Creation instant (ISO-8601 UTC)."}},"additionalProperties":false},"MeetingJoinCreated":{"type":"object","description":"POST /v1/meetings/join 201 response — the dispatched notetaker resource.","required":["id","type","status","meeting_url","team_id","created_at"],"properties":{"id":{"type":"string","description":"The join id; poll GET /v1/meetings/join/{id}."},"type":{"type":"string","const":"meeting_join","description":"The resource type."},"status":{"type":"string","description":"Initial status (dispatched)."},"meeting_url":{"type":"string","description":"The normalized meeting link the notetaker was sent to."},"team_id":{"type":"string","description":"The workspace the recording is saved into."},"created_at":{"type":"string","description":"Dispatch instant (ISO-8601 UTC)."}},"additionalProperties":false},"MeetingJoinStatus":{"type":"object","description":"GET /v1/meetings/join/{id} — a dispatched join's live status. The meeting URL/passcode is never returned.","required":["id","type","status","bot_in_call","fatal_reason","team_id","updated_at"],"properties":{"id":{"type":"string","description":"The join id."},"type":{"type":"string","const":"meeting_join","description":"The resource type."},"status":{"type":"string","enum":["dispatched","joining","in_waiting_room","in_call","recording","done","fatal"],"description":"The live join status."},"bot_in_call":{"type":"boolean","description":"Whether the notetaker is currently in the call."},"fatal_reason":{"type":["string","null"],"description":"The failure reason when status is fatal (e.g. meeting_requires_sign_in), else null."},"team_id":{"type":["string","null"],"description":"The workspace the recording is saved into, or null."},"updated_at":{"type":["string","null"],"description":"Last status update instant (ISO-8601 UTC), or null."}},"additionalProperties":false},"BubblesList":{"type":"object","description":"GET /v1/bubbles — cursor-paginated list of bubble summaries.","required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BubblesSummary"},"description":"The page of bubbles."},"has_more":{"type":"boolean","description":"Whether another page exists."},"next_cursor":{"type":["string","null"],"description":"Opaque cursor for the next page, or null when exhausted."}},"additionalProperties":false},"BubbleSearchResults":{"type":"object","description":"Search pages cover authorized matches within the first 1000 fuzzy-ranked candidates.","required":["data","has_more","next_cursor","search_window_limit"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BubblesSummary"},"description":"Authorized matches in this page."},"has_more":{"type":"boolean","description":"Whether another page exists within the search window."},"next_cursor":{"type":["string","null"],"description":"Next page within the window, or null."},"search_window_limit":{"type":"number","description":"Maximum ranked candidates examined (1000)."}},"additionalProperties":false},"CommentList":{"type":"object","description":"GET /v1/bubbles/{id}/comments — cursor-paginated list of comments.","required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Comment"},"description":"The page of comments."},"has_more":{"type":"boolean","description":"Whether another page exists."},"next_cursor":{"type":["string","null"],"description":"Opaque cursor for the next page, or null."}},"additionalProperties":false},"TeamList":{"type":"object","description":"GET /v1/teams — the finite list of the caller's teams.","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Team"},"description":"The caller's teams."}},"additionalProperties":false},"ApiKeyList":{"type":"object","description":"GET /v1/api-keys — the finite list of the caller's active keys.","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeySummary"},"description":"The caller's active API keys."}},"additionalProperties":false},"WebhookSubscriptionList":{"type":"object","description":"GET /v1/webhooks — the finite list of the caller's subscriptions.","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscription"},"description":"The caller's webhook subscriptions."}},"additionalProperties":false},"CreateApiKeyRequest":{"type":"object","description":"POST /v1/api-keys request body.","required":["name"],"properties":{"name":{"type":"string","description":"A human-readable name for the key (1–100 chars)."}},"additionalProperties":false},"CreateWebhookRequest":{"type":"object","description":"POST /v1/webhooks request body.","required":["url","events"],"properties":{"url":{"type":"string","description":"The https receiver URL to POST deliveries to."},"events":{"type":"array","items":{"type":"string"},"description":"Event types to subscribe to (at least one)."},"team_id":{"type":"string","description":"Restrict to one team."},"space_id":{"type":"string","description":"Restrict to one channel."},"filters":{"$ref":"#/components/schemas/WebhookSubscriptionFilters","description":"Optional post-match filters."}},"additionalProperties":false},"UpdateWebhookRequest":{"type":"object","description":"PATCH /v1/webhooks/{id} request body — all fields optional. status accepts \"active\" only (re-enable).","required":[],"properties":{"url":{"type":"string","description":"A new receiver URL."},"events":{"type":"array","items":{"type":"string"},"description":"A new event set."},"status":{"type":"string","enum":["active"],"description":"Set to \"active\" to re-enable an auto-disabled subscription."}},"additionalProperties":false},"JoinMeetingRequest":{"type":"object","description":"POST /v1/meetings/join request body.","required":["meeting_url","team_id"],"properties":{"meeting_url":{"type":"string","description":"A Google Meet, Microsoft Teams, Zoom, Webex, or GoTo meeting link."},"team_id":{"type":"string","description":"The workspace to save the recording into (see GET /v1/teams)."}},"additionalProperties":false},"SignupStartRequest":{"type":"object","description":"POST /v1/signup/start request body.","required":["email"],"properties":{"email":{"type":"string","description":"The work email to sign up / send the verification code to."}},"additionalProperties":false},"SignupStartResponse":{"type":"object","description":"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).","required":["next_step","agent_context","relay_to_user","signup_session"],"properties":{"next_step":{"type":"string","const":"verify","description":"The next call to make."},"agent_context":{"type":"string","description":"Agent-facing guidance for this step: what happened, what to tell the user, and what to ask/call next."},"relay_to_user":{"type":"string","description":"Plain-language text for the agent to show the user."},"signup_session":{"type":"string","description":"Opaque token to pass to the next step."}},"additionalProperties":false},"SignupVerifyRequest":{"type":"object","description":"POST /v1/signup/verify request body.","required":["signup_session","code"],"properties":{"signup_session":{"type":"string","description":"The token from POST /v1/signup/start."},"code":{"type":"string","description":"The verification code Bubbles emailed the user."}},"additionalProperties":false},"SignupVerifyResponse":{"type":"object","description":"POST /v1/signup/verify response — email verified.","required":["next_step","agent_context","relay_to_user","signup_session"],"properties":{"next_step":{"type":"string","const":"workspaces","description":"The next call to make."},"agent_context":{"type":"string","description":"Agent-facing guidance for this step: what happened, what to tell the user, and what to ask/call next."},"relay_to_user":{"type":"string","description":"Plain-language text for the agent to show the user."},"signup_session":{"type":"string","description":"Opaque token advanced to the workspace step."}},"additionalProperties":false},"SignupWorkspacesRequest":{"type":"object","description":"POST /v1/signup/workspaces request body.","required":["signup_session"],"properties":{"signup_session":{"type":"string","description":"A verified signup token."}},"additionalProperties":false},"SignupWorkspace":{"type":"object","description":"A workspace the verified email may join by domain match.","required":["id","name"],"properties":{"id":{"type":"string","description":"The workspace (team) id — pass as team_id to join."},"name":{"type":"string","description":"The workspace name."}},"additionalProperties":false},"SignupWorkspacesResponse":{"type":"object","description":"POST /v1/signup/workspaces response — the domain-matched joinable set.","required":["next_step","workspaces","agent_context","relay_to_user","signup_session"],"properties":{"next_step":{"type":"string","const":"workspace","description":"The next call to make."},"workspaces":{"type":"array","items":{"$ref":"#/components/schemas/SignupWorkspace"},"description":"Joinable workspaces (may be empty → create a new one)."},"agent_context":{"type":"string","description":"Agent-facing guidance for this step: what happened, what to tell the user, and what to ask/call next."},"relay_to_user":{"type":"string","description":"Plain-language text for the agent to show the user."},"signup_session":{"type":"string","description":"Opaque token to pass to the next step."}},"additionalProperties":false},"SignupWorkspaceRequest":{"type":"object","description":"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.","required":["signup_session","action"],"properties":{"signup_session":{"type":"string","description":"A verified signup token."},"action":{"type":"string","enum":["join","create","enable_domain_join"],"description":"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":{"type":"string","description":"For action:\"join\" — a listed, domain-matched workspace id."},"workspace_name":{"type":"string","description":"For action:\"create\" — the new workspace name."}},"additionalProperties":false},"SignupProTrial":{"type":"object","description":"The new workspace's free Pro-trial status (present only when creating).","required":["active","plan","benefits"],"properties":{"active":{"type":"boolean","description":"Whether the new workspace is on a free Pro trial."},"plan":{"type":"string","description":"The workspace plan (e.g. \"pro\")."},"benefits":{"type":"array","items":{"type":"string"},"description":"The Pro plan benefits to relay to the user."}},"additionalProperties":false},"SignupDomainJoin":{"type":"object","description":"Whether the user may open their new workspace to their verified email domain (present only when creating).","required":["available","domain"],"properties":{"available":{"type":"boolean","description":"True when the verified email domain is a non-public company domain that can be offered for auto-join."},"domain":{"type":"string","description":"The user's verified email domain."}},"additionalProperties":false},"SignupWorkspaceResponse":{"type":"object","description":"POST /v1/signup/workspace response — workspace ready.","required":["next_step","agent_context","relay_to_user","signup_session"],"properties":{"next_step":{"type":"string","const":"calendar","description":"The next call to make."},"team_id":{"type":"string","description":"The workspace the user joined or created."},"pro_trial":{"$ref":"#/components/schemas/SignupProTrial","description":"Free Pro-trial status (present when creating)."},"domain_join":{"$ref":"#/components/schemas/SignupDomainJoin","description":"Domain-join offer status (present when creating)."},"domain_join_enabled":{"type":"boolean","description":"True on an action:\"enable_domain_join\" response — the domain is now open for auto-join."},"domain":{"type":"string","description":"The domain opened for auto-join (enable_domain_join response)."},"agent_context":{"type":"string","description":"Agent-facing guidance for this step: what happened, what to tell the user, and what to ask/call next."},"relay_to_user":{"type":"string","description":"Plain-language text for the agent to show the user."},"signup_session":{"type":"string","description":"Opaque token advanced to the calendar step."}},"additionalProperties":false},"SignupCalendarRequest":{"type":"object","description":"POST /v1/signup/calendar request body.","required":["signup_session"],"properties":{"signup_session":{"type":"string","description":"A verified signup token."}},"additionalProperties":false},"SignupMcpTool":{"type":"object","description":"One tool the connected MCP server exposes.","required":["name","title","kind","summary"],"properties":{"name":{"type":"string","description":"The tool name (e.g. \"search_bubbles\")."},"title":{"type":"string","description":"The human-readable tool title."},"kind":{"type":"string","enum":["read","write"],"description":"Whether the tool only reads data or performs a write action."},"summary":{"type":"string","description":"A one-line summary of what the tool does."}},"additionalProperties":false},"SignupMcpConnect":{"type":"object","description":"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.","required":["endpoint","oauth_protected_resource","scopes","how_to_connect","tools"],"properties":{"endpoint":{"type":"string","description":"The Bubbles MCP server URL to add to an MCP client."},"oauth_protected_resource":{"type":"string","description":"The RFC 9728 protected-resource metadata URL an MCP client discovers the authorization server from."},"scopes":{"type":"array","items":{"type":"string"},"description":"The OAuth scopes the connected client may be granted."},"how_to_connect":{"type":"string","description":"Provider-agnostic, step-by-step connect instructions (add the URL, one-time browser consent) for the agent to relay."},"tools":{"type":"array","items":{"$ref":"#/components/schemas/SignupMcpTool"},"description":"The tools the connected MCP server exposes."}},"additionalProperties":false},"SignupCalendarResponse":{"type":"object","description":"POST /v1/signup/calendar response — calendar connect links + MCP connect + skip.","required":["next_step","why","google_connect_url","microsoft_connect_url","skip","mcp","agent_context","relay_to_user","signup_session"],"properties":{"next_step":{"type":"string","const":"done","description":"Signup is complete after this step."},"why":{"type":"array","items":{"type":"string"},"description":"The benefits of connecting a work calendar."},"google_connect_url":{"type":"string","description":"Link for the user to connect Google Calendar."},"microsoft_connect_url":{"type":"string","description":"Link for the user to connect Microsoft/Outlook Calendar."},"skip":{"type":"boolean","description":"Always true — the user may skip connecting a calendar."},"mcp":{"$ref":"#/components/schemas/SignupMcpConnect","description":"How to connect an AI assistant to Bubbles over MCP + the connected tools (owner directive 5885769d)."},"agent_context":{"type":"string","description":"Agent-facing guidance for this step: what happened, what to tell the user, and what to ask/call next."},"relay_to_user":{"type":"string","description":"Plain-language text for the agent to show the user."},"signup_session":{"type":"string","description":"The signup token (signup is complete)."}},"additionalProperties":false}}}}