{"openapi":"3.1.0","info":{"title":"Bird API","version":"1.0.0","description":"Build communications into your applications and AI workflows with the Bird API.\nExplore endpoints for email, SMS, WhatsApp, verification, and realtime messaging.\n\nStart with the [developer documentation](https://bird.com/docs), browse the\n[API reference](https://bird.com/docs/api), or connect your agent through the\n[Bird MCP server](https://bird.com/docs/ai/mcp-server).\n\nSetting a `User-Agent` header is recommended but not required. Official Bird\nSDKs set it automatically (format: `bird-<language>/<version>`). The header is\nused for operational logging and customer-support diagnostics; the API never\nrejects requests that omit it.\n\nRate limits are advertised on every response from a rate-limited endpoint as\nIETF `RateLimit` and `RateLimit-Policy` headers (Structured Fields per RFC\n9651; spec draft-ietf-httpapi-ratelimit-headers-11). On 429 responses, a\n`Retry-After` header in seconds is also returned. Clients should pace\nthemselves against the `r` (remaining) and `t` (seconds until reset) params\nin the `RateLimit` header rather than only reacting to 429s.\n\n"},"servers":[{"url":"https://{region}.platform.bird.com","description":"Regional API endpoint. Use the host for the region your organization is hosted in. Official Bird SDKs and the CLI select it automatically from your API key, so you rarely need to set it by hand.\n","variables":{"region":{"default":"us1","enum":["us1","eu1"],"description":"The region your organization's data is hosted in."}}},{"url":"https://platform.bird.com","description":"Region-independent endpoint for authentication and account administration."},{"url":"http://localhost:8080","description":"Local development."}],"security":[{"BearerAuth":[]}],"tags":[{"name":"workspaces","description":"Workspace management."},{"name":"realtime-apps","description":"Realtime app management."},{"name":"realtime-events","description":"Publish events to a Realtime app's channels from your server (the data plane). Sending to several channels at once broadcasts to all of them.\n"},{"name":"email-messages","description":"Send emails to recipients you address explicitly in `to`, `cc`, and `bcc`. Use this for transactional sends (receipts, password resets, alerts) and for marketing sends where you already have the recipient addresses on hand. The same endpoint accepts every content type. Set `category` to control suppression policy.\n"},{"name":"email-broadcasts","description":"Send one email to a stored audience. Create a broadcast as a draft, then send it immediately or schedule it for later; scheduled and in-progress broadcasts can be canceled. The audience's contacts at send time become the recipient set after suppressions are applied. The recipients endpoint returns each recipient's delivery state.\n"},{"name":"email-contacts","description":"Contacts are the people you send broadcasts to. Each contact is unique by email address within a workspace and carries optional name fields and custom properties for personalization. Custom properties are defined once per workspace via the contact properties API and then set per contact.\n"},{"name":"email-audiences","description":"Audiences are the recipient lists broadcasts are sent to. An audience holds a set of contacts that you manage through the API. The contacts in the audience at send time become the broadcast's recipients after suppressions are applied. A contact can belong to multiple audiences.\n"},{"name":"email-stats","description":"Email analytics, including daily and hourly delivery statistics, tag breakdowns, and a KPI summary."},{"name":"sms-messages","description":"Send SMS messages to recipients you address by phone number, and read their delivery status and lifecycle events. Each message is one recipient and one body; set `category` to control opt-out policy and per-country compliance.\n"},{"name":"sms-templates","description":"Create and publish multilingual workspace SMS templates and browse built-in templates. Published workspace versions preserve their language content; built-in versions reflect the current catalogue."},{"name":"sms-stats","description":"SMS analytics, including daily and hourly lifecycle counts, dimension breakdowns, and a KPI summary."},{"name":"verify-verifications","description":"Send a one-time passcode to a recipient and check the code they enter. Create a verification to send a passcode over email or SMS, then submit the recipient's code to verify it.\n"},{"name":"whatsapp-messages","description":"Send WhatsApp messages, whether a template, free-form content, or interactive content the recipient can tap, and read the messages your workspace sent and received, including their current delivery status and lifecycle events.\n"},{"name":"whatsapp-templates","description":"Browse the WhatsApp message templates available to your workspace, approved by Meta and ready to send.\n"},{"name":"whatsapp-stats","description":"WhatsApp analytics, including daily and hourly delivery statistics and a KPI summary."},{"name":"domains","description":"Sending domain management and DNS verification."},{"name":"email-suppressions","description":"Email suppression list management."},{"name":"email-templates","description":"Reusable email templates and their versions, with stored subject, HTML, and plain-text content you manage and reference when sending.\n"},{"name":"email-inbound-addresses","description":"Addresses generated for receiving mail. Forward a mailbox to an inbound address to parse each message into a received email.\n"},{"name":"email-inbound-messages","description":"Emails received on your behalf, including each parsed message, its body, raw MIME content, and attachments.\n"},{"name":"email-mailboxes","description":"Durable mailbox identities for agents. A mailbox owns an address, applies receive policy through allow/block rules, and remembers conversations for its retention tier.\n"},{"name":"email-inbound-routes","description":"Routing rules that direct inbound mail on your domains into mailboxes, or drop it, in priority order.\n"},{"name":"email-threads","description":"Conversations in a mailbox. Threads group related inbound and outbound messages and carry read state, labels, and participants.\n"},{"name":"email-smtp-configs","description":"Default IP pool, category, tags, and open and click tracking settings for messages submitted over SMTP with a given API key.\n"},{"name":"webhooks","description":"Webhook endpoint management."},{"name":"numbers","description":"Phone number catalog and purchasing."},{"name":"docs","description":"Search our developer documentation."},{"name":"voice-calls","description":"Call records (CDR) for the workspace, in flight and completed."},{"name":"whatsapp-numbers","description":"Connect phone numbers from Meta's embedded signup flow and check their WhatsApp setup status.\n"},{"name":"whatsapp-business-accounts","description":"Read the WhatsApp Business Accounts your workspace has connected, so a template can be created on the account you choose.\n"},{"name":"sms-keyword-rules","description":"Manage the response when someone sends a keyword to one of your numbers. Each supported country starts with opt-out, opt-in, and help keywords. Create a rule to replace a default response or add campaign keywords.\n"},{"name":"lookup","description":"Inspect a recipient before sending. Phone-number lookups return carrier, portability, number type, reachability, roaming, SIM-change, and fraud-risk data when requested. Email lookups return deliverability, confidence, failure reasons, and suggested corrections for likely misspellings.\n"},{"name":"sms-suppressions","description":"Sender and subscriber pairs that block SMS delivery."},{"name":"email-competitive","description":"Watch competitor brands and see how their email compares with yours. Figures about a competitor are estimates from an email panel, which observes a sample of real inboxes; figures about your own sending are counted rather than estimated wherever that is possible.\n"},{"name":"preferences","description":"Stated messaging preferences (consent grants and opt-outs) recorded per handle across email, SMS, and WhatsApp, with causally ordered writes."},{"name":"email-inbox-insights","description":"Inbox placement, seed tests, and sending reputation for the workspace's\nown sending domains, measured from a panel of real mailboxes.\n\nRates here are percentages carrying a `_percent` suffix (`87.4`).\nCompetitive Insights reports the same kind of figure as a fraction\n(`0.874`), so a client reading both products scales one of them.\n"},{"name":"whatsapp-keyword-rules","description":"What happens when someone replies STOP or START to a WhatsApp message: the keywords Bird ships, the keywords you add, and the replies you send back.\n"}],"paths":{"/v1/docs/search":{"get":{"operationId":"getDocsSearch","summary":"Get documentation search results","description":"Searches the documentation and returns matching sections, best match first. Each result links to its section heading and includes the page's `slug`; read the full page with `GET /v1/docs/pages`.\n\nNo authentication is required. A blank query returns `400`, and `503` means search is temporarily unavailable.\n","tags":["docs"],"security":[],"x-audiences":["public","command"],"parameters":[{"name":"q","in":"query","required":true,"description":"The text to search the documentation for. Must not be blank.","schema":{"type":"string","minLength":1}},{"name":"locale","in":"query","required":false,"description":"Documentation locale to search, as a language-region code such as `en-us`. Defaults to `en-us` when omitted or unavailable.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of results to return (1–25).","schema":{"type":"integer","minimum":1,"maximum":25,"default":10}},{"name":"contents","in":"query","required":false,"description":"How much of each matching section to return. `snippet` (the default) returns a short preview; `highlights` additionally returns the passages that match the query. For the full text, fetch a result's `markdown_url`.","schema":{"type":"string","enum":["snippet","highlights"],"default":"snippet"}}],"responses":{"200":{"description":"Ranked search results.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocsSearchResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n"]}},"/v1/docs/pages":{"get":{"operationId":"getDocsPage","summary":"Get a documentation page","description":"Returns the full Markdown content of a documentation page. Get its `slug` from `GET /v1/docs/search`, or pass `index` for the documentation landing page.\n\nNo authentication is required. An unknown slug returns `404`, and `503` means the documentation backend is temporarily unavailable.\n","tags":["docs"],"security":[],"x-audiences":["public","command"],"parameters":[{"name":"slug","in":"query","required":true,"description":"Slug of the page to read, as returned in the `slug` field of a search result (for example `guides/email/contacts`). Use `index` for the documentation landing page.","schema":{"type":"string","minLength":1}},{"name":"locale","in":"query","required":false,"description":"Documentation locale to read, as a language-region code such as `en-us`. Defaults to `en-us` when omitted or unavailable.","schema":{"type":"string"}}],"responses":{"200":{"description":"The documentation page content.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocsPage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n"]}},"/v1/workspace":{"get":{"operationId":"getCurrentWorkspace","summary":"Get the current workspace","description":"Returns the workspace the current credentials are scoped to: its ID, name, the ID of the organization that owns it, and its notification and logo settings. The organization's own name and members are not included. Requires at least read access to the workspace.\n","tags":["workspaces"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"workspace.get","responses":{"200":{"description":"The current workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/realtime/regions":{"get":{"operationId":"listRealtimeRegions","x-snippet-key":"none","summary":"List Realtime regions","description":"Returns the regions a Realtime app can be created in. Use one of these identifiers as the `region` when creating an app.","tags":["realtime-apps"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"}],"responses":{"200":{"description":"The available Realtime regions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeRegionList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","mcp","n8n"]}},"/v1/realtime/apps":{"post":{"operationId":"createRealtimeApp","x-snippet-key":"none","summary":"Create a Realtime app","description":"Provisions a new Realtime app for the workspace and returns it with the initial key. Store the key secret when you receive it because later responses do not include it. If you lose the secret, create a new key and revoke this one.","tags":["realtime-apps"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeAppCreate"}}}},"responses":{"201":{"description":"Realtime app created. Includes the initial key's one-time secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeAppCreated"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n"]},"get":{"operationId":"listRealtimeApps","x-snippet-key":"none","summary":"List Realtime apps","description":"Returns the workspace's Realtime apps as a paginated list, filterable by a case-insensitive `name` substring. Each entry carries the app's configuration and connection details (`app_id`, `region`) but never key secrets. Use [List a Realtime app's keys](/docs/api/reference/list-realtime-app-keys) and [Create a Realtime app key](/docs/api/reference/create-realtime-app-key) to manage them.\n","tags":["realtime-apps"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"name","in":"query","required":false,"description":"Substring match against the app name (case-insensitive).","schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"description":"Field to sort by.","schema":{"$ref":"#/components/schemas/RealtimeAppSortField"}},{"$ref":"#/components/parameters/OrderDesc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"$ref":"#/components/parameters/IncludeTotal"}],"responses":{"200":{"description":"Paginated list of Realtime apps.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeAppList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","mcp","n8n"]}},"/v1/realtime/apps/{realtime_app_id}":{"parameters":[{"name":"realtime_app_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeAppID"},"description":"ID of the Realtime app (`rap_` prefix), as returned when the app was created."}],"get":{"operationId":"getRealtimeApp","x-snippet-key":"none","summary":"Get a Realtime app","description":"Returns a single Realtime app with its configuration and connection details (`app_id`, `region`). Key secrets are never included; use [List a Realtime app's keys](/docs/api/reference/list-realtime-app-keys) to manage them. Returns a `404 Not Found` error if the app does not exist in the workspace.\n","tags":["realtime-apps"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"}],"responses":{"200":{"description":"Realtime app with its current configuration and status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeApp"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","mcp","n8n"]},"patch":{"operationId":"updateRealtimeApp","x-snippet-key":"none","summary":"Update a Realtime app","description":"Updates a Realtime app's name and configuration flags. Region is immutable; TLS is always enforced. Omitted fields are left unchanged.","tags":["realtime-apps"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeAppUpdate"}}}},"responses":{"200":{"description":"The updated Realtime app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeApp"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n"]},"delete":{"operationId":"deleteRealtimeApp","x-snippet-key":"none","summary":"Delete a Realtime app","description":"Permanently deletes the app: disconnects all clients and removes its keys and configuration. This cannot be undone.","tags":["realtime-apps"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"The app was deleted."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n"]}},"/v1/realtime/apps/{realtime_app_id}/events":{"parameters":[{"name":"realtime_app_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeAppID"},"description":"ID of the Realtime app (`rap_` prefix), as returned when the app was created."}],"post":{"operationId":"publishRealtimeAppEvent","x-snippet-key":"realtime.publish","summary":"Publish a Realtime event","description":"Publishes an event to one or more channels of a Realtime app. Listing several channels broadcasts the event to all of them in one call. Connected clients subscribed to those channels receive it in real time.","tags":["realtime-events"],"security":[{"BearerAuth":[],"RealtimeKey":[],"RealtimeSecret":[]},{"CookieAuth":[],"RealtimeKey":[],"RealtimeSecret":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimePublish"},"examples":{"onboarding-realtime":{"summary":"The first publish from the dashboard's onboarding step","value":{"event":"order-updated","channels":["orders"],"data":{"id":42,"status":"shipped"}}}}}}},"responses":{"200":{"description":"The event was accepted for delivery.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimePublishResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","sdk"]}},"/v1/realtime/apps/{realtime_app_id}/batch-events":{"parameters":[{"name":"realtime_app_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeAppID"},"description":"ID of the Realtime app (`rap_` prefix), as returned when the app was created."}],"post":{"operationId":"publishRealtimeAppBatch","x-snippet-key":"realtime.publishBatch","summary":"Publish a batch of Realtime events","description":"Publishes up to 10 events (each to one channel) in a single request.","tags":["realtime-events"],"security":[{"BearerAuth":[],"RealtimeKey":[],"RealtimeSecret":[]},{"CookieAuth":[],"RealtimeKey":[],"RealtimeSecret":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeBatchPublish"}}}},"responses":{"200":{"description":"The events were accepted for delivery.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeBatchPublishResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","sdk"]}},"/v1/realtime/apps/{realtime_app_id}/channels":{"parameters":[{"name":"realtime_app_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeAppID"},"description":"ID of the Realtime app (`rap_` prefix), as returned when the app was created."}],"get":{"operationId":"listRealtimeAppChannels","x-snippet-key":"realtime.channels.list","summary":"List Realtime channels","description":"Lists the app's currently occupied channels, optionally filtered by name prefix.","tags":["realtime-events"],"security":[{"BearerAuth":[],"RealtimeKey":[],"RealtimeSecret":[]},{"CookieAuth":[],"RealtimeKey":[],"RealtimeSecret":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"prefix","in":"query","required":false,"description":"Only channels whose name starts with this prefix (for example, `presence-`).","schema":{"type":"string"}},{"name":"include","in":"query","required":false,"description":"Per-channel attributes to include. Repeatable. Requesting `member_count` without a presence-channel `prefix`, or `connection_count` when the app's connection-counting flag is off, returns a validation error (400).","schema":{"type":"array","items":{"$ref":"#/components/schemas/RealtimeChannelInclude"}}}],"responses":{"200":{"description":"The occupied channels.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeChannelsList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["make","sdk"]}},"/v1/realtime/apps/{realtime_app_id}/channels/{channel_name}":{"parameters":[{"name":"realtime_app_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeAppID"},"description":"ID of the Realtime app (`rap_` prefix), as returned when the app was created."},{"name":"channel_name","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeChannelName"},"description":"Name of the Realtime channel to retrieve."}],"get":{"operationId":"getRealtimeAppChannel","x-snippet-key":"realtime.channels.get","summary":"Get a Realtime channel","description":"Returns a single channel's occupancy and optional counts. A channel appears when its first connection subscribes and disappears when its last connection leaves. An unknown or unused name returns `200 OK` with `occupied: false`.","tags":["realtime-events"],"security":[{"BearerAuth":[],"RealtimeKey":[],"RealtimeSecret":[]},{"CookieAuth":[],"RealtimeKey":[],"RealtimeSecret":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"include","in":"query","required":false,"description":"Attributes to include. Repeatable. Requesting `member_count` for a non-presence channel, or `connection_count` when the app's connection-counting flag is off, returns a validation error (400).","schema":{"type":"array","items":{"$ref":"#/components/schemas/RealtimeChannelInclude"}}}],"responses":{"200":{"description":"The channel state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeChannelInfo"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["make","sdk"]}},"/v1/realtime/apps/{realtime_app_id}/channels/{channel_name}/members":{"parameters":[{"name":"realtime_app_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeAppID"},"description":"ID of the Realtime app (`rap_` prefix), as returned when the app was created."},{"name":"channel_name","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeChannelName"},"description":"Name of the presence channel whose members to list."}],"get":{"operationId":"listRealtimeAppChannelMembers","x-snippet-key":"realtime.channels.members","summary":"List members on a presence channel","description":"Lists the member IDs currently subscribed to a presence channel. IDs only: `member_info` (the profile data attached by your authorization endpoint) is delivered to subscribed clients over the realtime connection and is not available over REST.","tags":["realtime-events"],"security":[{"BearerAuth":[],"RealtimeKey":[],"RealtimeSecret":[]},{"CookieAuth":[],"RealtimeKey":[],"RealtimeSecret":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"}],"responses":{"200":{"description":"The members on the presence channel.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeChannelMembers"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["make","sdk"]}},"/v1/realtime/apps/{realtime_app_id}/members/{member_id}/disconnect":{"parameters":[{"name":"realtime_app_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeAppID"},"description":"ID of the Realtime app (`rap_` prefix), as returned when the app was created."},{"name":"member_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeMemberID"},"description":"Member ID whose connections to disconnect."}],"post":{"operationId":"disconnectRealtimeAppMember","x-snippet-key":"realtime.members.disconnect","summary":"Disconnect a member","description":"Disconnects all of a member's active connections, for example on sign-out or ban.","tags":["realtime-events"],"security":[{"BearerAuth":[],"RealtimeKey":[],"RealtimeSecret":[]},{"CookieAuth":[],"RealtimeKey":[],"RealtimeSecret":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"The member's connections were disconnected."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","sdk"]}},"/v1/realtime/apps/{realtime_app_id}/members/{member_id}/events":{"parameters":[{"name":"realtime_app_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeAppID"},"description":"ID of the Realtime app (`rap_` prefix), as returned when the app was created."},{"name":"member_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeMemberID"},"description":"The member to deliver the event to."}],"post":{"operationId":"sendRealtimeAppMemberEvent","x-snippet-key":"realtime.members.send","summary":"Send an event to a member","description":"Delivers an event to one member of a Realtime app, addressing the person\nrather than a channel. Every connection that member currently holds receives\nit across tabs and devices, without requiring a dedicated channel.\n\nThe member must have signed in on the connection for it to be addressable.\nDelivery is best-effort and not queued. A member with no active connections\nat the time of the call does not receive the event.","tags":["realtime-events"],"security":[{"BearerAuth":[],"RealtimeKey":[],"RealtimeSecret":[]},{"CookieAuth":[],"RealtimeKey":[],"RealtimeSecret":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeMemberPublish"}}}},"responses":{"204":{"description":"The event was accepted for delivery. No body: a member has no channel occupancy or counts to report, and whether they hold connections is not something a publish can confirm."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","sdk"]}},"/v1/realtime/apps/{realtime_app_id}/keys":{"parameters":[{"name":"realtime_app_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeAppID"},"description":"ID of the Realtime app (`rap_` prefix), as returned when the app was created."}],"get":{"operationId":"listRealtimeAppKeys","x-snippet-key":"none","summary":"List a Realtime app's keys","description":"Returns the app's keys, oldest first. Non-revoked only by default; pass include_revoked=true to include revoked keys. An app can hold several keys at once (create a new key, roll it out, then revoke the old one for zero-downtime rotation). Secrets are never included in this response.","tags":["realtime-apps"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"include_revoked","in":"query","required":false,"schema":{"type":"boolean","default":false},"description":"When true, include revoked keys in the response."}],"responses":{"200":{"description":"The app's keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeAppKeyList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","mcp","n8n"]},"post":{"operationId":"createRealtimeAppKey","x-snippet-key":"none","summary":"Create a Realtime app key","description":"Adds a new key to the app and returns it with its secret. Store the secret when you receive it because later responses do not include it. Use this together with revoke for zero-downtime rotation: add a key, roll it out across your clients, then revoke the old key.","tags":["realtime-apps"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"201":{"description":"Key created. Includes the one-time secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeAppKey"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n"]}},"/v1/realtime/apps/{realtime_app_id}/keys/{realtime_app_key_id}/revoke":{"parameters":[{"name":"realtime_app_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeAppID"},"description":"ID of the Realtime app (`rap_` prefix), as returned when the app was created."},{"name":"realtime_app_key_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/RealtimeAppKeyID"},"description":"ID of the Realtime app key (`rak_` prefix), as returned when the key was created."}],"post":{"operationId":"revokeRealtimeAppKey","x-snippet-key":"none","summary":"Revoke a Realtime app key","description":"Revokes a key immediately and returns it with `revoked_at` set. Clients that still use the key can no longer authenticate. An already revoked key returns `409`. The app's only key cannot be revoked and returns `422`.","tags":["realtime-apps"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Key revoked. Returns the updated key with `revoked_at` set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimeAppKey"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n"]}},"/v1/email/messages":{"post":{"operationId":"createEmailMessage","summary":"Create an email message","description":"Sends an email to the recipients you list explicitly in `to`/`cc`/`bcc`. Use it for\ntransactional sends (receipts, password resets, alerts) and for marketing sends where\nyou have the recipient addresses on hand. To submit many independent messages in one\nrequest, use [Create a batch of email messages](/docs/api/reference/create-email-message-batch)\ninstead. The `category` field controls suppression policy independently of content:\nset it to `marketing` when sending marketing content.\n\nThe `202` response means the message is safely accepted and awaiting delivery.\nFetch it by `id` or subscribe to webhook events to follow delivery. The\nrequest never half-succeeds: an unverified sender domain or any field-level\nvalidation failure rejects it immediately with a `422` naming the reason.\nSuppression is evaluated per recipient after acceptance, so a suppressed recipient\nappears as `rejected` on the message's recipient list rather than as a synchronous\nerror. New workspaces can send from the shared onboarding domain before verifying\ntheir own. The [quickstart](/docs/get-started/send-your-first-email) covers its\nrecipient and volume limits.\n","tags":["email-messages"],"x-audiences":["public","command"],"x-snippet-key":"email.send","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailMessageSendRequest"},"examples":{"quickstart-first-email":{"summary":"The smallest send that works, from the getting-started guide","value":{"from":{"email":"onboarding@messagebird.dev","name":"Bird"},"to":[{"email":"delivered@messagebird.dev"}],"subject":"Hello from Bird","html":"<p>My first Bird email.</p>"}},"send-minimal":{"summary":"A minimal send: one sender, one recipient, one body","value":{"from":{"email":"hello@yourdomain.com"},"to":[{"email":"delivered@messagebird.dev"}],"subject":"Hello from Bird","html":"<p>It works.</p>"}},"send-template":{"summary":"A send that renders a stored template with parameters","value":{"from":{"email":"hello@yourdomain.com"},"to":[{"email":"delivered@messagebird.dev"}],"category":"transactional","template":{"slug":"welcome-email","parameters":{"first_name":"Jane"}}}},"send-from-ip-pool":{"summary":"A send routed through a named dedicated IP pool","value":{"from":{"email":"noreply@yourdomain.com"},"to":[{"email":"delivered@messagebird.dev"}],"subject":"Your receipt","html":"<p>Thanks for your order.</p>","ip_pool_id":"ipp_1btmn1nnkd8y6a4jckbkvvt9eh"}},"send-sandbox-bounce":{"summary":"A send to the sandbox address that always hard-bounces","value":{"from":{"email":"onboarding@messagebird.dev"},"to":[{"email":"bounce+signup-flow@messagebird.dev"}],"subject":"Sandbox bounce test","html":"<p>This message will hard-bounce.</p>","tags":[{"name":"flow","value":"signup"}],"metadata":{"test_run":"docs-capture-1"}}},"quickstart":{"summary":"A first send on a new workspace","value":{"from":{"email":"onboarding@messagebird.dev","name":"Bird"},"to":["delivered@messagebird.dev"],"subject":"Welcome to Bird","html":"<p>You are in.</p>"}},"onboarding-email":{"summary":"The first send from the dashboard's onboarding step","value":{"from":{"email":"onboarding@messagebird.dev","name":"Bird"},"to":["delivered@messagebird.dev"],"subject":"Hello World","html":"<p>You made your <strong>first email fly</strong>. Congratulations!</p>"}}}}}},"responses":{"202":{"description":"Message accepted for asynchronous delivery.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailMessage"},"example":{"id":"em_01krdgeqcxet5s7t44vh8rt9mg","from":{"email":"onboarding@messagebird.dev","name":"Bird"},"to":[{"email":"delivered@messagebird.dev"}],"subject":"Hello from Bird","category":"transactional","status":"accepted","accepted_count":1,"processed_count":0,"delivered_count":0,"bounced_count":0,"complained_count":0,"deferred_count":0,"rejected_count":0,"open_count":0,"click_count":0,"track_opens":false,"track_clicks":false,"created_at":"2026-07-01T12:00:00Z"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]},"get":{"operationId":"listEmailMessages","summary":"List messages","description":"Returns the workspace's sent and scheduled messages, newest first, as a cursor page. Each item has the aggregate delivery `status` and per-state recipient counts. Message bodies are omitted.\n\nCombine filters to narrow the page:\n\n- Delivery status.\n- Category.\n- Tag.\n- An exact `to` or `from` address.\n- A `created_after` or `created_before` time window.\n","tags":["email-messages"],"x-audiences":["public","command"],"x-snippet-key":"email.list.iterate","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"$ref":"#/components/parameters/CreatedAfter"},{"$ref":"#/components/parameters/CreatedBefore"},{"name":"status","in":"query","required":false,"description":"Filter by aggregate delivery status.","schema":{"$ref":"#/components/schemas/EmailMessageStatus"}},{"$ref":"#/components/parameters/TagFilter"},{"name":"category","in":"query","required":false,"description":"Filter by category.","schema":{"$ref":"#/components/schemas/EmailMessageCategory"}},{"name":"to","in":"query","required":false,"description":"Filter by recipient address. Exact match against any `to`/`cc`/`bcc` recipient on the message. The address is normalized to lowercase before comparison.\n","schema":{"type":"string","format":"email","example":"delivered@messagebird.dev"}},{"name":"from","in":"query","required":false,"description":"Filter by sender address. Exact match against the message `from` field. The address is normalized to lowercase before comparison.\n","schema":{"type":"string","format":"email","example":"noreply@acme.com"}}],"responses":{"200":{"description":"Paginated list of messages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailMessageList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/email/batches":{"post":{"operationId":"createEmailMessageBatch","summary":"Create a batch of email messages","description":"Accepts up to 100 independent email messages and queues them for delivery. All items are validated before any are queued: if one fails validation, the entire batch is rejected. Field-level validation failures and business-rule failures, such as sending from a domain that is not verified, both return `422`. An item can set `scheduled_at` to send it later, on the same terms as a single scheduled send, and one batch can mix scheduled and immediate messages. Cancel a scheduled item before it sends with [Cancel an email message](/docs/api/reference/cancel-email-message). Suppression is evaluated per recipient after acceptance, never as a synchronous error. The `202` response returns one entry per message in submission order, each with its own `id` you can use to fetch that message or match it against webhook events. Attachments are allowed per message. Each message must stay within the 20 MB estimated generated message-size cap, and the serialized JSON request body for the whole batch has a hard 20 MB cap.\n","tags":["email-messages"],"x-audiences":["public","command"],"x-snippet-key":"email.sendBatch","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailMessageBatchRequest"}}}},"responses":{"202":{"description":"Batch accepted for asynchronous delivery.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailMessageBatchResponse"},"example":{"data":[{"id":"em_01krdgeqcxet5s7t44vh8rt9mg","status":"accepted","category":"marketing","requested_language":"pt-BR","resolved_language":"pt-BR","template_id":"emt_01krdgeqcxet5s7t44vh8rt9mg","template_version_id":"emv_01krdgeqcxet5s7t44vh8rt9mg"},{"id":"em_01krdgeqcxet5s7t44vh8rt9mh","status":"accepted","category":"transactional","scheduled_at":"2026-05-22T09:00:00Z"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/messages/{message_id}":{"get":{"operationId":"getEmailMessage","summary":"Get a message","description":"Returns a single message with its aggregate delivery `status` and per-state recipient counts. The response never includes the `html`/`text` bodies. When content storage is enabled for the send, fetch the stored bodies with [Get stored message content](/docs/api/reference/get-email-message-content). Per-recipient statuses and the event timeline are separate sub-resources.\n","tags":["email-messages"],"x-audiences":["public","command"],"x-snippet-key":"email.get","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the message to fetch. A broadcast records one message per recipient, and [List email messages](/docs/api/reference/list-email-messages) returns those copies alongside ordinary sends. For a single send, this is the message's own ID, from the send response's `id` field.","schema":{"$ref":"#/components/schemas/EmailID"}}],"responses":{"200":{"description":"Message object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailMessage"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/messages/{message_id}/cancel":{"post":{"operationId":"cancelEmailMessage","summary":"Cancel a scheduled message","description":"Cancels a message that was scheduled with `scheduled_at` before it sends. Only a message that is still scheduled can be canceled. A message that already started sending, was delivered, or was previously canceled returns a conflict error. The message's status becomes `canceled` and an `email.canceled` webhook event fires. Canceling does not return consumed scheduled-send quota.\n","tags":["email-messages"],"x-audiences":["public","command"],"x-snippet-key":"email.cancel","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the scheduled message to cancel, from the send response's `id` field.","schema":{"$ref":"#/components/schemas/EmailID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"The message is canceled and no longer eligible to send."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/messages/{message_id}/recipients":{"get":{"operationId":"listEmailMessageRecipients","summary":"List recipients of a message","description":"Returns the message's per-recipient delivery state as a cursor page: each entry is\none `to`/`cc`/`bcc` recipient with its role, current `status`, rejection or bounce\ndetail when delivery failed, and open/click counts. Use it to see which specific\naddresses failed when the aggregate message `status` is mixed (for example\n`partial_failure`).\n\nThis works for every email message, including one a broadcast sent. A broadcast\nrecords one message per recipient, so such a message has exactly one recipient\nhere: the address its copy went to.\n","tags":["email-messages"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the message whose recipients to list. A broadcast records one message per recipient, and [List email messages](/docs/api/reference/list-email-messages) returns those copies alongside ordinary sends. For a single send, this is the message's own ID, from the send response's `id` field.","schema":{"$ref":"#/components/schemas/EmailID"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of recipients for this message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailRecipientList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n"]}},"/v1/email/messages/{message_id}/events":{"get":{"operationId":"listEmailMessageEvents","summary":"List events for a message","description":"Returns the message's per-recipient event timeline, oldest first, as a cursor page. Lifecycle, failure, and engagement events interleave as they happen, and engagement events (`email.opened`, `email.clicked`) can repeat per recipient. Filter to one event type with `type`. For each recipient's current state rather than its history, use [List recipients of a message](/docs/api/reference/list-email-message-recipients).\n","tags":["email-messages"],"x-audiences":["public"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the message whose timeline to read. A broadcast records one message per recipient, and [List email messages](/docs/api/reference/list-email-messages) returns those copies alongside ordinary sends. For a single send, this is the message's own ID, from the send response's `id` field.","schema":{"$ref":"#/components/schemas/EmailID"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"name":"type","in":"query","required":false,"description":"Filter by event type, for example `email.bounced` or `email.opened`.","schema":{"$ref":"#/components/schemas/EmailEventType"}}],"responses":{"200":{"description":"Paginated event timeline for this message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailEventList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["make","n8n"]}},"/v1/email/messages/{message_id}/content":{"get":{"operationId":"getEmailMessageContent","summary":"Get stored message content","description":"Returns the stored HTML and text bodies for a sent message. Content storage must be enabled for the message. Content is available for up to 30 days after sending. A broadcast stores no body, so a copy the send has recorded always answers `404`, whatever the workspace has configured. A `404` indicates no content was stored for this message. A `425` indicates the content is still being stored and the request can be retried shortly. A `410` indicates the content was stored but has since expired.\n","tags":["email-messages"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the message whose stored content to fetch. A broadcast records one message per recipient, and [List email messages](/docs/api/reference/list-email-messages) returns those copies alongside ordinary sends. For a single send, this is the message's own ID, from the send response's `id` field.","schema":{"$ref":"#/components/schemas/EmailID"}}],"responses":{"200":{"description":"Stored message content.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailMessageContent"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"425":{"$ref":"#/components/responses/TooEarly"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n"]}},"/v1/email/messages/{message_id}/attachments/{attachment_id}":{"get":{"operationId":"getEmailMessageAttachment","summary":"Get a message attachment","description":"Downloads the raw bytes of one attachment from a sent message, returned with the attachment's own content type and a Content-Disposition header that includes its filename. The message must have content storage enabled and the attachment is available for up to 30 days after sending. A `404` indicates the message has no stored content or no attachment with this ID. A `425` indicates the attachment is still being stored and the request can be retried shortly. A `410` indicates the attachment was stored but has since expired.\n","tags":["email-messages"],"x-audiences":["public"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the message the attachment belongs to, from the send response's `id` field.","schema":{"$ref":"#/components/schemas/EmailID"}},{"name":"attachment_id","in":"path","required":true,"description":"Attachment ID, as returned in the message's `attachments` list.","schema":{"$ref":"#/components/schemas/EmailAttachmentID"}}],"responses":{"200":{"description":"The raw attachment bytes.","headers":{"Content-Disposition":{"description":"Has the attachment's filename. The value is `attachment` for regular files, or `inline` for inline images referenced from the HTML body.\n","schema":{"type":"string"}}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"425":{"$ref":"#/components/responses/TooEarly"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make"]}},"/v1/email/broadcasts":{"post":{"operationId":"createEmailBroadcast","summary":"Create a broadcast","description":"Creates a broadcast for a stored audience. The default is an editable draft.\nSet `send` to `true` to send now or at `scheduled_at`; `scheduled_at` without\n`send` set to `true` returns `422`. Use\n[Create an email message](/docs/api/reference/create-email-message) for a small,\nexplicit recipient list.\n\nContacts in the audience at send time become recipients after suppressions. Read\ntheir states with\n[List recipients of a broadcast](/docs/api/reference/list-email-broadcast-recipients).\nWith `send`, the request returns `422` if any of these are true:\n\n- The sender domain is not verified.\n- No audience is selected, or the selected audience no longer exists.\n- The audience has no contact with an email address, or every contact is suppressed for the broadcast's category.\n- No template is set, or the set template has no published version.\n- The organization has used its broadcast allowance for the current billing period, or (for an immediate send) is already at its concurrent-broadcast limit; schedule the send instead to wait for a free slot.\n\nIf the audience is empty when a scheduled send starts, the broadcast becomes\n`failed` with a readable reason instead of silently dropping it.\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.create","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcastCreateRequest"}}}},"responses":{"201":{"description":"Draft broadcast created.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcast"},"example":{"id":"eb_01krdgeqcxet5s7t44vh8rt9mg","from":{"email":"jane@acme.com","name":"Jane Doe"},"audience_id":"adn_01krdgeqcxet5s7t44vh8rt9mg","template":{"id":"emt_01krdgeqcxet5s7t44vh8rt9mg","version_id":null},"category":"marketing","reply_to":[{"email":"jane@acme.com","name":"Jane Doe"}],"tags":[{"name":"category","value":"welcome"}],"track_opens":true,"track_clicks":true,"created_at":"2026-09-01T09:14:02.418Z","recipient_count":0,"sent_at":null,"status":"draft"}}}},"202":{"description":"Broadcast accepted for delivery.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcast"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]},"get":{"operationId":"listEmailBroadcasts","summary":"List broadcasts","description":"Returns a paginated list of broadcasts in the workspace, newest first. `created_after` and `created_before` narrow the list to broadcasts created in a half-open window, which is how you page a single month or quarter rather than the whole history.\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.list","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"$ref":"#/components/parameters/EmailBroadcastStatusFilter"},{"$ref":"#/components/parameters/EmailBroadcastAudienceFilter"},{"$ref":"#/components/parameters/EmailBroadcastTagFilter"},{"$ref":"#/components/parameters/EmailBroadcastSearchFilter"},{"$ref":"#/components/parameters/CreatedAfter"},{"$ref":"#/components/parameters/CreatedBefore"}],"responses":{"200":{"description":"Paginated list of broadcasts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcastList"},"example":{"data":[{"id":"eb_01krdgeqcxet5s7t44vh8rt9mg","from":{"email":"jane@acme.com","name":"Jane Doe"},"audience_id":"adn_01krdgeqcxet5s7t44vh8rt9mg","template":{"id":"emt_01krdgeqcxet5s7t44vh8rt9mg","version_id":"emv_01krdgeqcxet5s7t44vh8rt9mg"},"category":"marketing","reply_to":[{"email":"jane@acme.com","name":"Jane Doe"}],"status":"sent","failure_reason":null,"failure_detail":null,"recipient_count":4820,"sent_count":4820,"delivered_count":4712,"bounced_count":96,"complained_count":12,"open_count":3104,"click_count":812,"unique_opens_non_prefetched":2140,"unique_clicks":693,"out_of_band_bounces":14,"delivered_recipients":4724,"tags":[{"name":"category","value":"welcome"}],"track_opens":true,"track_clicks":true,"created_at":"2026-09-01T09:14:02.418Z","scheduled_at":"2026-09-02T08:00:00Z","started_at":"2026-09-02T08:00:03.771Z","sent_at":"2026-09-02T08:11:47.902Z"}],"next_cursor":"eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE0OjAzOjEwWlwiIiwiaSI6IjAxOTJmM2IxLTRjN2UtN2EyYi05ZDYxLThmM2E1YzJlN2I0MCJ9","prev_cursor":null,"refresh_cursor":"eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE2OjQyOjAxWlwiIiwiaSI6IjAxOTJmM2IxLTllMDQtN2NkMy1iODE3LTJhNmY0ZDFjOGUwOSJ9"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/broadcasts/export":{"get":{"operationId":"getEmailBroadcastsExport","summary":"Export broadcasts as CSV","description":"Downloads the workspace's broadcasts as a CSV file, one row per broadcast,\nnewest first. The header names fifteen columns, in this order:\n`created_at`, `broadcast_id`, `status`, `template_name`, `audience_id`,\n`audience_name`, `recipient_count`, `sent_count`, `delivered_count`,\n`track_opens`, `open_rate`, `track_clicks`, `click_rate`, `scheduled_at`\nand `sent_at`. `broadcast_id` is what joins a row back to the rest of the\nAPI. It takes the same filters as the broadcast list, so the file matches\nwhat the list shows for the same query. `recipient_count` is empty until a\nsend resolves the audience, so an empty cell there means the broadcast has\nno recipient list yet rather than a list of nobody.\n\n`open_rate` and `click_rate` are decimal fractions to four places, over the\nrecipients the message reached: `open_rate` is unique non-prefetched opens\ndivided by that number, and `click_rate` is unique clicks divided by it.\nThat divisor is not the `delivered_count` column, which resolves each\nrecipient to a single status and so counts a recipient who delivered and\nthen complained under the complaint, so neither rate can be reproduced from\nthis file alone. Either is empty when its `track_opens` or `track_clicks`\nflag is `false`, and also when the message reached nobody, so an empty rate\ndoes not mean tracking is off. Both flags default to `true` and the export\nnarrows by no status of its own, so a `draft` or `scheduled` row is included\nunless `status` excludes it, and carries `track_opens` as `true` with\n`open_rate` empty.\n\nA text cell whose first character is `=`, `+`, `-`, `@`, a tab or a carriage\nreturn is written with a leading apostrophe, so a spreadsheet reads it as\ntext rather than as a formula. A value that begins with an apostrophe of\nits own is written unchanged, so the original text cannot always be\nrecovered from the CSV cell. Match records by `broadcast_id` or `audience_id`,\nrather than by `template_name` or `audience_name`.\n\nThe file is complete or it is refused: a query matching more broadcasts than\none file carries returns a 422 naming the limit rather than a truncated\ndownload. Narrow it with `created_after` and `created_before` and request\neach window separately.\n","tags":["email-broadcasts"],"x-audiences":["public"],"x-snippet-key":"none","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/EmailBroadcastStatusFilter"},{"$ref":"#/components/parameters/EmailBroadcastAudienceFilter"},{"$ref":"#/components/parameters/EmailBroadcastTagFilter"},{"$ref":"#/components/parameters/EmailBroadcastSearchFilter"},{"$ref":"#/components/parameters/CreatedAfter"},{"$ref":"#/components/parameters/CreatedBefore"}],"responses":{"200":{"description":"The broadcasts as a CSV file.","headers":{"Content-Disposition":{"$ref":"#/components/headers/ContentDisposition"}},"content":{"text/csv":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/email/broadcasts/{broadcast_id}":{"get":{"operationId":"getEmailBroadcast","summary":"Get a broadcast","description":"Returns one broadcast, with its audience reference and counters.\n\nThe full recipient list, with each recipient's own delivery status, is paginated separately: see [List recipients of a broadcast](/docs/api/reference/list-email-broadcast-recipients).\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.get","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"broadcast_id","in":"path","required":true,"description":"Broadcast identifier. Starts with `eb_`.","schema":{"type":"string","pattern":"^eb_[0-9a-hjkmnp-tv-z]{26}$"}}],"responses":{"200":{"description":"The broadcast.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcast"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]},"patch":{"operationId":"updateEmailBroadcast","summary":"Update a broadcast","description":"Updates a draft or scheduled broadcast. A field you supply in the request is changed. A field you leave out is left as it is.\n\nA scheduled broadcast stays editable right up until it is accepted for sending. After that, editing it returns a conflict error.\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.update","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"broadcast_id","in":"path","required":true,"description":"Broadcast identifier. Starts with `eb_`.","schema":{"type":"string","pattern":"^eb_[0-9a-hjkmnp-tv-z]{26}$"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcastUpdateRequest"}}}},"responses":{"200":{"description":"The updated broadcast.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcast"},"example":{"id":"eb_01krdgeqcxet5s7t44vh8rt9mg","from":{"email":"jane@acme.com","name":"Jane Doe"},"audience_id":"adn_01krdgeqcxet5s7t44vh8rt9mg","template":{"id":"emt_01krdgeqcxet5s7t44vh8rt9mg","version_id":null},"category":"marketing","reply_to":[{"email":"jane@acme.com","name":"Jane Doe"}],"tags":[{"name":"category","value":"welcome"}],"track_opens":true,"track_clicks":true,"created_at":"2026-09-01T09:14:02.418Z","status":"scheduled","recipient_count":0,"scheduled_at":"2026-09-02T08:00:00Z","sent_at":null}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]},"delete":{"operationId":"deleteEmailBroadcast","summary":"Delete a broadcast","description":"Deletes a draft broadcast. Only a draft can be deleted. To stop a scheduled or sending broadcast, use [Cancel a broadcast](/docs/api/reference/cancel-email-broadcast) instead.\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.delete","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"broadcast_id","in":"path","required":true,"description":"Broadcast identifier. Starts with `eb_`.","schema":{"type":"string","pattern":"^eb_[0-9a-hjkmnp-tv-z]{26}$"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Draft broadcast deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/broadcasts/{broadcast_id}/counts":{"get":{"operationId":"getEmailBroadcastCounts","summary":"Get a broadcast's audience counts","description":"Returns a live estimate of how many contacts the broadcast can reach, narrowing from everyone in its audience down to the ones the send would go to.\n\n- `total` is every contact in the audience.\n- `addressable` is how many of those have an email address.\n- `sendable` is how many addressable contacts are not suppressed for the broadcast's category.\n\nA transactional broadcast still reaches a contact who unsubscribed from or complained about marketing mail. A marketing broadcast does not.\n\nThe estimate can change until the broadcast sends. The broadcast must have an audience selected; otherwise, the request returns `422`.\n\nThese are audience numbers, and sending does not change them: after a send they still describe who the broadcast resolved to, not what happened to them. `status` says which of the two situations you are in. For delivery outcomes, read [the broadcast's recipients](/docs/api/reference/list-email-broadcast-recipients) for per-recipient status, [its events](/docs/api/reference/list-email-broadcast-events) for the raw feed, or [email statistics by broadcast](/docs/api/reference/get-email-stats-by-broadcast) for the aggregates.\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.counts","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"broadcast_id","in":"path","required":true,"description":"Broadcast identifier. Starts with `eb_`.","schema":{"$ref":"#/components/schemas/EmailBroadcastID"}}],"responses":{"200":{"description":"The broadcast's audience counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcastCounts"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/broadcasts/{broadcast_id}/send-quota":{"get":{"operationId":"getEmailBroadcastSendQuota","summary":"Get how much of a broadcast the send allowance covers","description":"Returns how much of the broadcast the organization's email send allowance covers, so a send that would run past it can be reconsidered rather than failing part-way. `recipients` is how many contacts the broadcast would send to right now, the same number the audience counts report as sendable. `allowed` is how many of those the allowance covers, and equals `recipients` when nothing limits the send. `limited_by` names the allowance that stops the rest, either the monthly one that runs with the billing period or the daily one, with `limit` and `remaining` describing it. Both numbers are live estimates: audience membership, suppressions and the sends already made in the current window all move until the broadcast sends. The broadcast must have an audience selected; a broadcast with no audience yet returns a 422.\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.send_quota","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"broadcast_id","in":"path","required":true,"description":"ID of the broadcast whose send allowance to check.","schema":{"$ref":"#/components/schemas/EmailBroadcastID"}}],"responses":{"200":{"description":"How much of the broadcast the send allowance covers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcastSendQuota"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/broadcasts/{broadcast_id}/recipients":{"get":{"operationId":"listEmailBroadcastRecipients","summary":"List recipients of a broadcast","description":"Returns recipient-level delivery state for a broadcast, paginated. This is who a broadcast reached and how far each one got.\n\nThe list is empty for a draft, scheduled, or accepted broadcast: recipients are resolved from the audience only once sending begins. Pass `to` to return just that address's row, which is how you answer \"did this person get it?\" on a send too large to page through.\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.list_recipients","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"broadcast_id","in":"path","required":true,"description":"ID of the broadcast whose recipients to read.","schema":{"type":"string","pattern":"^eb_[0-9a-hjkmnp-tv-z]{26}$"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"name":"to","in":"query","required":false,"description":"Return only the recipient at this address. Exact match, normalised to lowercase before comparison, so it returns at most one row.","schema":{"type":"string","format":"email","example":"user@example.com"}}],"responses":{"200":{"description":"Paginated list of recipients for this broadcast.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailRecipientList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/broadcasts/{broadcast_id}/recipients/export":{"get":{"operationId":"getEmailBroadcastRecipientsExport","summary":"Export a broadcast's recipients as CSV","description":"Downloads a broadcast's recipients as a CSV file, one row per recipient. The\nheader names ten columns, in this order: `recipient`, `status`,\n`rejection_reason`, `open_count`, `click_count`, `processed_at`,\n`delivered_at`, `processing_latency_ms`, `delivery_latency_ms` and\n`total_latency_ms`.\n\n`rejection_reason` is filled only on a `status: rejected` row and names why\nthe message was never sent, which is not why it bounced. A bounce happens\nafter the send, so no column here carries `bounce_type` or `bounce_code`:\nread [List recipients of a broadcast](/docs/api/reference/list-email-broadcast-recipients)\nfor those. The three latency columns are milliseconds, and each is empty\nuntil the milestone it measures has happened: `processing_latency_ms` from\nthe send being accepted to the message being prepared for delivery,\n`delivery_latency_ms` from prepared to the receiving mail server accepting\nit, and `total_latency_ms` the accepted-to-delivered time end to end.\n\nA text cell whose first character is `=`, `+`, `-`, `@`, a tab or a carriage\nreturn is written with a leading apostrophe, so a spreadsheet reads it as\ntext rather than as a formula. Nothing else is prefixed, so a value that\nbegins with an apostrophe of its own is written unchanged: strip a leading\napostrophe only when one of those six characters follows it, before matching\n`recipient` back to the API.\n\nThe file is complete or it is refused: a broadcast with more recipients than\none file carries returns a 422 naming the limit rather than a truncated\ndownload. Pass `to` to export one address, or page\n[List recipients of a broadcast](/docs/api/reference/list-email-broadcast-recipients),\nwhich has no limit.\n","tags":["email-broadcasts"],"x-audiences":["public"],"x-snippet-key":"none","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"broadcast_id","in":"path","required":true,"description":"Broadcast identifier. Starts with `eb_`.","schema":{"type":"string","pattern":"^eb_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"to","in":"query","required":false,"description":"Return only the recipient at this address. Exact match, normalised to lowercase before comparison, so the file carries at most one row.","schema":{"type":"string","format":"email","example":"user@example.com"}}],"responses":{"200":{"description":"The broadcast's recipients as a CSV file.","headers":{"Content-Disposition":{"$ref":"#/components/headers/ContentDisposition"}},"content":{"text/csv":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/email/broadcasts/{broadcast_id}/events":{"get":{"operationId":"listEmailBroadcastEvents","summary":"List events for a broadcast","description":"Returns the per-recipient delivery timeline for a broadcast, oldest first, as a cursor page. Each entry is one event, such as a send, an open, a click, or a bounce.\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.list_events","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"broadcast_id","in":"path","required":true,"description":"ID of the broadcast whose event timeline to read.","schema":{"type":"string","pattern":"^eb_[0-9a-hjkmnp-tv-z]{26}$"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"name":"type","in":"query","required":false,"description":"Filter by event type, for example `email.bounced` or `email.opened`. A broadcast timeline is recipient-scoped, so `email.scheduled` and `email.canceled` never appear on it; a canceled broadcast reports that in its own `status`.","schema":{"$ref":"#/components/schemas/EmailEventType"}}],"responses":{"200":{"description":"Paginated event timeline for this broadcast.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailEventList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/broadcasts/{broadcast_id}/clicked-links":{"get":{"operationId":"listEmailBroadcastClickedLinks","summary":"List a broadcast's clicked links","description":"Returns the destination URLs a broadcast's recipients clicked, grouped by URL and sorted by click count. Each entry carries an exact click count and distinct-recipient count over every click event the broadcast has, plus the link's name: the name used by the most clicks that named it, or null if no click through that URL ever carried one. `data` is capped at the 100 most-clicked URLs; `total` carries the actual number of distinct URLs clicked, so a capped response is never mistaken for a complete one.\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.list_clicked_links","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"broadcast_id","in":"path","required":true,"description":"ID of the broadcast whose clicked links to read.","schema":{"$ref":"#/components/schemas/EmailBroadcastID"}}],"responses":{"200":{"description":"The broadcast's clicked links, grouped by URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcastClickedLinkList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/broadcasts/{broadcast_id}/send":{"post":{"operationId":"sendEmailBroadcast","summary":"Send a broadcast","description":"Sends a draft or scheduled broadcast, either immediately or at `scheduled_at`.\nCalling this on a scheduled broadcast replaces its schedule or sends it now if\n`scheduled_at` is omitted. Calling it again on a broadcast that is already\n`accepted` retries dispatch rather than returning an error. A broadcast that\nhas started sending, or that already reached a final state, returns a conflict.\n\nThe audience's contacts at send time become the recipients after suppressions.\nThe request returns `422` if any of these are true:\n\n- The sender domain is not verified.\n- No audience is selected, or the selected audience no longer exists.\n- The audience has no contact with an email address, or every contact is suppressed for the broadcast's category.\n- No template is set, or the set template has no published version.\n- The organization has used its broadcast allowance for the current billing period, or (for an immediate send) is already at its concurrent-broadcast limit; schedule the send instead to wait for a free slot.\n\nIf the audience is empty when a scheduled send starts, the broadcast becomes\n`failed` with a readable reason instead of silently dropping it.\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.send","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"broadcast_id","in":"path","required":true,"description":"Broadcast identifier. Starts with `eb_`.","schema":{"type":"string","pattern":"^eb_[0-9a-hjkmnp-tv-z]{26}$"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcastSendNowRequest"}}}},"responses":{"202":{"description":"Broadcast accepted for delivery.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcast"},"example":{"id":"eb_01krdgeqcxet5s7t44vh8rt9mg","from":{"email":"jane@acme.com","name":"Jane Doe"},"audience_id":"adn_01krdgeqcxet5s7t44vh8rt9mg","template":{"id":"emt_01krdgeqcxet5s7t44vh8rt9mg","version_id":null},"category":"marketing","reply_to":[{"email":"jane@acme.com","name":"Jane Doe"}],"tags":[{"name":"category","value":"welcome"}],"track_opens":true,"track_clicks":true,"created_at":"2026-09-01T09:14:02.418Z","status":"accepted","recipient_count":0,"sent_at":null}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/broadcasts/{broadcast_id}/cancel":{"post":{"operationId":"cancelEmailBroadcast","summary":"Cancel a broadcast","description":"Cancels a scheduled, accepted, or sending broadcast. Canceling it while it is sending stops every delivery that has not gone out yet, though messages already on their way to a recipient are not recalled.\n\nCalling this again on a broadcast that is already canceling or canceled is idempotent: it returns the broadcast's current state rather than an error. A draft cannot be canceled, because it was never sent; use [Delete a broadcast](/docs/api/reference/delete-email-broadcast) instead. A broadcast that already sent or failed has reached a terminal state and returns a conflict.\n","tags":["email-broadcasts"],"x-audiences":["public","command"],"x-snippet-key":"broadcasts.cancel","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"broadcast_id","in":"path","required":true,"description":"Broadcast identifier. Starts with `eb_`.","schema":{"type":"string","pattern":"^eb_[0-9a-hjkmnp-tv-z]{26}$"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"202":{"description":"Cancellation accepted.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailBroadcast"},"example":{"id":"eb_01krdgeqcxet5s7t44vh8rt9mg","from":{"email":"jane@acme.com","name":"Jane Doe"},"audience_id":"adn_01krdgeqcxet5s7t44vh8rt9mg","template":{"id":"emt_01krdgeqcxet5s7t44vh8rt9mg","version_id":"emv_01krdgeqcxet5s7t44vh8rt9mg"},"category":"marketing","reply_to":[{"email":"jane@acme.com","name":"Jane Doe"}],"tags":[{"name":"category","value":"welcome"}],"track_opens":true,"track_clicks":true,"created_at":"2026-09-01T09:14:02.418Z","status":"canceling","failure_reason":null,"failure_detail":null,"recipient_count":4820,"scheduled_at":"2026-09-02T08:00:00Z","started_at":"2026-09-02T08:00:03.771Z","sent_at":null,"canceled_at":"2026-09-02T08:06:12.204Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/contacts":{"post":{"operationId":"createContact","x-snippet-key":"contacts.create","summary":"Create a contact","description":"Creates a contact in the workspace, identified by an email address, a phone number, or both; at least one is required. Email is stored trimmed and lowercased, and phone in its canonical international form. Creating a second contact with the same email or phone number, or reusing another contact's `external_id`, returns a conflict error.\n\nTo create or update many contacts in one request, or to write a contact without knowing whether the address already exists, use [Create or update contacts in bulk](/docs/api/reference/create-contact-batch) instead.\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreateRequest"}}}},"responses":{"201":{"description":"Contact created.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"get":{"operationId":"listContacts","x-snippet-key":"contacts.list","summary":"List contacts","description":"Returns a paginated list of contacts in the workspace, newest first. Look up a single contact by its exact `email`, `phone_number`, or `external_id`, or search by email, first name, last name, or phone substring with `q`. Repeat `phone_number` to resolve up to 50 numbers to their contacts in one request, raising `limit` to at least the number of values you pass. Pass `include_total=true` to add the total number of matching contacts to the response.\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"email","in":"query","required":false,"description":"Return the contact with exactly this email address (case-insensitive). Email is unique within a workspace, so this matches at most one contact. An empty value is a validation error, never an unfiltered page.","schema":{"type":"string"},"example":"user@example.com"},{"name":"phone_number","in":"query","required":false,"description":"Return the contacts with exactly this phone number in international E.164 form. Repeat the parameter to match any of up to 50 numbers. Set `limit` to at least the number of values you pass. The default `limit` is 25, and a page cut short by it looks exactly like numbers that matched nothing. Different identifier parameters still combine with AND, so `phone_number=a&phone_number=b&email=c` asks for a contact whose phone number is `a` or `b` and whose email is `c`. Encode the leading plus sign as `%2B` (an unencoded `+` arrives as a space and is rejected). Phone numbers are unique within a workspace, so each value matches at most one contact. Non-canonical forms of the same number match the contact they canonicalize to; a value that is not a phone number shape, or an empty value, is a validation error, never an unfiltered page.","schema":{"type":"array","maxItems":50,"items":{"type":"string"}},"example":["+31612345678","+31698765432"]},{"name":"external_id","in":"query","required":false,"description":"Return the contact with exactly this external_id (your own identifier for the contact). Unique within a workspace, so this matches at most one contact. An empty value is a validation error, never an unfiltered page.","schema":{"type":"string"},"example":"user_12345"},{"name":"q","in":"query","required":false,"description":"Case-insensitive substring match against the contact's email address, first name, last name, or phone number. Phone matching is over the digits of the international form, so a full pasted number, a formatted number, or trailing digits all match; a national form with a leading trunk zero does not.","schema":{"type":"string","minLength":1},"example":"acme.com"},{"name":"identifier","in":"query","required":false,"description":"Filter to contacts that have a specific identifier on file.","schema":{"$ref":"#/components/schemas/ContactIdentifierFilter"},"example":"email"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"$ref":"#/components/parameters/IncludeTotal"}],"responses":{"200":{"description":"A page of contacts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/contacts/batch":{"post":{"operationId":"createContactBatch","x-snippet-key":"contacts.batch","summary":"Create or update contacts in bulk","description":"Creates or updates up to 1,000 contacts in one request. Each entry is matched automatically against every identifier it supplies: its email address (trimmed and lowercased), its phone number (normalized to international form), and your own `external_id`. An entry with no match creates a contact. An entry whose identifiers all match one contact updates the supplied fields and preserves omitted fields. This lets an email address change under a stable `external_id` without creating a second contact. An entry whose identifiers belong to several contacts fails with an error naming each match; contacts are never merged automatically. Supplying `match_on` makes that field the only matching key, and every entry must include it. You can also add every contact in the request to up to 10 audiences.\n\nEach entry succeeds or fails on its own: the response lists one result per contact in submission order (`created`, `updated`, or `failed` with the reason), and a failed entry does not abort the rest. If the request itself is invalid, for example when an entry in `audience_ids` does not exist, the whole request fails with a validation error and no contacts are written.\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpsertRequest"}}}},"responses":{"200":{"description":"Per-contact results, in submission order.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpsertResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/contacts/{contact_id}":{"get":{"operationId":"getContact","x-snippet-key":"contacts.get","summary":"Get a contact","description":"Returns a single contact, including its custom `data` values and the channels it can be reached on. To find a contact's ID by email address or `external_id`, use [List contacts](/docs/api/reference/list-contacts).\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"contact_id","in":"path","required":true,"description":"ID of the contact to fetch.","schema":{"$ref":"#/components/schemas/ContactID"}}],"responses":{"200":{"description":"The contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]},"patch":{"operationId":"updateContact","x-snippet-key":"contacts.update","summary":"Update a contact","description":"Updates a contact. Supplied fields are changed and omitted fields are left unchanged; set `first_name`, `last_name`, or `external_id` to `null` to clear them. Custom values in `data` are merged: keys you supply are set, keys set to `null` are removed, and keys you omit are unchanged.\n\nChanging the email address, phone number, or `external_id` to a value already used by another contact returns a conflict error. A contact always keeps at least one identifier. Clearing both email and phone in the same contact is rejected.\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"contact_id","in":"path","required":true,"description":"ID of the contact to update.","schema":{"$ref":"#/components/schemas/ContactID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpdateRequest"}}}},"responses":{"200":{"description":"The updated contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"delete":{"operationId":"deleteContact","x-snippet-key":"contacts.delete","summary":"Delete a contact","description":"Deletes a contact permanently and removes it from every audience it belongs to. Suppression records for the address are not affected: an unsubscribed or bounced address stays suppressed even after the contact is deleted.\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"contact_id","in":"path","required":true,"description":"ID of the contact to delete.","schema":{"$ref":"#/components/schemas/ContactID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Contact deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/contacts/{contact_id}/preferences":{"parameters":[{"name":"contact_id","in":"path","required":true,"description":"ID of the contact.","schema":{"$ref":"#/components/schemas/ContactID"},"example":"con_01krdgeqcxet5s7t44vh8rt9mg"}],"get":{"operationId":"listContactPreferences","summary":"List a contact's preferences","description":"Returns the preferences on record for the contact's current handles: rows keyed to their email address on the email channel, and to their phone number on SMS and WhatsApp. A contact with no handles, or with no statements on record, returns an empty page.\n\nRows are keyed by handle, not by contact: changing a contact's email address or phone number changes which rows this returns, and the old handle's rows remain in force for anything still sent to it.\n","tags":["preferences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"x-snippet-key":"contacts.preferences.list","parameters":[{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of the contact's preferences.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreferenceList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["make","n8n","sdk"]}},"/v1/preferences":{"get":{"operationId":"listPreferences","summary":"List preferences","description":"Returns the workspace's recorded preferences, most recently created first. Pass `channel` to narrow to one channel, and `handle` with it to look up everything on record for one address or number.\n\nEach row is a key's current statement. A person can hold several rows on one channel (a channel-wide opt-out next to sender-scoped ones), and the most restrictive statement is what decides whether a message goes out.\n","tags":["preferences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"preferences.list","parameters":[{"name":"channel","in":"query","required":false,"description":"Return only preferences on this channel.","schema":{"$ref":"#/components/schemas/PreferenceChannel"}},{"name":"handle","in":"query","required":false,"description":"Return only preferences for this exact handle: an email address or an E.164 phone number. Requires `channel`, since a handle only means something on its channel.\n","schema":{"type":"string","minLength":1,"maxLength":320},"example":"+15550001234"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of preferences.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreferenceList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"post":{"operationId":"createPreference","summary":"Record a preference","description":"Records one statement, a grant or an opt-out, for a handle on one channel. Writing is an upsert: the key is the channel, handle, and optional sender scope, and a new statement replaces the key's current one.\n\nStatements are ordered by when they were made, not when they arrive. A statement older than the key's current one is refused and returned with `applied: false` alongside the statement that survived; refusals are recorded on the key's history. Granting over a stored opt-out needs `consented_at` later than the opt-out, and a person's own opt-out (an unsubscribe, a stop keyword) cannot be overridden by a grant asserted on their behalf.\n\nA `201` means this key had no record and one was created; a `200` returns the key's surviving record, whether this statement replaced it, repeated it, or was refused.\n","tags":["preferences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"preferences.create","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreferenceCreate"},"example":{"channel":"sms","handle":"+15550001234","status":"revoked","coverage":"non_transactional"}}}},"responses":{"200":{"description":"The key already had a record. The result says whether this statement replaced it, repeated it, or was refused as out of order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreferenceWriteResult"}}}},"201":{"description":"The key had no record; this statement created one.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreferenceWriteResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/preferences/{preference_id}":{"parameters":[{"name":"preference_id","in":"path","required":true,"description":"ID of the preference, as returned when it was recorded or listed. The ID stays stable while the key holds a record; deleting and re-recording the same key mints a new one.\n","schema":{"$ref":"#/components/schemas/PreferenceID"},"example":"prf_01krdgeqcxet5s7t44vh8rt9mg"}],"get":{"operationId":"getPreference","summary":"Get a preference","description":"Returns one preference: the key it is about, the current statement on it, and the statement's provenance. An ID that does not exist in the workspace returns `404`, including after a delete, which removes the record its ID pointed at.\n","tags":["preferences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"preferences.get","responses":{"200":{"description":"Preference.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preference"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"delete":{"operationId":"deletePreference","summary":"Delete a preference","description":"Deletes a preference, returning its key to having no record, as if nothing had ever been stated. The deletion itself is kept on the key's history, so a later statement is still ordered against what was deleted.\n\n**A statement the person made themselves cannot be deleted.** An unsubscribe or a stop keyword is their statement to reverse: it ends when they opt back in, and attempts to delete it return `422`. To restore messaging with the person's consent, record a `granted` statement with `consented_at` evidence instead; that records the change of mind rather than erasing the opt-out.\n\nA delete is ordered like any statement, using the time it is received: if the record carries a statement made after that moment, the delete is refused and returned with `applied: false` alongside the surviving record. An ID that does not exist in the workspace returns `404`.\n","tags":["preferences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"preferences.delete","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"The outcome. `applied: true` with a null `preference` means the record is gone; `applied: false` means a newer statement survived the delete and is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreferenceWriteResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/contact-properties":{"post":{"operationId":"createContactProperty","x-snippet-key":"contact_properties.create","summary":"Create a contact property","description":"Defines a custom property that contacts in the workspace can carry. The key becomes available in contact `data` and as a template variable in broadcasts. The key and type cannot be changed after creation.\n\nA key already in use returns a conflict error. A workspace can hold at most 200 properties; archived properties keep their key and count toward that limit.\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactPropertyCreateRequest"}}}},"responses":{"201":{"description":"Contact property created.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactProperty"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"get":{"operationId":"listContactProperties","x-snippet-key":"contact_properties.list","summary":"List contact properties","description":"Returns a paginated list of the workspace's contact properties, newest first. Archived properties are included; check each entry's `archived` flag.\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"A page of contact properties.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactPropertyList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/contact-properties/{property_id}":{"get":{"operationId":"getContactProperty","x-snippet-key":"contact_properties.get","summary":"Get a contact property","description":"Returns a single contact property: its immutable key and type, the fallback value, and whether it is archived.\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"property_id","in":"path","required":true,"description":"ID of the contact property to fetch.","schema":{"$ref":"#/components/schemas/ContactPropertyID"}}],"responses":{"200":{"description":"The contact property.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactProperty"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"patch":{"operationId":"updateContactProperty","x-snippet-key":"contact_properties.update","summary":"Update a contact property","description":"Updates a contact property's fallback value, the only mutable field. The key and type cannot be changed after creation; create a new property instead.\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"property_id","in":"path","required":true,"description":"ID of the contact property to update.","schema":{"$ref":"#/components/schemas/ContactPropertyID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactPropertyUpdateRequest"}}}},"responses":{"200":{"description":"The updated contact property.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactProperty"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/contact-properties/{property_id}/archive":{"post":{"operationId":"archiveContactProperty","x-snippet-key":"contact_properties.archive","summary":"Archive a contact property","description":"Archives a contact property. The key stops being accepted in contact writes and stops rendering in templates, but every value already stored on your contacts is preserved and still returned when you read a contact.\n\nThe key stays reserved and still counts toward the workspace's 200-property limit, so it cannot be re-created with a different type. Archiving an already-archived property returns a conflict error; reverse it with [Unarchive a contact property](/docs/api/reference/unarchive-contact-property).\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"property_id","in":"path","required":true,"description":"ID of the contact property to archive.","schema":{"$ref":"#/components/schemas/ContactPropertyID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"The archived contact property.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactProperty"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/contact-properties/{property_id}/unarchive":{"post":{"operationId":"unarchiveContactProperty","x-snippet-key":"contact_properties.unarchive","summary":"Unarchive a contact property","description":"Reactivates an archived contact property. The key is accepted in contact writes and renders in templates again; stored values were never removed, so they are unchanged. Unarchiving a property that is not archived returns a conflict error.\n","tags":["email-contacts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"property_id","in":"path","required":true,"description":"ID of the contact property to unarchive.","schema":{"$ref":"#/components/schemas/ContactPropertyID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"The reactivated contact property.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactProperty"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/audiences":{"post":{"operationId":"createAudience","x-snippet-key":"audiences.create","summary":"Create an audience","description":"Creates an audience in the workspace. New audiences start empty: add members with [Add contacts to an audience](/docs/api/reference/assign-audience-contacts) or through [Create or update contacts in bulk](/docs/api/reference/create-contact-batch). The `type` field currently accepts only `static` audiences.\n","tags":["email-audiences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceCreateRequest"}}}},"responses":{"201":{"description":"The created audience.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Audience"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]},"get":{"operationId":"listAudiences","x-snippet-key":"audiences.list","summary":"List audiences","description":"Returns a paginated list of audiences in the workspace, newest first. Filter to audiences whose name contains a substring with `q`.\n","tags":["email-audiences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"q","in":"query","required":false,"description":"Case-insensitive substring match against the audience's name.","schema":{"type":"string","minLength":1},"example":"newsletter"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"A page of audiences.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/audiences/{audience_id}":{"get":{"operationId":"getAudience","x-snippet-key":"audiences.get","summary":"Get an audience","description":"Returns a single audience: its name, description, and type. The member list is separate; fetch it with [List an audience's contacts](/docs/api/reference/list-audience-contacts).\n","tags":["email-audiences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"audience_id","in":"path","required":true,"description":"ID of the audience to fetch.","schema":{"$ref":"#/components/schemas/AudienceID"}}],"responses":{"200":{"description":"The audience.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Audience"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]},"patch":{"operationId":"updateAudience","x-snippet-key":"audiences.update","summary":"Update an audience","description":"Updates an audience's name or description. Omitted fields are left unchanged; set `description` to `null` to clear it.\n","tags":["email-audiences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"audience_id","in":"path","required":true,"description":"ID of the audience to update.","schema":{"$ref":"#/components/schemas/AudienceID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceUpdateRequest"}}}},"responses":{"200":{"description":"The updated audience.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Audience"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"delete":{"operationId":"deleteAudience","x-snippet-key":"audiences.delete","summary":"Delete an audience","description":"Deletes an audience and its memberships. Contacts themselves are not deleted. An audience cannot be deleted while a broadcast targeting it is scheduled, accepted, sending, or canceling; cancel that broadcast first, then retry.\n","tags":["email-audiences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"audience_id","in":"path","required":true,"description":"ID of the audience to delete.","schema":{"$ref":"#/components/schemas/AudienceID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"The audience was deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/audiences/{audience_id}/contacts":{"get":{"operationId":"listAudienceContacts","x-snippet-key":"audiences.list_contacts","summary":"List an audience's contacts","description":"Lists the contacts in a static audience as a cursor page, ordered by the time each contact joined the audience, most recent first. Each entry is the contact together with the time it joined.\n","tags":["email-audiences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"audience_id","in":"path","required":true,"description":"ID of the audience whose contacts to list.","schema":{"$ref":"#/components/schemas/AudienceID"}},{"name":"q","in":"query","required":false,"description":"Case-insensitive substring match against a contact's email address or the digits in its international phone number.","schema":{"type":"string","minLength":1},"example":"acme.com"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"A page of the audience's contacts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceMemberList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"post":{"operationId":"assignAudienceContacts","x-snippet-key":"audiences.add_contacts","summary":"Assign contacts to an audience","description":"Adds up to 1,000 contacts to an audience. Adding is idempotent: contacts that are already members are left in place and keep their original join time. If any contact ID does not exist in the workspace, the whole request fails with `422 Unprocessable Entity` and no contacts are added.\n","tags":["email-audiences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"audience_id","in":"path","required":true,"description":"ID of the audience to add contacts to.","schema":{"$ref":"#/components/schemas/AudienceID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceContactsAddRequest"}}}},"responses":{"204":{"description":"Contacts added to the audience.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/audiences/{audience_id}/contacts/remove":{"post":{"operationId":"unassignAudienceContacts","x-snippet-key":"audiences.remove_contacts","summary":"Unassign contacts from an audience","description":"Removes up to 1,000 contacts from an audience. Contacts that are not members are skipped. If any contact ID does not exist in the workspace, the whole request fails with `422 Unprocessable Entity` and no memberships are removed. The contacts themselves are not deleted and remain members of any other audiences.\n","tags":["email-audiences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"audience_id","in":"path","required":true,"description":"ID of the audience to remove contacts from.","schema":{"$ref":"#/components/schemas/AudienceID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudienceContactsRemoveRequest"}}}},"responses":{"204":{"description":"Contacts removed from the audience.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/audiences/{audience_id}/contacts/{contact_id}":{"delete":{"operationId":"unassignAudienceContact","x-snippet-key":"audiences.remove_contact","summary":"Unassign a contact from an audience","description":"Removes a contact's membership in an audience. The contact itself is not deleted and remains a member of any other audiences. Removing a contact that is not a member of the audience succeeds with no effect (`204 No Content`); an unknown audience or contact returns a not-found error.\n","tags":["email-audiences"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"audience_id","in":"path","required":true,"description":"ID of the audience to remove the contact from.","schema":{"$ref":"#/components/schemas/AudienceID"}},{"name":"contact_id","in":"path","required":true,"description":"ID of the contact to remove.","schema":{"$ref":"#/components/schemas/ContactID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"The contact was removed from the audience, or was already not a member."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/messages":{"post":{"operationId":"createSMSMessage","x-snippet-key":"sms.send","summary":"Create an SMS message","description":"Sends one SMS to one recipient with exactly one content form: `text`, which\nrequires `category` and `from`, or a stored `template`, which supplies its\ncategory. A workspace template requires `from`, while a built-in template\nselects its sender. To submit up to 100 independent messages in one request, use\n[Send a batch of SMS messages](/docs/api/reference/create-sms-message-batch)\ninstead.\n\nThe `202 Accepted` response means the API durably accepted the message for\nasynchronous delivery. Delivery remains pending; follow it with\n[Get an SMS message](/docs/api/reference/get-sms-message) or by subscribing\nto `sms.*` webhook events.\n\nAn invalid field, more than 12 segments, a disabled destination country, or\na sender not permitted for the destination returns `422`. Insufficient\nwallet balance returns `402`.\n","tags":["sms-messages"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSMessageSendRequest"},"examples":{"sms-smart-encoding":{"summary":"A send with smart encoding, which downgrades look-alike characters to GSM-7","value":{"to":"+31612345678","from":"Bird","text":"Your order shipped, track it here…","category":"transactional","options":{"smart_encoding":true}}},"sms-template":{"summary":"A send that renders a stored template","value":{"to":"+14155550100","template":{"slug":"bird_otp_verification","parameters":{"code":"123456"}}}},"onboarding-sms":{"summary":"The first send from the dashboard's onboarding step","value":{"to":"+14155550100","template":{"slug":"bird_otp_verification","parameters":{"code":"493021"}}}}}}}},"responses":{"202":{"description":"Message accepted for asynchronous delivery.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSMessage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]},"get":{"operationId":"listSMSMessages","x-snippet-key":"sms.list","summary":"List SMS messages","description":"Returns the workspace's SMS messages as a cursor-paginated list, newest\nfirst. Filter by direction, status, category, recipient, sender, failure\nreason, tag, or creation time; pass the response's `next_cursor` back as\n`starting_after` to fetch the next page. To follow a single message's\ndelivery, use [Get an SMS message](/docs/api/reference/get-sms-message)\ninstead.\n\nMessages are retained for **30 days**. A `created_after` earlier than that\nis accepted and raised to the retention bound rather than rejected, so a\nwider window returns what is still retained instead of failing. Messages\nolder than the retention window cannot be retrieved.\n","tags":["sms-messages"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"$ref":"#/components/parameters/CreatedAfter"},{"$ref":"#/components/parameters/CreatedBefore"},{"name":"direction","in":"query","required":false,"description":"Filter by direction. Omit for both.","schema":{"$ref":"#/components/schemas/MessageDirection"}},{"name":"status","in":"query","required":false,"description":"Keep only messages whose current `status` matches; repeat the parameter to match any of several. One of `scheduled`, `accepted`, `sent`, `delivered`, `undelivered`, `failed`, `rejected`, `canceled`, `expired`, or `received`. `scheduled` and `canceled` are accepted but match nothing until send-later scheduling ships.\n","schema":{"type":"array","items":{"type":"string","minLength":1}}},{"name":"error_code","in":"query","required":false,"description":"Keep only messages whose failure reason (`last_error.code`) matches; repeat the parameter to match any of several. One of `invalid_destination`, `unreachable`, `blocked_by_carrier`, `blocked_by_recipient`, `landline_unreachable`, `content_rejected`, `sender_unregistered`, `recipient_opted_out`, `provider_unavailable`, `insufficient_balance`, or `unknown`.\n","schema":{"type":"array","items":{"type":"string","minLength":1}}},{"name":"category","in":"query","required":false,"description":"Filter by category.","schema":{"$ref":"#/components/schemas/SMSMessageCategory"}},{"name":"to","in":"query","required":false,"description":"Filter by recipient phone number (E.164 exact match).","schema":{"type":"string","example":"+14155550100"}},{"name":"from","in":"query","required":false,"description":"Filter by sender (E.164, alphanumeric, or short code; exact match).","schema":{"type":"string","example":"+15557654321"}},{"$ref":"#/components/parameters/TagFilter"}],"responses":{"200":{"description":"Paginated list of messages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSMessageList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/sms/batches":{"post":{"operationId":"createSMSMessageBatch","x-snippet-key":"sms.sendBatch","summary":"Create a batch of SMS messages","description":"Sends up to 100 independent SMS messages in one request. Each item is a\ncomplete send request with its own recipient, content, ID, status, and\ncost. For a single message, use\n[Send an SMS message](/docs/api/reference/create-sms-message) instead.\n\nAcceptance is all-or-nothing: every item is validated before any is queued,\nand one invalid item rejects the whole batch with a `422` (nothing is\nsent). A batch from a workspace with no wallet balance fails with a `402`.\nThe `202` response lists the accepted messages in submission order; each\ndelivers asynchronously and is tracked individually, like a single send.\n","tags":["sms-messages"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSMessageBatchRequest"},"example":{"messages":[{"to":"+15551111111","from":"+15557654321","text":"Hi Alice!","category":"marketing"},{"to":"+15552222222","from":"+15557654321","text":"Hi Bob!","category":"marketing"}]}}}},"responses":{"202":{"description":"Batch accepted for asynchronous delivery.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSMessageBatchResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","mcp","n8n","sdk"]}},"/v1/sms/messages/{message_id}":{"get":{"operationId":"getSMSMessage","x-snippet-key":"sms.get","summary":"Get an SMS message","description":"Returns a single SMS message: its current delivery status, segment breakdown, cost, and failure detail when it failed. The `status` advances asynchronously as delivery progresses, and `cost` is null until the message has been priced, so poll this operation (or subscribe to `sms.*` webhook events) after a send to confirm delivery. To scan messages in bulk, use [List SMS messages](/docs/api/reference/list-sms-messages) instead.\n","tags":["sms-messages"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the message, as returned in the send response's `id` field.","schema":{"$ref":"#/components/schemas/SMSMessageID"}}],"responses":{"200":{"description":"SMS message with its current delivery status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSMessage"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/messages/{message_id}/events":{"get":{"operationId":"listSMSMessageEvents","summary":"List events for an SMS message","description":"Returns the lifecycle event timeline for a message, in chronological order.","tags":["sms-messages"],"x-audiences":["public","command"],"x-snippet-key":"sms.list_events","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the SMS message (`sms_` prefix), as returned when the message was accepted.","schema":{"$ref":"#/components/schemas/SMSMessageID"}},{"name":"type","in":"query","required":false,"description":"Filter by event type, such as `sms.delivered` or `sms.failed`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Event timeline for this message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSEventList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/templates":{"get":{"operationId":"listSMSTemplates","x-snippet-key":"smsTemplates.list","summary":"List SMS templates","description":"Returns SMS templates as a cursor-paginated list. The workspace's templates\ncome first, newest first, followed by our built-in templates by default.\nSet `order=asc` to reverse this order.\n\nFilter by scope, category, status, or language. Use `q` for a\ncase-insensitive substring match against slug, name, and description. The\nresponse is shallow; read a version to retrieve its content and variables.\n","tags":["sms-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"scope","in":"query","required":false,"description":"Filter by who owns the template. Use `system` for built-in templates and `workspace` for templates your workspace created.","schema":{"$ref":"#/components/schemas/TemplateScope"}},{"name":"category","in":"query","required":false,"description":"Return templates in this category.","schema":{"$ref":"#/components/schemas/SMSTemplateCategory"}},{"name":"status","in":"query","required":false,"description":"Return templates with this lifecycle status.","schema":{"$ref":"#/components/schemas/TemplateStatus"}},{"name":"language","in":"query","required":false,"description":"Return templates whose published content contains this language, after the tag is canonicalized. Draft-only languages do not match.","schema":{"$ref":"#/components/schemas/LanguageTag"}},{"name":"q","in":"query","required":false,"description":"A case-insensitive substring search across slug, name, and description.","schema":{"type":"string","minLength":1}},{"name":"sort","in":"query","required":false,"description":"Field to sort by.","schema":{"$ref":"#/components/schemas/SMSTemplateSortField"}},{"$ref":"#/components/parameters/OrderDesc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of SMS templates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSTemplateList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/sms/templates/{template_ref}":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's ID (`smt_…`) or slug. Built-in templates use a `bird_` slug. Write operations accept workspace templates because built-in templates are read-only.\n","schema":{"type":"string","minLength":1,"maxLength":63},"example":"bird_otp_verification"}],"get":{"operationId":"getSMSTemplate","x-snippet-key":"smsTemplates.get","summary":"Get an SMS template","description":"Returns one SMS template's metadata, policies, language states, draft revision, and draft and live version IDs. The response is shallow; read a version to retrieve content and variables. An unknown or deleted template returns `404`.\n","tags":["sms-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"responses":{"200":{"description":"The requested SMS template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSTemplate"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/sms/templates/{template_ref}/versions":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's ID (`smt_…`) or slug.","schema":{"type":"string","minLength":1,"maxLength":63},"example":"order-shipped"}],"get":{"operationId":"listSMSTemplateVersions","x-snippet-key":"smsTemplates.versions.list","summary":"List an SMS template's versions","description":"Returns a cursor-paginated version history, newest first. Each entry is shallow and names its variables and languages. Read a version item or one of its languages to retrieve text. A built-in template exposes its current catalogue content as one synthetic published version.\n","tags":["sms-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"sort","in":"query","required":false,"description":"Field to sort by.","schema":{"$ref":"#/components/schemas/SMSTemplateSortField"}},{"$ref":"#/components/parameters/OrderDesc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of template versions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSTemplateVersionList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/sms/templates/{template_ref}/versions/{version_id}":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's ID (`smt_…`) or slug.","schema":{"type":"string","minLength":1,"maxLength":63},"example":"order-shipped"},{"name":"version_id","in":"path","required":true,"description":"The version to read or reset.","schema":{"$ref":"#/components/schemas/SMSTemplateVersionID"}}],"get":{"operationId":"getSMSTemplateVersion","x-snippet-key":"smsTemplates.versions.get","summary":"Get an SMS template version","description":"Returns one version with its variables and text in every language. A workspace draft is editable, while published workspace versions are immutable. A built-in template exposes its current catalogue content through a synthetic published version.\n","tags":["sms-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"responses":{"200":{"description":"The requested SMS template version.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSTemplateVersion"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","sdk"]}},"/v1/sms/templates/{template_ref}/versions/{version_id}/languages":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's ID (`smt_…`) or slug.","schema":{"type":"string","minLength":1,"maxLength":63},"example":"order-shipped"},{"name":"version_id","in":"path","required":true,"description":"The version whose languages to list.","schema":{"$ref":"#/components/schemas/SMSTemplateVersionID"}}],"get":{"operationId":"listSMSTemplateVersionLanguages","x-snippet-key":"smsTemplates.versions.languages.list","summary":"List an SMS template version's languages","description":"Returns the languages a version holds, ordered by canonical language tag, without their text. Each summary includes the revision and content hash needed to detect changes. A version holds at most 25 languages.\n","tags":["sms-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"responses":{"200":{"description":"The version's language summaries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSTemplateLanguageList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/sms/templates/{template_ref}/versions/{version_id}/languages/{language}":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's ID (`smt_…`) or slug.","schema":{"type":"string","minLength":1,"maxLength":63},"example":"order-shipped"},{"name":"version_id","in":"path","required":true,"description":"The version that holds the language.","schema":{"$ref":"#/components/schemas/SMSTemplateVersionID"}},{"name":"language","in":"path","required":true,"description":"The language as a BCP-47 tag. Case and separator differences are accepted and canonicalized.","schema":{"$ref":"#/components/schemas/LanguageTag"},"example":"pt-BR"}],"get":{"operationId":"getSMSTemplateVersionLanguage","x-snippet-key":"smsTemplates.versions.languages.get","summary":"Get an SMS template version's language","description":"Returns one language's full text, revision, content hash, and update time. The response echoes the language in its canonical BCP-47 form.\n","tags":["sms-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"responses":{"200":{"description":"The requested language and its text.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSTemplateLanguage"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/sms/suppressions":{"get":{"operationId":"listSMSSuppressions","summary":"List SMS suppressions","description":"Returns the suppressions currently stopping your messages, most recent opt-out first. Pass `destination` to look up one subscriber before sending to them.\n\nA suppression covers one sender and one subscriber, so the same number can appear more than once: opting out of one of your senders does not opt out of the others.\n\nEnded suppressions are excluded. A subscriber who opted back in is reachable again and does not appear in this list.\n","tags":["sms-suppressions"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"sms_suppressions.list","parameters":[{"name":"destination","in":"query","required":false,"description":"Return only suppressions for this exact subscriber number in E.164 form. Prefix matching is unsupported.\n","schema":{"type":"string","minLength":2,"maxLength":20},"example":"+15550001234"},{"name":"originator","in":"query","required":false,"description":"Return only suppressions covering this sender.","schema":{"type":"string","minLength":1,"maxLength":20},"example":"+15557654321"},{"name":"reason","in":"query","required":false,"description":"Return only suppressions with this reason:\n\n- `keyword_stop`: The subscriber texted a stop keyword to the sender.\n- `carrier_opted_out`: Their carrier reported the opt-out.\n- `manual`: Added through the API or dashboard.\n","schema":{"$ref":"#/components/schemas/SMSSuppressionReasonFilter"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of SMS suppressions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSSuppressionList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"post":{"operationId":"createSMSSuppression","summary":"Create an SMS suppression","description":"Stops a sender's messages to a subscriber, with reason `manual`, blocking every category including transactional. Both ends are required: a suppression covers a sender-and-subscriber pair, so stopping all of your senders means one call per sender.\n\nAdding is idempotent. A `201` means a new suppression was recorded, and a `200` means a `manual` one for that pair was already in place and is returned unchanged. A pair already stopped for another reason, such as the subscriber having texted a stop keyword, still gets its own `manual` record, and messages stay stopped until every one of them has ended.\n","tags":["sms-suppressions"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"sms_suppressions.add","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSSuppressionCreate"},"example":{"destination":"+15550001234","originator":"+15557654321"}}}},"responses":{"200":{"description":"A manual suppression for this pair was already in place. The existing one is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSSuppression"}}}},"201":{"description":"Suppression recorded.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSSuppression"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/suppressions/{suppression_id}":{"parameters":[{"name":"suppression_id","in":"path","required":true,"description":"ID of the suppression, as returned when it was created or listed.\n","schema":{"$ref":"#/components/schemas/SMSSuppressionID"},"example":"ssu_01krdgeqcxet5s7t44vh8rt9mg"}],"get":{"operationId":"getSMSSuppression","summary":"Get an SMS suppression","description":"Returns one suppression: the sender and subscriber it covers, why messages are stopped, how the record came to exist, what it blocks, and whether it is still in force.\n\nThis operation also returns a suppression that has already ended. The `blocking` field is `false`, and the `ended_*` fields say when and why. An ID you kept from a create or delete therefore stays readable. To find one when you only know the number, use `GET /v1/sms/suppressions` with the `destination` parameter. An ID that does not exist in the workspace returns `404`.\n","tags":["sms-suppressions"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"sms_suppressions.get","responses":{"200":{"description":"SMS suppression.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSSuppression"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"delete":{"operationId":"deleteSMSSuppression","summary":"Delete an SMS suppression","description":"Ends a suppression, so the sender reaches the subscriber again. The record stays with the ending noted on it. This history helps answer later complaints or carrier audits.\n\n**Only the `manual` reason can be ended here.** A `keyword_stop` is the subscriber's own statement. It ends only when they text a start keyword to that sender. A `carrier_opted_out` mirrors what the carrier reported, so it ends when the carrier says so. Attempts to end either reason return `422`.\n\nEnding a suppression resumes messaging to someone your own records say did not want it, so do it only when you know why the `manual` record exists. An ID that does not exist in the workspace returns `404`, and one that has already ended returns `204`.\n","tags":["sms-suppressions"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"sms_suppressions.remove","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Suppression ended."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/keyword-rules":{"get":{"operationId":"listSMSKeywordRules","summary":"List SMS keyword rules","description":"Returns the default and workspace keyword rules that apply to inbound messages, most specific first. Where the default catalog covers a country, opt-out, opt-in, and help keywords work without setup.\n\nUse the filters to narrow the full, unpaginated list. Set `scope=system` for default rules only. Set `number` for rules in evaluation order, and add `from_country` to account for the sender's country.\n\nDefault coverage varies by country. If a country has no default rules, the service does not recognize keywords, send replies, or record opt-outs there. You can add `custom` keywords for that country. Opt-out, opt-in, and help rules require default coverage.\n","tags":["sms-keyword-rules"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"sms_keyword_rules.list","parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"country","in":"query","required":false,"description":"Keep only rules that apply in this country, as an ISO 3166-1 alpha-2 code. Omit for every country the default catalog covers, plus your own rules.\n","schema":{"type":"string","minLength":2,"maxLength":2,"example":"NL"}},{"name":"number","in":"query","required":false,"description":"Keep only the rules that apply to this number of yours, in E.164 format or as a short code, ordered the way they are applied to an inbound message.\n","schema":{"type":"string","minLength":1,"example":"+18005551234"}},{"name":"from_country","in":"query","required":false,"description":"The country a sender is messaging from, as an ISO 3166-1 alpha-2 code. Use it with `number` to see what someone in that country gets, which can differ from what a local sender gets. Ignored without `number`.\n","schema":{"type":"string","minLength":2,"maxLength":2,"example":"CA"}},{"name":"operation","in":"query","required":false,"description":"Keep only rules for this operation. Omit for all of them.","schema":{"$ref":"#/components/schemas/SMSKeywordOperation"}},{"name":"scope","in":"query","required":false,"description":"Keep only default rules (`system`) or only the rules you created (`workspace`). Omit for both.\n","schema":{"$ref":"#/components/schemas/SMSKeywordRuleScope"}}],"responses":{"200":{"description":"The keyword rules that apply to your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeywordRuleList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"post":{"operationId":"createSMSKeywordRule","summary":"Create an SMS keyword rule","description":"Creates a workspace keyword rule. Use it to replace the default opt-out, opt-in, or help reply for one country, or to add a `custom` keyword.\n\nYour rule takes precedence over the default for the same country and keeps default keywords unless you add more. Opt-out and opt-in keywords cannot be assigned to another operation.\n","tags":["sms-keyword-rules"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"sms_keyword_rules.create","parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeywordRuleCreate"}}}},"responses":{"201":{"description":"The created rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeywordRule"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/keyword-rules/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"ID of the default or workspace keyword rule, as returned by the list operation.","schema":{"$ref":"#/components/schemas/SMSKeywordRuleID"}}],"get":{"operationId":"getSMSKeywordRule","summary":"Get an SMS keyword rule","description":"Returns one keyword rule, either one of Bird's defaults or one you created, including\nevery keyword that matches it and the reply it sends.\n","tags":["sms-keyword-rules"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"sms_keyword_rules.get","parameters":[{"$ref":"#/components/parameters/XWorkspaceId"}],"responses":{"200":{"description":"The keyword rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeywordRule"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"patch":{"operationId":"updateSMSKeywordRule","summary":"Update an SMS keyword rule","description":"Changes the reply or the added keywords of a rule you created. Bird's defaults cannot be\nchanged. To replace one, create a rule with the same operation and country and yours\ntakes precedence.\n\nWhat the rule applies to is fixed once created, so this changes the reply and the keywords\nonly.\n","tags":["sms-keyword-rules"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"sms_keyword_rules.update","parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeywordRuleUpdate"}}}},"responses":{"200":{"description":"The updated rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeywordRule"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"delete":{"operationId":"deleteSMSKeywordRule","summary":"Delete an SMS keyword rule","description":"Deletes a rule you created. Bird's default for that operation and country applies again straight away, so deleting an opt-out rule restores Bird's reply rather than switching opt-out off. Bird's defaults cannot be deleted.\n","tags":["sms-keyword-rules"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"sms_keyword_rules.delete","parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"The rule was deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/summary":{"get":{"operationId":"getSMSStatsSummary","summary":"Get aggregate outbound SMS statistics","description":"Returns one aggregate row for the requested period. It includes SMS lifecycle counts, delivery and failure rates, and processing, delivery, and total latency percentiles (`p50`, `p95`, and `p99`). Rows use send-time attribution, so recent periods can under-report `delivered` while delivery reports arrive.\n\nRate fields are `null` when their denominator is zero. For example, `delivery_rate` is `null` when no message was accepted.\n\n`from` and `to` must both be days or RFC 3339 instants. Day windows cover up to 365 days. Instant bounds round down to the hour and may span up to 720 hours. Mixing the forms returns `422`. Set `timezone` for local boundaries, one dimension filter at most, or `compare=previous_period` for the preceding equal-length window.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.summary","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window: a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The `timezone` parameter makes a calendar day local and rounds an instant down to the local hour. Omit `timezone` to use UTC. When `timezone` is set, a numeric UTC offset such as `+05:45` is rejected; use a calendar day or a `Z` (UTC) instant. This value must use the same form as `to`. When omitted, it defaults to 30 days before `to` for day windows or 168 hours (7 days) before `to` for hour windows.\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window: a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The `timezone` parameter makes a calendar day local and rounds an instant down to the local hour. Omit `timezone` to use UTC. When `timezone` is set, a numeric UTC offset is rejected; use a calendar day or a `Z` (UTC) instant. This value must use the same form as `from`. When omitted, it defaults to today for day windows or the current hour for hour windows in that timezone. Day windows may not exceed 365 days; hour windows may not exceed 720 hours (30 days).\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"originator","in":"query","required":false,"description":"Restrict the statistics to a single originator (the sender address messages were sent from). Mutually exclusive with the other dimension filters (`country`, `category`, `carrier`); only one may be set per request. Matches the message `from`.\n","schema":{"type":"string","minLength":1,"example":"BirdSMS"}},{"name":"country","in":"query","required":false,"description":"Restrict the statistics to a single destination country, as an ISO 3166-1 alpha-2 code. Mutually exclusive with the other dimension filters (`originator`, `category`, `carrier`); only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"US"}},{"name":"category","in":"query","required":false,"description":"Restrict the statistics to a single category. Mutually exclusive with the other dimension filters (`originator`, `country`, `carrier`); only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"transactional"}},{"name":"carrier","in":"query","required":false,"description":"Restrict the statistics to a single delivery carrier. Mutually exclusive with the other dimension filters (`originator`, `country`, `category`); only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"Verizon"}},{"name":"compare","in":"query","required":false,"description":"Set to `previous_period` to also include the same statistics for the immediately preceding window of equal length, plus the change between the two, so you can show \"+X% vs last period\" without a second request.\n","schema":{"$ref":"#/components/schemas/StatsComparePeriod"}}],"responses":{"200":{"description":"Aggregate summary for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsSummary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/daily":{"get":{"operationId":"getSMSStatsDaily","summary":"Get daily outbound SMS statistics","description":"Returns one row of SMS lifecycle counts per calendar day. Rows use send-time attribution, so a delivery confirmation is counted on the day when its message was accepted. Recent rows can under-report `delivered` while delivery reports arrive. Days without activity contain zero counts.\n\nRates and latency are whole-window aggregates available from the summary endpoint. Use the message detail endpoints for individual message status.\n\nA request may span up to 365 days; a longer window returns `422`. Set `timezone` for local calendar days instead of UTC.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.daily","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"originator","in":"query","required":false,"description":"Restrict the statistics to a single originator (the sender address messages were sent from). Mutually exclusive with the other dimension filters (`country`, `category`, `carrier`); only one may be set per request. Matches the message `from`.\n","schema":{"type":"string","minLength":1,"example":"BirdSMS"}},{"name":"country","in":"query","required":false,"description":"Restrict the statistics to a single destination country, as an ISO 3166-1 alpha-2 code. Mutually exclusive with the other dimension filters (`originator`, `category`, `carrier`); only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"US"}},{"name":"category","in":"query","required":false,"description":"Restrict the statistics to a single category. Mutually exclusive with the other dimension filters (`originator`, `country`, `carrier`); only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"transactional"}},{"name":"carrier","in":"query","required":false,"description":"Restrict the statistics to a single delivery carrier. Mutually exclusive with the other dimension filters (`originator`, `country`, `category`); only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"Verizon"}}],"responses":{"200":{"description":"Daily aggregate stats for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/hourly":{"get":{"operationId":"getSMSStatsHourly","summary":"Get hourly outbound SMS statistics","description":"Returns one row of SMS lifecycle counts per hour. Rows use send-time attribution, so a delivery confirmation is counted in the hour when its message was accepted. Recent rows can under-report `delivered` while delivery reports arrive.\n\nRates and latency are whole-window aggregates available from the summary endpoint. Set `timezone` for local hours instead of UTC, including zones with sub-hour offsets.\n\nA request may span up to 30 days (720 rows). `from` and `to` are ISO 8601 instants; each bound rounds down to the hour and remains inclusive. An excessive or reversed window returns `422`. Use the daily endpoint for longer ranges.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.hourly","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start of the window (ISO 8601 instant), rounded down to the start of its hour and included. The boundary uses the local hour when `timezone` is set and the UTC hour otherwise. When `timezone` is set, a numeric UTC offset such as `+05:45` is rejected; use a `Z` (UTC) instant. Defaults to 7 days before `to` when omitted.","schema":{"type":"string","format":"date-time","example":"2026-05-25T00:00:00Z"}},{"name":"to","in":"query","required":false,"description":"End of the window (ISO 8601 instant), rounded down to the start of its hour and included. The boundary uses the local hour when `timezone` is set and the UTC hour otherwise, so both bounds are inclusive. When `timezone` is set, a numeric UTC offset is rejected; use a `Z` (UTC) instant. Defaults to the current hour when omitted. The window may not exceed 30 days (720 hours).","schema":{"type":"string","format":"date-time","example":"2026-05-25T23:59:59Z"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"originator","in":"query","required":false,"description":"Restrict the statistics to a single originator (the sender address messages were sent from). Mutually exclusive with the other dimension filters (`country`, `category`, `carrier`); only one may be set per request. Matches the message `from`.\n","schema":{"type":"string","minLength":1,"example":"BirdSMS"}},{"name":"country","in":"query","required":false,"description":"Restrict the statistics to a single destination country, as an ISO 3166-1 alpha-2 code. Mutually exclusive with the other dimension filters (`originator`, `category`, `carrier`); only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"US"}},{"name":"category","in":"query","required":false,"description":"Restrict the statistics to a single category. Mutually exclusive with the other dimension filters (`originator`, `country`, `carrier`); only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"transactional"}},{"name":"carrier","in":"query","required":false,"description":"Restrict the statistics to a single delivery carrier. Mutually exclusive with the other dimension filters (`originator`, `country`, `category`); only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"Verizon"}}],"responses":{"200":{"description":"Hourly aggregate stats for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/originators":{"get":{"operationId":"getSMSStatsByOriginator","summary":"Get outbound SMS statistics by originator","description":"Returns aggregate delivery and latency stats grouped by originator (the sender address messages were sent from) for the requested period. Rows are ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, hard maximum 200). Use this to compare sending performance across the senders you dispatch from.\n\nRows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive.\n\nThe maximum window is 365 days; requesting a longer range returns 422.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.by_originator","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any lifecycle count or derived rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `accepted`.\n","schema":{"$ref":"#/components/schemas/SMSStatsSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of originator rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-originator breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsByOriginatorResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/countries":{"get":{"operationId":"getSMSStatsByCountry","summary":"Get outbound SMS statistics by country","description":"Returns aggregate delivery and latency stats grouped by destination country for the requested period. Rows are ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, hard maximum 200). Use this to compare sending performance across the countries you send to.\n\nRows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive.\n\nThe maximum window is 365 days; requesting a longer range returns 422.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.by_country","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any lifecycle count or derived rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `accepted`.\n","schema":{"$ref":"#/components/schemas/SMSStatsSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of country rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-country breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsByCountryResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/categories":{"get":{"operationId":"getSMSStatsByCategory","summary":"Get outbound SMS statistics by category","description":"Returns aggregate delivery and latency stats grouped by message category for the requested period. Rows are ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, hard maximum 200). Use this to compare sending performance across the categories you send under.\n\nRows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive.\n\nThe maximum window is 365 days; requesting a longer range returns 422.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.by_category","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any lifecycle count or derived rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `accepted`.\n","schema":{"$ref":"#/components/schemas/SMSStatsSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of category rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-category breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsByCategoryResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/error-codes":{"get":{"operationId":"getSMSStatsByErrorCode","summary":"Get outbound SMS statistics by error code","description":"Returns aggregate delivery and latency statistics grouped by normalized failure reason for the requested period. The grouping key matches the `error_code` filter on the message list, so each row maps directly to the affected messages rather than a raw carrier code. Rows are ranked by the `sort` metric (default `failed`) descending and capped at the requested `limit` (default 50, hard maximum 200).\n\nRows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive. The maximum window is 365 days; requesting a longer range returns 422.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.by_error_code","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Defaults to `failed`. Only lifecycle counts are sortable; this breakdown has no rates.\n","schema":{"$ref":"#/components/schemas/SMSStatsLifecycleSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of error-code rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-error-code breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsByErrorCodeResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/carriers":{"get":{"operationId":"getSMSStatsByCarrier","summary":"Get outbound SMS statistics by carrier","description":"Returns aggregate delivery and latency stats grouped by delivery carrier for the requested period. Rows are ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, hard maximum 200). Use this to compare delivery performance across the carriers that handled your messages.\n\nRows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive.\n\nThe maximum window is 365 days; requesting a longer range returns 422.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.by_carrier","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any lifecycle count or derived rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `accepted`.\n","schema":{"$ref":"#/components/schemas/SMSStatsSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of carrier rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-carrier breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsByCarrierResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/tags":{"get":{"operationId":"getSMSStatsByTag","summary":"Get outbound SMS statistics by tag","description":"Returns delivery and latency statistics grouped by tag (`name:value`). Rows sort by the selected metric in descending order and are capped by `limit`. The default sort is `accepted`; the default limit is 50 and the maximum is 200.\n\nOnly tagged messages appear. A message with several tags is counted once under each, so rows do not sum to the period total.\n\nRows use send-time attribution, so recent periods can under-report `delivered` while delivery reports arrive. A request may span up to 365 days; a longer window returns `422`.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.by_tag","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any lifecycle count or derived rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `accepted`.\n","schema":{"$ref":"#/components/schemas/SMSStatsSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of tag rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-tag breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsByTagResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/statuses":{"get":{"operationId":"getSMSStatsByStatus","summary":"Get outbound SMS statistics by status","description":"Returns one row per lifecycle status with activity in the requested period, ordered by count descending. The statuses are `accepted`, `sent`, `delivered`, `undelivered`, `failed`, `rejected`, and `expired`.\n\nRows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving. With at most seven statuses, this breakdown has no cap, ranking, limit, or trend parameters.\n\nThe maximum window is 365 days; requesting a longer range returns 422.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.by_status","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"}],"responses":{"200":{"description":"Per-status breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsByStatusResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/inbound/summary":{"get":{"operationId":"getSMSInboundStatsSummary","summary":"Get aggregate inbound SMS statistics","description":"Returns the total number of messages your numbers received over the period, using the time the carrier received each message.\n\nThe response contains only a count because a received message has one state. Use the outbound statistics endpoints for delivery rates and latency data about messages you send.\n\nThe maximum window is 365 days; a longer range returns 422. Set `timezone` to resolve the period against your local calendar instead of UTC.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.inbound.summary","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window, either a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The form you use selects the grain the total is resolved at. Interpreted in `timezone`, or in UTC when `timezone` is omitted. Must use the same form as `to`. A numeric UTC offset (for example `+05:45`) is rejected when `timezone` is set; pass a calendar day or a `Z` instant instead. Defaults to 30 days before `to` for day windows, or 168 hours before `to` for hour windows.\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window, in the same form as `from`. Defaults to today, or the current hour for an hour window. A day window may not exceed 365 days and an hour window 720 hours.\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"compare","in":"query","required":false,"description":"Set to `previous_period` to include the received-message count for the immediately preceding window of equal length. The response also includes the change between the two, so you can show \"+X% vs last period\" without a second request.\n","schema":{"$ref":"#/components/schemas/StatsComparePeriod"}}],"responses":{"200":{"description":"Total received messages for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSInboundStatsSummaryResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/inbound/daily":{"get":{"operationId":"getSMSInboundStatsDaily","summary":"Get daily inbound SMS statistics","description":"Returns the number of messages your numbers received, one row per calendar day. Rows use the time the carrier received each message, and days with no messages contain a zero count.\n\nEach row contains only a count because a received message has one state. Use the outbound statistics endpoints for lifecycle and delivery-latency data about messages you send.\n\nThe maximum window is 365 days; a longer range returns 422. Set `timezone` to bucket rows by your local calendar day instead of UTC.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.inbound.daily","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"}],"responses":{"200":{"description":"Received-message counts per day for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSInboundStatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/inbound/hourly":{"get":{"operationId":"getSMSInboundStatsHourly","summary":"Get hourly inbound SMS statistics","description":"Returns the number of messages your numbers received, one row per hour. Rows use the time the carrier received each message, and hours with no messages contain a zero count.\n\nEach row contains only a count because a received message has one state. Use the outbound statistics endpoints for lifecycle and delivery-latency data about messages you send.\n\nThe maximum window is 720 hours; a longer range returns 422. Set `timezone` to bucket rows by your local hour instead of UTC.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.inbound.hourly","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start of the window (inclusive), an RFC 3339 instant truncated to the hour. Defaults to 7 days (168 hours) before `to` when omitted.","schema":{"type":"string","format":"date-time","example":"2026-05-01T00:00:00Z"}},{"name":"to","in":"query","required":false,"description":"End of the window (inclusive), an RFC 3339 instant truncated to the hour. Defaults to the current hour when omitted. Window may not exceed 720 hours. A numeric UTC offset (for example `+05:45`) is rejected when `timezone` is set; pass a calendar day or a `Z` instant instead.","schema":{"type":"string","format":"date-time","example":"2026-05-25T23:00:00Z"}},{"$ref":"#/components/parameters/StatsTimezone"}],"responses":{"200":{"description":"Received-message counts per hour for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSInboundStatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/inbound/countries":{"get":{"operationId":"getSMSInboundStatsByCountry","summary":"Get inbound SMS statistics by country","description":"Returns the number of messages your numbers received, grouped by the receiving number's country. Rows are ranked by volume, highest first, and use the time the carrier received each message.\n\nEach row contains only a count because a received message has one state. The maximum window is 365 days; a longer range returns `422`. Set `timezone` to resolve the period against your local calendar.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.inbound.by_country","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"limit","in":"query","required":false,"description":"Maximum rows to return, ranked by volume. Defaults to 50; the maximum is 200, and asking for more returns 422 rather than silently returning fewer.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50,"example":50}}],"responses":{"200":{"description":"Received-message volume by country for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSInboundStatsByCountryResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/inbound/operators":{"get":{"operationId":"getSMSInboundStatsByOperator","summary":"Get inbound SMS statistics by operator","description":"Returns the number of messages your numbers received, grouped by the sender's mobile operator. Rows are ranked by volume, highest first, and use the time the carrier received each message. Operators are identified by MCC-MNC when the carrier reports it.\n\nEach row contains only a count because a received message has one state. Messages without a reported sending operator are excluded, so the rows can sum to less than the summary total.\n\nThe maximum window is 365 days; a longer range returns `422`. Set `timezone` to resolve the period against your local calendar.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.inbound.by_operator","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"limit","in":"query","required":false,"description":"Maximum rows to return, ranked by volume. Defaults to 50; the maximum is 200, and asking for more returns 422 rather than silently returning fewer.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50,"example":50}}],"responses":{"200":{"description":"Received-message volume by operator for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSInboundStatsByOperatorResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/sms/stats/inbound/numbers":{"get":{"operationId":"getSMSInboundStatsByNumber","summary":"Get inbound SMS statistics by number","description":"Returns how many messages each of your numbers received. Rows are ranked by volume, highest first, and use the time the carrier received each message.\n\nEach row contains only a count because a received message has one state. The maximum window is 365 days; a longer range returns `422`. Set `timezone` to resolve the period against your local calendar.\n","tags":["sms-stats"],"x-audiences":["public","command"],"x-snippet-key":"sms.stats.inbound.by_number","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"limit","in":"query","required":false,"description":"Maximum rows to return, ranked by volume. Defaults to 50; the maximum is 200, and asking for more returns 422 rather than silently returning fewer.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50,"example":50}}],"responses":{"200":{"description":"Received-message volume by number for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSInboundStatsByNumberResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/lookup/phone-number":{"post":{"operationId":"createPhoneNumberLookup","x-snippet-key":"lookup.phone_number","summary":"Create a phone number lookup","description":"Returns the number's serving and issuing networks, porting state, country, and line type. The baseline fields are included in each lookup. Request additional `type` blocks for classification, presence, roaming, SIM-swap, porting-history, or credibility data. Each block reports its own `status`; only blocks with an `ok` status incur an additional charge.\n\nThis form keeps the number out of the URL. The [URL form](/docs/api/reference/get-phone-number-lookup) performs the same lookup but cannot use an idempotency key. With this form, reuse an `Idempotency-Key` to return the stored result without another lookup or charge.\n","tags":["lookup"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberLookupRequest"}}}},"responses":{"200":{"description":"Available network and number-intelligence information.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberLookup"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/lookup/phone-number/{number}":{"get":{"operationId":"getPhoneNumberLookup","x-snippet-key":"none","summary":"Get a phone number lookup by URL","description":"Performs the same lookup as [Create a phone number lookup](/docs/api/reference/create-phone-number-lookup), with the number in the URL. The response includes the number's serving and issuing networks, porting state, country, and line type. Repeat `type` to request additional blocks, such as `?type=classification&type=score`; only blocks with an `ok` status incur an additional charge.\n\nBecause the number is in the URL, it can appear in proxies, access logs, and browser history. This form does not accept an `Idempotency-Key`; each retry performs and charges for another lookup.\n","tags":["lookup"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"number","in":"path","required":true,"description":"The number to look up, in international format. The leading `+` is optional, and `00` works in its place, so `31612345678` and `+31612345678` are the same number, with nothing to percent-encode. If you do send the `+`, percent-encode it as `%2B` when your client does not do that for you.","schema":{"type":"string","minLength":2,"maxLength":20}},{"name":"type","in":"query","required":false,"description":"An additional data block to request. Repeat the parameter for multiple blocks; each block is billed separately only when its status is `ok`.","schema":{"type":"array","items":{"$ref":"#/components/schemas/LookupProperty"}}}],"responses":{"200":{"description":"Available network and number-intelligence information.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberLookup"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make"]}},"/v1/lookup/email":{"post":{"operationId":"createEmailLookup","x-snippet-key":"lookup.email","summary":"Create an email address lookup","description":"Returns a deliverability `result`, a `delivery_confidence` score, address characteristics, an undeliverable `reason`, and a suggested correction when available. `result` and `reason` are open vocabularies. Handle unknown values and use `delivery_confidence` as the stable fallback. Each completed lookup incurs the same charge regardless of its result.\n\nThis form keeps the address out of the URL. The [URL form](/docs/api/reference/get-email-lookup) performs the same lookup but cannot use an idempotency key. With this form, reuse an `Idempotency-Key` to return the stored result without another lookup or charge.\n","tags":["lookup"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailLookupRequest"}}}},"responses":{"200":{"description":"Available deliverability information about the address.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailLookup"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/lookup/email/{address}":{"get":{"operationId":"getEmailLookup","x-snippet-key":"none","summary":"Get an email address lookup by URL","description":"Performs the same deliverability lookup as [Create an email address lookup](/docs/api/reference/create-email-lookup), with the address in the URL. The response includes a result, confidence score, address characteristics, failure reason, and suggested correction when available. Treat unknown `result` and `reason` values as valid additions.\n\nBecause the address is in the URL, it can appear in proxies, access logs, and browser history. This form does not accept an `Idempotency-Key`; each retry performs and charges for another lookup.\n","tags":["lookup"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"address","in":"path","required":true,"description":"The email address to look up. Percent-encode it, because a local part may legally contain characters a URL path reads as structure. The `@` itself is safe either way.","schema":{"type":"string","minLength":3,"maxLength":254}}],"responses":{"200":{"description":"Available deliverability information about the address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailLookup"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make"]}},"/v1/verify/verifications":{"post":{"operationId":"createVerification","summary":"Create a verification","description":"Creates a verification and sends the recipient a one-time passcode. Provide an email address, a phone number, or both in `to`. The service sends over one channel at a time and moves to the next planned channel if delivery fails.\n\nCalling this again for the same recipient reuses the verification in progress. During the resend cooldown, it returns the current state without sending, so nothing is charged and no send budget is spent. After the cooldown, it sends a fresh passcode, and that send draws on the recipient's hourly send cap exactly as a new verification does, so repeated resends can exhaust the cap and return `429` for the rest of that rolling hour. [Abuse guardrails](/docs/guides/verify/sending-verifications#abuse-guardrails) gives the figures.\n\nThe `200` response contains the current state, never the passcode. Submit the recipient's passcode with [Check a verification](/docs/api/reference/create-verification-check) before `expires_at`. An invalid recipient returns `422`; exceeding the send rate limit returns `429`.\n","tags":["verify-verifications"],"x-audiences":["public","command"],"x-snippet-key":"verify.verifications.create","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationCreateRequest"},"examples":{"onboarding-verify-create":{"summary":"The first verification from the dashboard's onboarding step","value":{"to":{"email":"user@example.com"}}}}}}},"responses":{"200":{"description":"The verification's current state, whether newly opened or reused.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Verification"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/verify/verifications/check":{"post":{"operationId":"createVerificationCheck","summary":"Create a verification passcode check","description":"Checks a passcode for a recipient and returns the outcome together with the verification's current state. Identify the verification by the same `to` used to create it; you do not need to store a verification ID.\n\nA wrong or expired passcode returns `200 OK` with `success: false` and a `reason` such as `incorrect_code` or `expired`. `success: true` means the verification is complete. Each verification reports its final outcome once and cannot be checked again.\n\nAn error status is returned only when the check cannot be evaluated. A `404\nE13000` means no active verification matched the recipient: either none\nexists for it, or the most recent one is already resolved as verified,\nexpired, or out of attempts. One code covers all of those, so a `404` is not\nevidence the recipient failed to verify. Treat your own record of an earlier\n`success: true` as the outcome, and create a new verification only if the\nrecipient still needs to verify. A `422` indicates an invalid recipient. A\n`429` means passcodes for a recipient are being checked too quickly.\n","tags":["verify-verifications"],"x-audiences":["public","command"],"x-snippet-key":"verify.verifications.check","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationCheckRequest"},"examples":{"onboarding-verify-check":{"summary":"Checking the passcode from the dashboard's onboarding step","value":{"to":{"email":"user@example.com"},"code":"123456"}}}}}},"responses":{"200":{"description":"The check outcome and the verification's current state.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationCheckResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/verify/verifications/next-channel":{"post":{"operationId":"createVerificationNextChannel","summary":"Create the next verification channel attempt","description":"Advances an in-progress verification to the next channel in its plan and sends a fresh passcode there. Identify the verification by the same `to` recipient used to create it; no verification ID is required.\n\nThe send bypasses the resend cooldown and does not draw on the recipient's hourly send cap; what bounds it is the channel plan, since each call advances by at most one channel. Passcodes sent earlier remain valid. The response sets `last_channel` to the most recent completed send. Concurrent requests each advance the plan by at most one channel and return committed state.\n\nA recipient with no in-progress verification returns `404 E13000`, whether none was ever created or the most recent one is already resolved. A recipient who has already verified is in that set, so a `404` here is not evidence they still need verifying, and creating another verification would send a passcode they no longer need. A plan with no further channel returns `422 NoNextChannel`; create the verification again to resend on the current channel. If every remaining channel fails, the operation returns `422 NoAvailableChannel`. Requests that exceed the send rate limit return `429`.\n","tags":["verify-verifications"],"x-audiences":["public","command"],"x-snippet-key":"verify.verifications.next_channel","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationNextChannelRequest"},"example":{"to":{"phone_number":"+15551234567"}}}}},"responses":{"200":{"description":"Verification state after the advance. `last_channel` identifies the most recent completed send.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Verification"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/whatsapp/messages":{"get":{"operationId":"listWhatsAppMessages","x-snippet-key":"whatsapp.list","summary":"List WhatsApp messages","description":"Returns the workspace's WhatsApp messages as a cursor-paginated list,\nnewest first, outbound and inbound alike. Each message carries the one\ncontent object it was built from: `template`, or free-form `text`,\n`image`, `video`, `audio`, `sticker`, `document`, `location`,\n`interactive` or `contact_cards`. An inbound message carries\n`interactive_reply` when the contact tapped a reply button or a list row,\non an interactive message or on a template's quick reply. An inbound\nmessage whose content WhatsApp models and we do not carries `unsupported`\ninstead, naming the type rather than reading back empty.\nFilter by direction, status, recipient (`to`), sender (`from`),\nbusiness-scoped user ID (`bsuid`), group (`group_id`), template category,\ntag, or creation\ntime. `to` and `from` name the same ends of the message the response\ndoes, and each accepts an E.164 phone number or a business-scoped user\nID. Pair either with `direction` to search a single side of the message.\nNeither matches a group, so `group_id` is what narrows the list to one\ngroup's messages.\nPass the response's `next_cursor` back as\n`starting_after` to fetch the next page. To follow a single message's\ndelivery, use\n[Get a WhatsApp message](/docs/api/reference/get-whatsapp-message)\ninstead.\n\nMessages are retained for **30 days**. A `created_after` earlier than that\nis accepted and raised to the retention bound rather than rejected, so a\nwider window returns what is still retained instead of failing. There is no\nway to read messages older than the window.\n","tags":["whatsapp-messages"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"$ref":"#/components/parameters/CreatedAfter"},{"$ref":"#/components/parameters/CreatedBefore"},{"name":"status","in":"query","required":false,"description":"Filter by status. Repeat the parameter to match any of several statuses.","schema":{"type":"array","items":{"$ref":"#/components/schemas/WhatsAppMessageStatus"}}},{"name":"direction","in":"query","required":false,"description":"Filter by whether the business sent the message (`outbound`) or received it from the contact (`inbound`).\n","schema":{"$ref":"#/components/schemas/MessageDirection"}},{"name":"to","in":"query","required":false,"description":"Filter by recipient, exact match. The recipient is the contact on an outbound message and your business number on an inbound one, matching the `to` each message returns. Accepts an E.164 phone number, or a business-scoped user ID to name the contact. Only a contact is ever identified by a business-scoped user ID, so `to=<business-scoped user ID>` matches outbound messages only.\n","schema":{"type":"string","minLength":1,"example":"+15551234567"}},{"name":"from","in":"query","required":false,"description":"Filter by sender, exact match. The sender is your business number on an outbound message and the contact on an inbound one, matching the `from` each message returns. Accepts an E.164 phone number, or a business-scoped user ID to name the contact. Only a contact is ever identified by a business-scoped user ID, so `from=<business-scoped user ID>` matches inbound messages only.\n","schema":{"type":"string","minLength":1,"example":"+13124495648"}},{"name":"phone_number","in":"query","required":false,"deprecated":true,"description":"Deprecated: use `to` or `from` instead, which also match a business-scoped user ID. Filters by contact phone number (E.164 exact match), in either direction.\n","schema":{"type":"string","example":"+15551234567"}},{"name":"bsuid","in":"query","required":false,"description":"Filter by business-scoped user ID (Meta identifier), matching the contact in either direction. `to` and `from` also accept one, but each matches a single end of the message.\n","schema":{"type":"string","example":"NL.xxxx"}},{"name":"category","in":"query","required":false,"description":"Filter by category.","schema":{"$ref":"#/components/schemas/WhatsAppTemplateCategory"}},{"name":"group_id","in":"query","required":false,"description":"Filter by the WhatsApp group the message belongs to, in either direction: the group an outbound message was addressed to, or the group an inbound message arrived through. Matches the `group_id` on each message's `to`. It names one group, so there is no way to ask for the messages that belong to no group: omit it to list group and one-to-one messages together.\n","schema":{"$ref":"#/components/schemas/WhatsAppGroupID"}},{"$ref":"#/components/parameters/TagFilter"}],"responses":{"200":{"description":"Paginated list of WhatsApp messages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppMessageList"},"example":{"data":[{"id":"wam_01kya1b3xdq7fe8m2v5t9rncgs","direction":"inbound","from":{"phone_number":"+15550002222","bsuid":"US.AbC1","display_name":"Dana Reyes"},"to":{"phone_number":"+15550001111","group_id":"wag_01krdgeqcxet5s7t44vh8rt9mg"},"text":{"body":"Got it, thanks."},"status":"received","created_at":"2026-09-15T14:05:41Z"},{"id":"wam_01kya19eknftrs2s6p82asmvnh","direction":"outbound","from":{"phone_number":"+15550001111"},"to":{"group_id":"wag_01krdgeqcxet5s7t44vh8rt9mg"},"text":{"body":"The route sheet for Tuesday is up."},"status":"delivered","recipient_count":2,"delivered_count":2,"read_count":1,"created_at":"2026-09-15T14:03:10Z"}],"next_cursor":null,"prev_cursor":null,"refresh_cursor":"eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE0OjAzOjEwWlwiIiwiaSI6IjAxOTJmM2IxLTRjN2UtN2EyYi05ZDYxLThmM2E1YzJlN2I0MCJ9"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]},"post":{"operationId":"createWhatsAppMessage","x-snippet-key":"whatsapp.send","summary":"Send a WhatsApp message","description":"Sends one WhatsApp message to one recipient. The request carries exactly one\nkind of content: a message template, or free-form `text`, `image`, `video`,\n`audio`, `sticker`, `document`, `location`, `contact_cards` or\n`interactive`. A request carrying none is rejected with a `422`, and one\ncarrying more than one is too.\n\nA **template** is the only content WhatsApp delivers outside an open\ncustomer service window, so it is what starts a conversation. Name the\ntemplate, optionally pick its language variant, and fill its placeholders in\n`components`. A Bird-managed template selects its sender number from its\ncategory, so the request carries no `from`; a template your workspace\nauthored requires one. Browse your workspace's templates in the Bird\ndashboard.\n\n**Free-form content** is deliverable only inside an open 24-hour customer\nservice window, which the contact opens by messaging or calling you and\nresets each time they do it again. Bird tracks that window, so a send into a\nclosed one is refused with a `422` `WhatsAppServiceWindowClosed` before\nanything is created or charged;\nsend a template instead, which reopens the window once the contact replies.\nA window that closes between accept and dispatch still fails\nasynchronously, carrying `service_window_expired` on the message's\n`last_error`. Every free-form send requires `from`.\n\n**Interactive content** gives the recipient something to tap. `interactive`\nnames its kind in `type` and carries that kind's own field: reply `buttons`,\na `list` menu, a `cta_url` link button, or `cards` for a carousel;\n`location_request_message` and `request_contact_info` are each a single\nbutton asking the recipient for something, so `body_text` is the whole\nmessage. A tap on a reply button or a list row comes back as an inbound\nmessage carrying `interactive_reply`. The other kinds answer in their own\nshape: a `cta_url` link opens in the recipient's browser and sends nothing\nback, and the two request kinds come back as the thing they asked for, an\ninbound `location` or `contact_cards` message.\nInteractive content is free-form, so the customer service window and the\n`from` requirement above both apply.\n\n**Contact cards** share up to five contacts in one message. Each card's\n`name` needs `formatted_name` plus at least one other part, and a\n`phone_number` in E.164 earns that card a button opening a chat with it.\nContact cards are free-form too, so the same window and `from` rules apply.\n\n**A group send** addresses a WhatsApp group ID in `to` and carries no\n`from`: the group sends on its own number, and the response reports the\nfan-out.\n`recipient_count` is the group's membership when the send was accepted,\n`delivered_count` and `read_count` count against it, and `status` turns\n`delivered` only once every participant has it. WhatsApp delivers neither\ninteractive content nor an authentication template to a group, and a\nBird-managed template sends from a number no group is scoped to.\n\nSet `in_reply_to_message_id` to quote a message the contact sees above this\none, the way replying in the WhatsApp client does. Any content quotes, and\nthe quoted message must be one from this same conversation.\n\nThe `202` response is the accepted message, echoing the resolved content; it\nis not a delivery confirmation. Follow delivery with\n[Get a WhatsApp message](/docs/api/reference/get-whatsapp-message), the\nper-message timeline from\n[List events for a WhatsApp message](/docs/api/reference/list-whatsapp-message-events),\nor `whatsapp.*` webhook events.\n\nEach of these returns a `422`:\n\n- A template slug or language the catalogue does not stock.\n- Parameter values that do not match the template's declared placeholders.\n- A `from` this workspace cannot send from.\n- A recipient that is neither a valid phone number nor a business-scoped user ID.\n- Free-form content sent into a closed customer service window\n  (`WhatsAppServiceWindowClosed`).\n- Interactive content or an authentication template addressed to a group\n  (`WhatsAppGroupContentNotSupported`).\n\nA group `to` naming no group this workspace holds fails with a `404`\n`WhatsAppGroupNotFound`, and one whose group is not active with a `409`\n`WhatsAppGroupNotActive`. A send from a workspace with no wallet balance\nfails with a `402`.\n","tags":["whatsapp-messages"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppMessageSendRequest"},"examples":{"onboarding-whatsapp":{"summary":"The first send from the dashboard's onboarding step","value":{"to":"+15551234567","template":{"slug":"bird_delivery_update","components":[{"type":"body","parameters":[{"type":"text","name":"ref","text":"A1B2C3D4"},{"type":"text","name":"date","text":"10 Jul 2026"}]}]}}},"audio":{"summary":"A voice note or audio clip by URL","value":{"to":"+16505551234","from":"+13124495648","audio":{"url":"https://cdn.example.com/vm/9f2.ogg"}}},"documents":{"summary":"A PDF or other document by URL","value":{"to":"+16505551234","from":"+13124495648","document":{"url":"https://cdn.example.com/invoices/a1b2c3.pdf"}}},"images":{"summary":"An image by URL","value":{"to":"+16505551234","from":"+13124495648","image":{"url":"https://cdn.example.com/receipt.png"}}},"stickers":{"summary":"A WebP sticker by URL","value":{"to":"+16505551234","from":"+13124495648","sticker":{"url":"https://cdn.example.com/stickers/thumbs-up.webp"}}},"video":{"summary":"A video by URL","value":{"to":"+16505551234","from":"+13124495648","video":{"url":"https://cdn.example.com/unboxing.mp4"}}},"location":{"summary":"A pin the recipient can open in their maps app","value":{"to":"+16505551234","from":"+13124495648","location":{"latitude":37.7793,"longitude":-122.4193}}},"groups":{"summary":"A text message to every participant of a group","value":{"to":"wag_01krdgeqcxet5s7t44vh8rt9mg","text":{"body":"The route sheet for Tuesday is up."}}},"interactive-overview":{"summary":"Quick-reply buttons offering two replies","value":{"to":"+15551234567","from":"+13124495648","interactive":{"type":"button","body_text":"Your gardening workshop is scheduled for 9am tomorrow.","buttons":[{"type":"quick_reply","quick_reply":{"slug":"change-booking","text":"Change"}},{"type":"quick_reply","quick_reply":{"slug":"cancel-booking","text":"Cancel"}}]}}},"reply-buttons":{"summary":"A single quick-reply button","value":{"to":"+16505551234","from":"+13124495648","interactive":{"type":"button","body_text":"Your gardening workshop is scheduled for 9am tomorrow.","buttons":[{"type":"quick_reply","quick_reply":{"slug":"change-booking","text":"Change"}}]}}},"carousels":{"summary":"A carousel of product cards, each with its own button","value":{"to":"+16505551234","from":"+13124495648","interactive":{"type":"carousel","body_text":"Here are two of our latest arrivals, each under $25:","cards":[{"header":{"type":"image","url":"https://cdn.example.com/plants/blue-echeveria.jpeg"},"buttons":[{"type":"cta_url","cta_url":{"text":"Buy now","url":"https://shop.example.com/blue-echeveria"}}]},{"header":{"type":"image","url":"https://cdn.example.com/plants/zebra-haworthia.jpeg"},"buttons":[{"type":"cta_url","cta_url":{"text":"Buy now","url":"https://shop.example.com/zebra-haworthia"}}]}]}}},"contact-info-requests":{"summary":"A prompt asking the recipient to share their phone number","value":{"to":"+16505551234","from":"+13124495648","interactive":{"type":"request_contact_info","body_text":"To confirm your booking we need a number to reach you on. Tap below to share yours."}}},"cta-url-buttons":{"summary":"A button that opens a URL, with click tracking in the link","value":{"to":"+16505551234","from":"+13124495648","interactive":{"type":"cta_url","body_text":"Tap the button below to see the available dates.","cta_url":{"text":"See dates","url":"https://example.com/workshops?click_id=a1b2c3"}}}},"list-menus":{"summary":"A list menu the recipient picks one row from","value":{"to":"+16505551234","from":"+13124495648","interactive":{"type":"list","body_text":"Which shipping option do you prefer?","list":{"button_text":"Shipping options","sections":[{"title":"As soon as possible","rows":[{"slug":"priority_express","text":"Priority Mail Express"}]}]}}}},"location-requests":{"summary":"A prompt asking the recipient to share their location","value":{"to":"+16505551234","from":"+13124495648","interactive":{"type":"location_request_message","body_text":"Let's start with your pickup. Share your current location, or type an address instead."}}},"template-authentication":{"summary":"An authentication template carrying a one-time passcode","value":{"to":"+14155550100","template":{"slug":"bird_otp","language":"en","components":[{"type":"body","parameters":[{"type":"text","text":"481920"}]}]}}},"template-marketing":{"summary":"A marketing template with an image header and a coupon button","value":{"to":"+16505551234","from":"+13125550101","template":{"slug":"summer_sale","language":"en","components":[{"type":"header","parameters":[{"type":"image","url":"https://cdn.example.com/banners/summer.png"}]},{"type":"body","parameters":[{"type":"text","name":"first_name","text":"Pablo"}]},{"type":"button","parameters":[{"type":"text","text":"SUMMER25"}]}]}}},"template-utility":{"summary":"A utility template confirming an order","value":{"to":"+16505551234","template":{"slug":"bird_order_confirmation","language":"en","components":[{"type":"body","parameters":[{"type":"text","name":"ref","text":"A1B2C3D4"},{"type":"text","name":"amount","text":"USD 49.99"}]}]}}},"plain-text":{"summary":"A plain text message, the smallest free-form send","value":{"to":"+16505551234","from":"+13124495648","text":{"body":"Your driver is 2 minutes away."}}},"free-form-text":{"summary":"A free-form reply inside an open customer service window","value":{"to":"+31612345678","from":"+13124495648","text":{"body":"Your order shipped: https://example.com/track/A1B2C3","preview_url":true}}},"interactive-buttons":{"summary":"Reply buttons quoting the message that asked for them","value":{"to":"+31612345678","from":"+13124495648","in_reply_to_message_id":"wam_01kya19eknftrs2s6p82asmvnh","interactive":{"type":"button","body_text":"Your driver is 2 minutes away. Still at the same address?","buttons":[{"type":"quick_reply","quick_reply":{"slug":"same_address","text":"Yes, same address"}},{"type":"quick_reply","quick_reply":{"slug":"change_address","text":"Change address"}}]}}}}}}},"responses":{"202":{"description":"Message accepted for asynchronous delivery.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppMessage"},"example":{"id":"wam_01kya19eknftrs2s6p82asmvnh","direction":"outbound","from":{"phone_number":"+15550001111"},"to":{"group_id":"wag_01krdgeqcxet5s7t44vh8rt9mg"},"text":{"body":"The route sheet for Tuesday is up."},"status":"accepted","recipient_count":2,"delivered_count":0,"read_count":0,"created_at":"2026-09-15T14:03:10Z"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/whatsapp/messages/{message_id}":{"get":{"operationId":"getWhatsAppMessage","x-snippet-key":"whatsapp.get","summary":"Get a WhatsApp message","description":"Returns a single WhatsApp message: its current delivery status, per-stage timestamps (`sent_at`, `delivered_at`, `read_at`), and failure detail when it failed. It carries the one content object it was built from: `template`, or free-form `text`, `image`, `video`, `audio`, `sticker`, `document`, `location`, `interactive` or `contact_cards`. An inbound message carries `interactive_reply` when the contact tapped a reply button or a list row. An inbound message whose content WhatsApp models and we do not carries `unsupported` instead, naming the type rather than reading back empty. The `status` advances asynchronously as delivery progresses, so poll this endpoint (or subscribe to `whatsapp.*` webhook events) after a send to confirm delivery. For the per-event timeline, use [List events for a WhatsApp message](/docs/api/reference/list-whatsapp-message-events) instead.\n","tags":["whatsapp-messages"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the message, as returned in the send response's `id` field.","schema":{"$ref":"#/components/schemas/WhatsAppMessageID"}}],"responses":{"200":{"description":"WhatsApp message object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppMessage"},"example":{"id":"wam_01kya19eknftrs2s6p82asmvnh","direction":"outbound","from":{"phone_number":"+15550001111"},"to":{"group_id":"wag_01krdgeqcxet5s7t44vh8rt9mg"},"text":{"body":"Tuesday's route sheet is up."},"status":"delivered","recipient_count":2,"delivered_count":2,"read_count":2,"created_at":"2026-09-15T14:03:10Z","sent_at":"2026-09-15T14:03:11Z","delivered_at":"2026-09-15T14:03:12Z","read_at":"2026-09-15T14:04:02Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/whatsapp/messages/{message_id}/read":{"post":{"operationId":"sendWhatsAppReadReceipt","summary":"Mark a WhatsApp message as read","description":"Marks an inbound WhatsApp message as read, showing the contact the blue\nticks. WhatsApp also marks every earlier message in that conversation read.\n\nPass `typing_indicator: true` to show a typing indicator as well. WhatsApp\nclears it when you send your next message, or after 25 seconds, whichever\ncomes first, and there is no call to clear it early, so ask for one only\nwhen you are about to reply. WhatsApp cannot show a typing indicator without\na read receipt, so both arrive together.\n\nThe acknowledgement is sent asynchronously: a `202` means Bird accepted the\nrequest, not that WhatsApp has shown it. Nothing reports back, because\nWhatsApp publishes no delivery, status or failure callback for an\nacknowledgement, so there is nothing to poll and no webhook event.\nRepeating the call is safe, and each call restarts the 25-second typing\nwindow. To refresh the indicator, send a fresh `Idempotency-Key` or none at\nall: a replayed key answers from the stored response without acknowledging\nanything again.\n\nOnly an inbound message can be marked read. WhatsApp allows this for 30\ndays after receipt, but Bird keeps the provider id a receipt needs for 15\ndays, so a message older than that answers `404`. A message Bird sent, or\none that never reached WhatsApp, answers `422`.\n","tags":["whatsapp-messages"],"x-audiences":["public","dashboard","command"],"x-snippet-key":"whatsapp.markRead","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the inbound message to acknowledge.","schema":{"$ref":"#/components/schemas/WhatsAppMessageID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppReadReceiptRequest"}}}},"responses":{"202":{"description":"Acknowledgement accepted for asynchronous delivery.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppReadReceipt"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/messages/{message_id}/events":{"get":{"operationId":"listWhatsAppMessageEvents","x-snippet-key":"whatsapp.listEvents","summary":"List events for a WhatsApp message","description":"Returns a WhatsApp message's lifecycle events in chronological order, one entry per delivery transition (`whatsapp.accepted`, `whatsapp.sent`, `whatsapp.delivered`, `whatsapp.read`, `whatsapp.failed`). The timeline is bounded and returned in full, so this list is not paginated; an unknown message ID returns `404`. For the message's current state in a single field, use [Get a WhatsApp message](/docs/api/reference/get-whatsapp-message) instead.\n","tags":["whatsapp-messages"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the message, as returned in the send response's `id` field.","schema":{"$ref":"#/components/schemas/WhatsAppMessageID"}},{"name":"type","in":"query","required":false,"description":"Keep only events of this exact type (for example `whatsapp.delivered` or `whatsapp.failed`). Omit for the full timeline.\n","schema":{"$ref":"#/components/schemas/WhatsAppEventType"}}],"responses":{"200":{"description":"Event timeline for this WhatsApp message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppEventList"},"example":{"data":[{"id":"ev_01kya19f8p2hs5w1y7k4cmqrtv","type":"whatsapp.sent","occurred_at":"2026-09-15T14:03:11Z"},{"id":"ev_01kya19f2m8xqe4v0t6r3bnpcd","type":"whatsapp.delivered","occurred_at":"2026-09-15T14:03:12Z","recipient":{"phone_number":"+15550002222","bsuid":"US.AbC1"}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/whatsapp/messages/{message_id}/media/{media_id}":{"parameters":[{"name":"message_id","in":"path","required":true,"description":"WhatsApp message ID.","schema":{"$ref":"#/components/schemas/WhatsAppMessageID"}},{"name":"media_id","in":"path","required":true,"description":"Media ID, as returned in `id` on the message's content object.","schema":{"$ref":"#/components/schemas/WhatsAppFileID"}}],"get":{"operationId":"getWhatsAppMessageMedia","summary":"Get a WhatsApp message's media","description":"Redirects to a short-lived URL for the media on a received WhatsApp message.\nInbound media is stored because WhatsApp's own URL is not fetchable\nwithout our credentials; this endpoint is what the `url` on the message's\n`image`, `video`, `audio`, `sticker` or `document` points at. The bytes\nlive in object storage and are served straight from there, so they never\ntransit the API.\n\nThe response is a `302` whose `Location` is that pre-authorized storage URL,\nvalid for 15 minutes; your client must follow redirects. The `Authorization`\nheader must be absent from the request that fetches that URL: a client that\nattaches credentials centrally, at its transport, interceptor or middleware\nlayer rather than per request, re-adds the header on every hop including the\nredirect, so the storage URL must be fetched with a client that carries\nnone.\n\nMedia is kept for 30 days after the message is received, and the message\nitself is kept longer. A message older than that still lists its media's\n`mime_type` and `caption`, and this operation returns `410` once the bytes\nhave expired. Outbound messages have no media to serve.\n","tags":["whatsapp-messages"],"x-audiences":["public","command"],"x-snippet-key":"whatsapp.messages.media","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"302":{"description":"Redirect to a storage URL valid for 15 minutes.","headers":{"Location":{"description":"The storage URL to fetch the media from.","schema":{"type":"string","format":"uri"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/messages/{message_id}/reaction":{"put":{"operationId":"upsertWhatsAppMessageReaction","summary":"React to a WhatsApp message","description":"Places an emoji reaction on a message this workspace received, the same way\ntapping and holding a message in WhatsApp does.\n\nYou hold at most one reaction per message, so this replaces your existing\none rather than adding another. To take a reaction back entirely, delete it.\n\nThe `202` means the reaction was accepted, not that WhatsApp applied it.\nReactions carry no delivery or read receipt, so the furthest one gets is\nsent. Read the message's `reactions` for what currently stands, or\n[List reaction events for a WhatsApp message](/docs/api/reference/list-whatsapp-message-reaction-events)\nfor what became of each change, including one WhatsApp refused.\n\nEach of these returns a `422`:\n\n- A message this workspace sent. This endpoint places reactions on messages\n  the contact sent; reacting to your own outbound message is not supported.\n- More than one emoji, since WhatsApp takes exactly one.\n\nA message Bird can no longer resolve returns a `404` instead. WhatsApp\naccepts a reaction on a message up to 30 days old, but Bird keeps the\nprovider id a reaction needs for **15 days**, so that is the practical age\nlimit. The message and its reaction log stay readable for 30; only the id\na placement needs is gone.\n\nReacting to a message received in a group addresses the group, so the\ngroup's own state applies: a group this workspace no longer holds returns\na `404` `WhatsAppGroupNotFound`, and one that is not active a `409`\n`WhatsAppGroupNotActive`.\n\nReactions are not charged for.\n","tags":["whatsapp-messages"],"x-audiences":["public","dashboard","command"],"x-snippet-key":"whatsapp.reaction.set","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the message to react to, as returned in the `id` field of the message. Must be a message this workspace received.\n","schema":{"$ref":"#/components/schemas/WhatsAppMessageID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppReactionUpsert"},"examples":{"thumbs-up":{"summary":"Acknowledge a message with a thumbs up","value":{"emoji":"👍"}}}}}},"responses":{"202":{"description":"Reaction accepted; WhatsApp applies it asynchronously.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppReactionAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]},"delete":{"operationId":"deleteWhatsAppMessageReaction","summary":"Remove your reaction from a WhatsApp message","description":"Takes back the reaction this workspace placed on a message, the same way\ntapping your own reaction in WhatsApp does. Only your own reaction can be\nremoved; one the contact placed is theirs to take back.\n\nRemoving a reaction from a message you have not reacted to changes nothing\nand still answers `202`, so a repeated call is safe.\n\nA removal travels the same path as placing a reaction, so it is refused on\nthe same grounds. A message this workspace sent returns a `422`: it could\nnever have carried a reaction of ours to remove, since placing one there is\nnot supported either.\n\nA message Bird can no longer resolve returns a `404` instead, on the same\n15-day retention a placement is bounded by. A reaction on a message\nreceived in a group addresses the group, so a group this workspace no\nlonger holds returns a `404` `WhatsAppGroupNotFound` and one that is not\nactive a `409` `WhatsAppGroupNotActive`, the same as placing one does.\n\nThe `202` is the removal accepted rather than applied. The reaction stays in\nthe message's `reactions` until WhatsApp confirms the removal and then drops\nout, so one on its way off reads as still standing rather than disappearing\nbefore it is gone.\n","tags":["whatsapp-messages"],"x-audiences":["public","dashboard","command"],"x-snippet-key":"whatsapp.reaction.remove","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the message to take your reaction off, as returned in the `id` field of the message.\n","schema":{"$ref":"#/components/schemas/WhatsAppMessageID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"202":{"description":"Removal accepted. No body: the reaction is still standing at this point, and on a message this workspace never reacted to there is nothing to return. Read the message's `reactions` to see it go.\n","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/messages/{message_id}/reaction-events":{"get":{"operationId":"listWhatsAppMessageReactionEvents","summary":"List reaction events for a WhatsApp message","description":"Returns the changes made to this message's reactions as a cursor-paginated\nlist, newest first: each emoji placed, each one replaced by a different\nemoji, and each one taken back. Entries are never edited, so a contact who\nreacts, changes their mind and then removes it leaves three of them.\n\nOne case is missing rather than recorded. A reaction is matched to the\nmessage it was placed on through a provider id we keep for 15 days, while\nWhatsApp accepts a reaction on a message up to 30 days old, so one placed on\na message older than that cannot be matched and is recorded nowhere: not\nhere, and not in the message's `reactions`.\n\nUse this to show who reacted and when, or to find out what became of a\nreaction that never appeared: a `failed` or `rejected` entry carries the\nreason on `error`. For what currently stands on the message, read its\n`reactions`, which folds this log down to one entry per sender.\n\nPass the response's `next_cursor` back as `starting_after` to fetch the next\npage.\n\nReaction events are kept for **30 days**, counted from when the message they\nbelong to was accepted rather than from the reaction itself. They therefore\ngo at about the same time as the message, not 30 days after the last\nreaction on it.\n","tags":["whatsapp-messages"],"x-audiences":["public","dashboard","command"],"x-snippet-key":"whatsapp.reaction.listEvents","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"description":"ID of the message whose reactions to read, as returned in the `id` field of the message.\n","schema":{"$ref":"#/components/schemas/WhatsAppMessageID"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of reaction changes for this WhatsApp message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppReactionEventList"},"example":{"data":[{"id":"war_01krdgeqcxet5s7t44vh8rt9mh","emoji":"🎉","status":"rejected","from":{"phone_number":"+13124495569"},"error":{"code":"internal_error","description":"the receiving number is no longer connected","occurred_at":"2026-08-28T19:04:22Z"},"occurred_at":"2026-08-28T19:04:22Z"},{"id":"war_01krdgeqcxet5s7t44vh8rt9mg","emoji":"👍","status":"received","from":{"phone_number":"+14155550100","bsuid":"US.13491208655302741918"},"occurred_at":"2026-08-28T19:01:10Z"}],"next_cursor":null,"prev_cursor":null,"refresh_cursor":"eyJ2IjoxLCJzIjoiMjAyNi0wOC0yOFQxOTowNDoyMloiLCJpIjoiMDE5ZTFiMDctNWQ5ZC03NjhiLTkzZTgtODRkYzUxOGQyNjkxIn0"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/templates":{"get":{"operationId":"listWhatsAppTemplates","x-snippet-key":"whatsapp.templates.list","summary":"List available message templates","description":"Returns the WhatsApp message templates available to your workspace: both the workspace's own templates (`scope: workspace`) and our built-in, Meta-approved templates (`scope: system`); filter to one tier with `scope`. Each entry carries the template's `slug` (the handle you reference when sending), its aggregated `status`, and the languages a send can currently resolve. It also summarizes where every language stands at Meta, so a list page can show an accurate row without another request. Content is not here: it lives under [a version](/docs/api/reference/get-whatsapp-template-version). The list is cursor-paginated. With no `scope`, the workspace's own templates come first, newest first, followed by our built-in templates.\n","tags":["whatsapp-templates"],"x-audiences":["public","dashboard","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"waba","in":"query","required":false,"description":"Filter to a single WhatsApp Business Account by its Meta WABA ID: the same value each template reports in its own `waba` field. Our built-in templates belong to no account and are never returned when this is set, and an account your workspace does not hold returns an empty page.","schema":{"type":"string","minLength":1,"example":"102290129340398"}},{"name":"status","in":"query","required":false,"description":"Filter by lifecycle status. Repeat the parameter to match any of several. Our built-in templates are always `active`.","schema":{"type":"array","items":{"$ref":"#/components/schemas/TemplateStatus"}}},{"name":"scope","in":"query","required":false,"description":"Filter by ownership tier: `system` for the built-in, Meta-approved template catalog, or `workspace` for the workspace's own templates. Omit to return both.","schema":{"$ref":"#/components/schemas/TemplateScope"}},{"name":"category","in":"query","required":false,"description":"Filter by template category.","schema":{"$ref":"#/components/schemas/WhatsAppTemplateCategory"}},{"name":"q","in":"query","required":false,"description":"A case-insensitive substring search across the template's slug, name, and description.","schema":{"type":"string","minLength":1}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"The message templates available to your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppTemplateList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","sdk"]}},"/v1/whatsapp/templates/{template_ref}":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"Template ID (`wat_` prefix) or slug. A value that parses as a valid ID resolves by ID; any other value resolves as a slug.\n","schema":{"type":"string","minLength":1,"maxLength":63},"example":"bird_otp"}],"get":{"operationId":"getWhatsAppTemplate","x-snippet-key":"whatsapp.templates.get","summary":"Get a message template","description":"Returns one template by its ID or slug: its lifecycle, the languages a send can currently resolve, a summary of where every language stands at Meta, and a pointer to the version that is live. Content is not here: read [a version](/docs/api/reference/get-whatsapp-template-version) for that.\n","tags":["whatsapp-templates"],"x-audiences":["public","dashboard","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"The requested template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppTemplate"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/templates/{template_ref}/versions":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"Template ID (`wat_` prefix) or slug. A value that parses as a valid ID resolves by ID; any other value resolves as a slug.\n","schema":{"type":"string","minLength":1,"maxLength":63},"example":"bird_otp"}],"get":{"operationId":"listWhatsAppTemplateVersions","x-snippet-key":"whatsapp.templates.versions.list","summary":"List a template's versions","description":"Returns the template's versions, newest first. Each names the languages it holds and what became of them; content is not here, since a page of versions would carry a copy of every language in every one of them. Read a single version for its content.\n","tags":["whatsapp-templates"],"x-audiences":["public","dashboard","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"The template's versions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppTemplateVersionList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/templates/{template_ref}/versions/{version_id}":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"Template ID (`wat_` prefix) or slug. A value that parses as a valid ID resolves by ID; any other value resolves as a slug.\n","schema":{"type":"string","minLength":1,"maxLength":63},"example":"bird_otp"},{"name":"version_id","in":"path","required":true,"description":"ID of the template version (`wav_` prefix), as returned by the version list.","schema":{"$ref":"#/components/schemas/WhatsAppTemplateVersionID"}}],"get":{"operationId":"getWhatsAppTemplateVersion","x-snippet-key":"whatsapp.templates.versions.get","summary":"Get a template version","description":"Returns one version: the content of every language it holds and what its submission did with each.\n","tags":["whatsapp-templates"],"x-audiences":["public","dashboard","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"The requested version.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppTemplateVersion"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","sdk"]}},"/v1/whatsapp/templates/{template_ref}/versions/{version_id}/languages":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"Template ID (`wat_` prefix) or slug. A value that parses as a valid ID resolves by ID; any other value resolves as a slug.\n","schema":{"type":"string","minLength":1,"maxLength":63},"example":"bird_otp"},{"name":"version_id","in":"path","required":true,"description":"ID of the template version (`wav_` prefix), as returned by the version list.","schema":{"$ref":"#/components/schemas/WhatsAppTemplateVersionID"}}],"get":{"operationId":"listWhatsAppTemplateVersionLanguages","x-snippet-key":"whatsapp.templates.versions.languages.list","summary":"List a version's languages","description":"Returns every language a version holds, without content: each language's tag, what this version's submission did with it, its write counter, and a hash over its content. Compare the hashes to tell which languages actually differ before fetching any content. Fetch [a single language](/docs/api/reference/get-whatsapp-template-version-language) for its content blocks.\n","tags":["whatsapp-templates"],"x-audiences":["public","dashboard","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"The version's languages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppTemplateLanguageList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/templates/{template_ref}/versions/{version_id}/languages/{language}":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"Template ID (`wat_` prefix) or slug. A value that parses as a valid ID resolves by ID; any other value resolves as a slug.\n","schema":{"type":"string","minLength":1,"maxLength":63},"example":"bird_otp"},{"name":"version_id","in":"path","required":true,"description":"ID of the template version (`wav_` prefix), as returned by the version list.","schema":{"$ref":"#/components/schemas/WhatsAppTemplateVersionID"}},{"name":"language","in":"path","required":true,"description":"The language, as a BCP-47 tag. Case and separator variance is accepted and normalised, and the canonical form is returned.\n","schema":{"$ref":"#/components/schemas/LanguageTag"},"example":"nl-BE"}],"get":{"operationId":"getWhatsAppTemplateVersionLanguage","x-snippet-key":"whatsapp.templates.versions.languages.get","summary":"Get a version's language","description":"Returns one language of one version: its content blocks, what this version's submission did with it, and everything Meta holds about it: review outcome and category.\n","tags":["whatsapp-templates"],"x-audiences":["public","dashboard","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"The requested language.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppTemplateLanguage"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/summary":{"get":{"operationId":"getWhatsAppStatsSummary","x-snippet-key":"whatsapp.stats.summary","summary":"Get aggregate outbound WhatsApp statistics","description":"Returns one aggregate row for the requested period. It includes WhatsApp lifecycle counts, delivery and failure rates, and processing, delivery, and total latency percentiles (`p50`, `p95`, and `p99`). Rows use send-time attribution, so recent periods can under-report `delivered` while delivery reports arrive.\n\nRate fields are `null` when their denominator is zero. `from` and `to` must both be calendar days or RFC 3339 instants. Day windows cover up to 365 whole days. Instant bounds round down to the hour, remain inclusive, and may span up to 720 hours. Mixing the forms returns `422`. Set `timezone` for local boundaries.\n\nSet one dimension filter at most; more than one returns `422`. Use `compare=previous_period` to include the preceding equal-length window and each metric's change.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window: a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The `timezone` parameter makes a calendar day local and rounds an instant down to the local hour. Omit `timezone` to use UTC. When `timezone` is set, a numeric UTC offset such as `+05:45` is rejected; use a calendar day or a `Z` (UTC) instant. This value must use the same form as `to`. When omitted, it defaults to 30 days before `to` for day windows or 168 hours (7 days) before `to` for hour windows.\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window: a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The `timezone` parameter makes a calendar day local and rounds an instant down to the local hour. Omit `timezone` to use UTC. When `timezone` is set, a numeric UTC offset is rejected; use a calendar day or a `Z` (UTC) instant. This value must use the same form as `from`. When omitted, it defaults to today for day windows or the current hour for hour windows in that timezone. Day windows may not exceed 365 days; hour windows may not exceed 720 hours (30 days).\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"$ref":"#/components/parameters/WhatsAppStatsTemplateFilter"},{"name":"category","in":"query","required":false,"description":"Restrict the statistics to a single template category. Mutually exclusive with the other dimension filters (`template`, `phone_number`, `tag`); only one may be set per request. Matches the `category` key on a row of the per-category breakdown.\n","schema":{"$ref":"#/components/schemas/WhatsAppTemplateCategory"}},{"name":"phone_number","in":"query","required":false,"description":"Restrict the statistics to a single business sender phone number, in E.164 form. Mutually exclusive with the other dimension filters (`template`, `category`, `tag`); only one may be set per request. Matches the `phone_number` key on a row of the per-phone-number breakdown.\n","schema":{"type":"string","minLength":1,"pattern":"^\\+[1-9]\\d{1,14}$","example":"+13124495569"}},{"name":"tag","in":"query","required":false,"description":"Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters (`template`, `category`, `phone_number`); only one may be set per request. A row of the per-tag breakdown carries the same pair in its single `tag` key.\n","schema":{"type":"string","minLength":1,"example":"campaign:spring_launch"}},{"name":"compare","in":"query","required":false,"description":"Set to `previous_period` to also include the same statistics for the immediately preceding window of equal length, plus the change between the two, so you can show \"+X% vs last period\" without a second request. The comparison window carries any dimension filter set on the request, so a filtered comparison compares like with like.\n","schema":{"$ref":"#/components/schemas/StatsComparePeriod"}}],"responses":{"200":{"description":"Aggregate summary for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppStatsSummary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/daily":{"get":{"operationId":"getWhatsAppStatsDaily","x-snippet-key":"whatsapp.stats.daily","summary":"Get daily outbound WhatsApp statistics","description":"Returns one row of aggregate WhatsApp statistics per calendar day for the workspace. Rows use send-time attribution, so a delivery confirmation received on Wednesday for a message accepted the prior Monday is counted in Monday's row. Recent rows can under-report `delivered` while delivery reports arrive. Days with no activity are included with zero counts.\n\nEach row carries lifecycle counts (accepted, sent, delivered, read, failed) and its own latency percentiles; delivery, failure, and read rates are whole-window aggregates available from the summary endpoint instead. `from` and `to` are optional calendar days (YYYY-MM-DD), defaulting to the trailing 30 days. The maximum window is 365 days; a longer range returns `422`. Set `timezone` to report rows by your local calendar day.\n\nSet at most one dimension filter (`template`, `category`, `phone_number`, `tag`) to restrict the statistics to that dimension's value; setting more than one returns 422.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). The window may not exceed 365 days. Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). The window may not exceed 365 days. Defaults to today in that timezone when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"$ref":"#/components/parameters/WhatsAppStatsTemplateFilter"},{"name":"category","in":"query","required":false,"description":"Restrict the statistics to a single template category. Mutually exclusive with the other dimension filters (`template`, `phone_number`, `tag`); only one may be set per request. Matches the `category` key on a row of the per-category breakdown.\n","schema":{"$ref":"#/components/schemas/WhatsAppTemplateCategory"}},{"name":"phone_number","in":"query","required":false,"description":"Restrict the statistics to a single business sender phone number, in E.164 form. Mutually exclusive with the other dimension filters (`template`, `category`, `tag`); only one may be set per request. Matches the `phone_number` key on a row of the per-phone-number breakdown.\n","schema":{"type":"string","minLength":1,"pattern":"^\\+[1-9]\\d{1,14}$","example":"+13124495569"}},{"name":"tag","in":"query","required":false,"description":"Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters (`template`, `category`, `phone_number`); only one may be set per request. A row of the per-tag breakdown carries the same pair in its single `tag` key.\n","schema":{"type":"string","minLength":1,"example":"campaign:spring_launch"}}],"responses":{"200":{"description":"Daily aggregate stats for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppStatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/hourly":{"get":{"operationId":"getWhatsAppStatsHourly","x-snippet-key":"whatsapp.stats.hourly","summary":"Get hourly outbound WhatsApp statistics","description":"Returns one row of WhatsApp lifecycle counts per hour. Rows use send-time attribution, so a delivery confirmation is counted in the hour when its message was accepted. Recent rows can under-report `delivered` while delivery reports arrive. Set `timezone` for local hours instead of UTC, including zones with sub-hour offsets.\n\nEach row includes its own latency percentiles; delivery, failure, and read rates are whole-window aggregates available from the summary endpoint instead. `from` and `to` are optional RFC 3339 instants, defaulting to the trailing 168 hours; each bound rounds down to the hour and remains inclusive. A request may span up to 30 days (720 rows). An excessive or reversed window returns `422`.\n\nSet one dimension filter at most; more than one returns `422`.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start of the window (RFC 3339 instant), rounded down to the start of its hour and included. The boundary uses the local hour when `timezone` is set and the UTC hour otherwise. The window may not exceed 30 days (720 hours). Defaults to 168 hours (7 days) before `to` when omitted.","schema":{"type":"string","format":"date-time","minLength":1,"example":"2026-05-25T00:00:00Z"}},{"name":"to","in":"query","required":false,"description":"End of the window (RFC 3339 instant), rounded down to the start of its hour and included. The boundary uses the local hour when `timezone` is set and the UTC hour otherwise, so both bounds are inclusive. The window may not exceed 30 days (720 hours). Defaults to the current hour when omitted.","schema":{"type":"string","format":"date-time","minLength":1,"example":"2026-05-25T23:59:59Z"}},{"$ref":"#/components/parameters/StatsTimezone"},{"$ref":"#/components/parameters/WhatsAppStatsTemplateFilter"},{"name":"category","in":"query","required":false,"description":"Restrict the statistics to a single template category. Mutually exclusive with the other dimension filters (`template`, `phone_number`, `tag`); only one may be set per request. Matches the `category` key on a row of the per-category breakdown.\n","schema":{"$ref":"#/components/schemas/WhatsAppTemplateCategory"}},{"name":"phone_number","in":"query","required":false,"description":"Restrict the statistics to a single business sender phone number, in E.164 form. Mutually exclusive with the other dimension filters (`template`, `category`, `tag`); only one may be set per request. Matches the `phone_number` key on a row of the per-phone-number breakdown.\n","schema":{"type":"string","minLength":1,"pattern":"^\\+[1-9]\\d{1,14}$","example":"+13124495569"}},{"name":"tag","in":"query","required":false,"description":"Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters (`template`, `category`, `phone_number`); only one may be set per request. A row of the per-tag breakdown carries the same pair in its single `tag` key.\n","schema":{"type":"string","minLength":1,"example":"campaign:spring_launch"}}],"responses":{"200":{"description":"Hourly aggregate stats for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppStatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/error-codes":{"get":{"operationId":"getWhatsAppStatsByErrorCode","x-snippet-key":"whatsapp.stats.by_error_code","summary":"Get outbound WhatsApp statistics by error code","description":"Returns the count of failed WhatsApp messages grouped by normalized failure reason for the requested period. Rows are ranked by failure count descending and capped at the requested `limit` (default 50, max 200). Rows use send-time attribution, so a failure reported during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports failures while reports are still arriving, and its counts grow as they land. The maximum window is 365 days; a longer range returns 422. A breakdown is already a single-dimension view and takes no dimension filter; to restrict statistics to a single template, category, phone number or tag, use the summary, daily or hourly statistics instead.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-06-14"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-07-13"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"limit","in":"query","required":false,"description":"Maximum number of error-code rows to return, ranked by failure count descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-error-code failure breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppStatsByErrorCodeResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/templates":{"get":{"operationId":"getWhatsAppStatsByTemplate","x-snippet-key":"whatsapp.stats.by_template","summary":"Get outbound WhatsApp statistics by template","description":"Returns lifecycle counts and delivery rates for WhatsApp messages grouped by the template they were sent from, for the requested period. Rows are keyed by `template_id`, matching the email template breakdown, so a renamed template stays one row. Rows are ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). Rows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive. The maximum window is 365 days; a longer range returns 422. A breakdown is already a single-dimension view and takes no dimension filter; to restrict statistics to a single template, category, phone number or tag, use the summary, daily or hourly statistics instead. For the coarser split across Meta's marketing, utility, and authentication categories, use the template-categories breakdown instead. Each row also carries the same three latency families as the summary: `processing`, `delivery` and `total`. `delivery` is measured from a best-effort handoff timestamp that a fast delivery callback can beat, so it can be absent for a row whose other two families are present.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-06-14"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-07-13"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"limit","in":"query","required":false,"description":"Maximum number of template rows to return, ranked by accepted volume descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-template breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppStatsByTemplateResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/template-categories":{"get":{"operationId":"getWhatsAppStatsByTemplateCategory","x-snippet-key":"whatsapp.stats.by_template_category","summary":"Get outbound WhatsApp statistics by template category","description":"Returns lifecycle counts and delivery rates for WhatsApp messages grouped by template category for the requested period. Rows are ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). Rows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive. The maximum window is 365 days; a longer range returns 422. A breakdown is already a single-dimension view and takes no dimension filter; to restrict statistics to a single template, category, phone number or tag, use the summary, daily or hourly statistics instead. Each row also carries the same three latency families as the summary: `processing`, `delivery` and `total`. `delivery` is measured from a best-effort handoff timestamp that a fast delivery callback can beat, so it can be absent for a row whose other two families are present.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-06-14"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-07-13"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"limit","in":"query","required":false,"description":"Maximum number of template-category rows to return, ranked by accepted volume descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-template-category breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppStatsByTemplateCategoryResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/tags":{"get":{"operationId":"getWhatsAppStatsByTag","x-snippet-key":"whatsapp.stats.by_tag","summary":"Get outbound WhatsApp statistics by tag","description":"Returns lifecycle counts and delivery rates for WhatsApp messages grouped by tag (`name:value`) for the requested period. Rows are ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). Rows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive. The maximum window is 365 days; a longer range returns 422. A breakdown is already a single-dimension view and takes no dimension filter; to restrict statistics to a single template, category, phone number or tag, use the summary, daily or hourly statistics instead. Each row also carries the same three latency families as the summary: `processing`, `delivery` and `total`. `delivery` is measured from a best-effort handoff timestamp that a fast delivery callback can beat, so it can be absent for a row whose other two families are present.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-06-14"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-07-13"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"limit","in":"query","required":false,"description":"Maximum number of tag rows to return, ranked by accepted volume descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-tag breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppStatsByTagResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/phone-numbers":{"get":{"operationId":"getWhatsAppStatsByPhoneNumber","x-snippet-key":"whatsapp.stats.by_phone_number","summary":"Get outbound WhatsApp statistics by phone number","description":"Returns delivery counts grouped by business phone number, including whether each is platform-managed or customer-owned. Rows use send-time attribution, rank by accepted volume, and are capped by `limit` (default 50, maximum 200). A recent period under-reports `delivered` while delivery reports are still arriving. The maximum window is 365 days; a longer range returns `422`. A breakdown is already a single-dimension view and takes no dimension filter; to restrict statistics to a single template, category, phone number or tag, use the summary, daily or hourly statistics instead. Each row also carries the same three latency families as the summary: `processing`, `delivery` and `total`. `delivery` is measured from a best-effort handoff timestamp that a fast delivery callback can beat, so it can be absent for a row whose other two families are present.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-06-14"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-07-13"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"limit","in":"query","required":false,"description":"Maximum number of phone-number rows to return, ranked by accepted volume descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-phone-number breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppStatsByPhoneNumberResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/countries":{"get":{"operationId":"getWhatsAppStatsByCountry","x-snippet-key":"whatsapp.stats.by_country","summary":"Get outbound WhatsApp statistics by country","description":"Returns delivery counts, read engagement and latency grouped by the recipient's destination\ncountry. Rows use send-time attribution, rank by accepted volume, and are capped by `limit`\n(default 50, maximum 200). A recent period under-reports `delivered` while delivery reports\nare still arriving. The maximum window is 365 days; a longer range returns `422`.\n\nA country row covers messages addressed to a single recipient. A recipient whose country\ncannot be resolved (a number shaped like a phone number that belongs to no country, or a\nnon-geographic range such as freephone) is counted under `ZZ` rather than dropped,\nmatching the SMS country breakdown. Group sends are omitted entirely: one spans\nup to eight recipients in as many countries, so no single destination country describes\nit. These rows therefore sum to the summary less that group volume. A window reaching before this\nbreakdown shipped falls short by more than that: history was seeded only where the\nrecipient's country could be recovered from stored data, so a phone-addressed send\naccepted before the cutover has no `accepted` leg here. Two cases follow, and they read\ndifferently. Acceptance days more than 14 days before the cutover are a plain shortfall\nand never change. In the 14 days immediately before it, a `delivered`, `read` or `failed`\ncallback arriving after the cutover does carry a country and is attributed to its original\nacceptance day, so a row there can report deliveries with `accepted` at zero. Its\n`delivery_rate` and `failure_rate` are then null for want of a denominator while\n`read_rate` still computes and looks healthy. The 14 days are the status recording window,\nafter which a callback is dropped. All of this is confined to pre-cutover acceptance days\nand gone once the requested window starts after the cutover date.\n\nLatency reports the same three families as the summary: `processing`, `delivery` and\n`total`. The delivery family depends on a best-effort handoff timestamp that a fast\ndelivery callback can beat, so it can be absent for a country whose other two families\nare present.\n\nA breakdown is already a single-dimension view and takes no dimension filter; to restrict\nstatistics to a single template, category, phone number or tag, use the summary, daily or\nhourly statistics instead.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-08-09"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-09-08"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"limit","in":"query","required":false,"description":"Maximum number of country rows to return, ranked by accepted volume descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-country breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppStatsByCountryResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/inbound/summary":{"get":{"operationId":"getWhatsAppInboundStatsSummary","x-snippet-key":"whatsapp.stats.inbound.summary","summary":"Get aggregate inbound WhatsApp statistics","description":"Returns the total number of WhatsApp messages your business numbers received over the period, using the time each message reached your number.\n\nThe response contains only a count because a received message has one state. Use the send statistics endpoints for delivery rates and latency data about messages you send.\n\nThe maximum window is 365 days for a day-grain range, or 720 hours for an hour-grain range; a longer range returns 422. Set `timezone` to resolve the period against your local calendar instead of UTC.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window, either a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The form you use selects the grain the total is resolved at. Interpreted in `timezone`, or in UTC when `timezone` is omitted. Must use the same form as `to`. A numeric UTC offset (for example `+05:45`) is rejected when `timezone` is set; pass a calendar day or a `Z` instant instead. Defaults to 30 days before `to` for day windows, or 168 hours before `to` for hour windows.\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window, in the same form as `from`. Defaults to today, or the current hour for an hour window. A day window may not exceed 365 days and an hour window 720 hours.\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"compare","in":"query","required":false,"description":"Set to `previous_period` to include the received-message count for the immediately preceding window of equal length. The response also includes the change between the two, so you can show \"+X% vs last period\" without a second request.\n","schema":{"$ref":"#/components/schemas/StatsComparePeriod"}}],"responses":{"200":{"description":"Total received messages for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppInboundStatsSummaryResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/inbound/daily":{"get":{"operationId":"getWhatsAppInboundStatsDaily","x-snippet-key":"whatsapp.stats.inbound.daily","summary":"Get daily inbound WhatsApp statistics","description":"Returns the number of WhatsApp messages your business numbers received, one row per calendar day. Rows use the time each message reached your number, and days with no messages contain a zero count.\n\nEach row contains only a count because a received message has one state. Use the send statistics endpoints for lifecycle and delivery-latency data about messages you send.\n\n`from` and `to` are optional calendar days (YYYY-MM-DD), defaulting to the trailing 30 days. The maximum window is 365 days; requesting a longer range returns 422. Set `timezone` to bucket rows by your local calendar day instead of UTC.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). The window may not exceed 365 days. Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). The window may not exceed 365 days. Defaults to today in that timezone when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"}],"responses":{"200":{"description":"Daily received-message counts for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppInboundStatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/inbound/hourly":{"get":{"operationId":"getWhatsAppInboundStatsHourly","x-snippet-key":"whatsapp.stats.inbound.hourly","summary":"Get hourly inbound WhatsApp statistics","description":"Returns the number of WhatsApp messages your business numbers received, one row per hour. Rows use the time each message reached your number, in UTC by default or local time when you set `timezone`. Hours with no messages contain a zero count.\n\nEach row contains only a count because a received message has one state. Use the send statistics endpoints for lifecycle and delivery-latency data about messages you send.\n\n`from` and `to` are optional RFC 3339 instants defaulting to the trailing 168 hours, rounded down to the enclosing hour and echoed back in `period`, both bounds inclusive. A single request may span at most 30 days (720 hourly rows); for longer ranges use the daily endpoint. Requesting an hourly window longer than 30 days, or a `from` after `to`, returns 422.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start of the window (RFC 3339 instant). Rounded down to the start of its hour (the local hour when `timezone` is set, otherwise the UTC hour), and that hour is included. The window may not exceed 30 days (720 hours). Defaults to 168 hours (7 days) before `to` when omitted.","schema":{"type":"string","format":"date-time","minLength":1,"example":"2026-05-25T00:00:00Z"}},{"name":"to","in":"query","required":false,"description":"End of the window (RFC 3339 instant). Rounded down to the start of its hour (the local hour when `timezone` is set, otherwise the UTC hour), and that hour is included (both bounds inclusive). The window may not exceed 30 days (720 hours). Defaults to the current hour when omitted.","schema":{"type":"string","format":"date-time","minLength":1,"example":"2026-05-25T23:59:59Z"}},{"$ref":"#/components/parameters/StatsTimezone"}],"responses":{"200":{"description":"Hourly received-message counts for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppInboundStatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/stats/inbound/phone-numbers":{"get":{"operationId":"getWhatsAppInboundStatsByPhoneNumber","x-snippet-key":"whatsapp.stats.inbound.by_phone_number","summary":"Get inbound WhatsApp statistics by phone number","description":"Returns how many WhatsApp messages each of your business phone numbers received. Rows are ranked by volume descending and capped at the requested `limit` (default 50, max 200), counted by the time each message reached the number.\n\nEach row contains only a count because a received message has one state. Lifecycle and delivery-latency data do not apply to received messages.\n\nThe maximum window is 365 days; a longer range returns 422. Set `timezone` to resolve the period against your local calendar instead of UTC.\n","tags":["whatsapp-stats"],"x-audiences":["public","command"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Must not be after `to`. Max window 365 days. Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-06-14"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window, a calendar day (YYYY-MM-DD). Interpreted in `timezone`, or UTC when omitted. Max window 365 days. Defaults to today in that timezone when omitted.","schema":{"type":"string","format":"date","minLength":1,"example":"2026-07-13"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"limit","in":"query","required":false,"description":"Maximum number of phone-number rows to return, ranked by received-message volume descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-phone-number breakdown of received messages for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppInboundStatsByPhoneNumberResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/numbers":{"get":{"operationId":"listWhatsAppNumbers","x-snippet-key":"whatsapp.numbers.list","summary":"List WhatsApp numbers","description":"Returns a paginated list of the WhatsApp numbers your workspace can send\nfrom. The list includes both platform-managed numbers and numbers on a\nWhatsApp Business Account you connected. Each item reports its WhatsApp\nstatus, quality rating, business portfolio messaging limit, allowed\nsend rate, and Official Business Account status.\n\nPage through the full set with the response cursors. A cursor naming a\nplatform-managed number is rejected once the active filters would exclude\nit, including a `scope=system` cursor for a number that is not\nplatform-managed. Start again without `starting_after` or `ending_before`\nwhenever you change `phone_number`, `waba`, `status`, or `scope`.\n","tags":["whatsapp-numbers"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"waba","in":"query","required":false,"description":"Filter to a single WhatsApp Business Account by its Meta-assigned ID. Use the `waba` of a connected WhatsApp Business Account, or the `waba` on a number this list returns. A platform-managed number belongs to no WhatsApp Business Account and is never returned when this is set, so pairing it with `scope=system` always returns an empty page. An account this workspace does not hold returns an empty page.","schema":{"type":"string","minLength":1,"example":"102290129340398"}},{"name":"phone_number","in":"query","required":false,"description":"Filter to a single number, given in E.164 format. The value is normalized before matching, so `+31612340001` and `+31 6 1234 0001` are the same filter. A value that cannot be normalized is matched exactly as given. A number this workspace cannot send from returns an empty page, rather than being rejected.","schema":{"type":"string","minLength":1,"example":"+31612340001"}},{"name":"status","in":"query","required":false,"description":"Filter by the number's WhatsApp state: the `status` a number in this list carries. Every value matches that status exactly, so repeat the parameter for each state you want. `pending` covers both a number WhatsApp reports as not registered and one it has reported no state for at all, while the two states before that keep their own values: a number we are still verifying carries `preparing`, and one waiting for someone to finish signup carries `awaiting_signup`. Asking for all three reaches every connection that has not finished, plus any finished one WhatsApp has not reported on yet. `failed` matches a connection refused permanently. A value this vocabulary does not recognize matches nothing, rather than being rejected. Omit to return numbers in every state.","style":"form","explode":true,"schema":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/WhatsAppNumberStatus"}}},{"name":"scope","in":"query","required":false,"description":"Filter by ownership tier: `system` for platform-managed numbers and `workspace` for numbers your workspace connected. Omit to return both. A `system` number belongs to no WhatsApp Business Account, so pairing this with `waba` always returns an empty page.","schema":{"$ref":"#/components/schemas/WhatsAppNumberScope"}},{"name":"sort","in":"query","required":false,"description":"Field to sort by.","schema":{"$ref":"#/components/schemas/WhatsAppNumberSortField"}},{"$ref":"#/components/parameters/OrderDesc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"A page of the WhatsApp numbers your workspace can send from.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppNumberList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","sdk"]}},"/v1/whatsapp/numbers/{number_id}":{"get":{"operationId":"getWhatsAppNumber","x-snippet-key":"whatsapp.numbers.get","summary":"Get a WhatsApp number","description":"Returns a WhatsApp number connected to the workspace and its state as of `meta_synced_at`. Poll this after completing embedded signup to follow the connection. The reported status can lag send availability by up to one hour.\n\n`pending` is WhatsApp's own token for a number it does not hold as registered, and is also what a number with no stored WhatsApp status reads, including after setup completes. Other WhatsApp states include `connected`, `disconnected`, and `flagged`. A `failed` status means connection setup ended permanently, and `error` says why; a failure still being retried leaves the number `pending` and sets no `error`.\n","tags":["whatsapp-numbers"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"number_id","in":"path","required":true,"description":"ID of the WhatsApp number (`wan_` prefix), as returned by the number list.","schema":{"$ref":"#/components/schemas/WhatsAppNumberID"}}],"responses":{"200":{"description":"The WhatsApp number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppNumber"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/numbers/{number_id}/events":{"get":{"operationId":"listWhatsAppNumberEvents","summary":"List WhatsApp number events","description":"Returns the number's activity history, newest first by default:\n\n- The number being added, and every change of its status.\n- A messaging-limit change reported by WhatsApp.\n- A display-name review decision.\n- A quality-rating change, observed when Bird next reads the number.\n\nUse it to see when and why a number's status, limit, name, or quality changed,\nrather than polling [Get a WhatsApp number](/docs/api/reference/get-whatsapp-number).\nA number ID that does not belong to the workspace, or no longer exists,\nreturns `404`.\n","tags":["whatsapp-numbers"],"x-audiences":["public","dashboard","command"],"x-snippet-key":"whatsapp.numbers.list_events","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"number_id","in":"path","required":true,"description":"ID of the WhatsApp number whose events to list.","schema":{"$ref":"#/components/schemas/WhatsAppNumberID"}},{"name":"sort","in":"query","required":false,"description":"Field to sort by. Defaults to `created_at`.","schema":{"$ref":"#/components/schemas/WhatsAppNumberEventSortField"}},{"$ref":"#/components/parameters/OrderDesc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"A page of number events.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppNumberEventList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/numbers/{number_id}/profile":{"get":{"operationId":"getWhatsAppNumberProfile","x-snippet-key":"whatsapp.numbers.profile.get","summary":"Get a WhatsApp number's business profile","description":"Returns the business profile WhatsApp shows to people this number messages, read from WhatsApp on each request rather than from a stored copy. Readable for a number your workspace connected itself and for one Bird operates on your behalf alike, since the profile is what everyone the number messages already sees; only the first can be changed. The response includes the display name WhatsApp shows for this number and the state of its review.\n","tags":["whatsapp-numbers"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"number_id","in":"path","required":true,"description":"ID of the WhatsApp number (`wan_` prefix), as returned by the number list.","schema":{"$ref":"#/components/schemas/WhatsAppNumberID"}}],"responses":{"200":{"description":"The number's business profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppNumberProfile"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/business-accounts":{"get":{"operationId":"listWhatsAppBusinessAccounts","x-snippet-key":"whatsapp.business_accounts.list","summary":"List WhatsApp Business Accounts","description":"Returns a paginated list of the WhatsApp Business Accounts your workspace has\nconnected, so you can choose which one a template belongs to. Only accounts\nwhose setup finished are listed: an account appears once WhatsApp has reported\nits name and at least one of its phone numbers has finished connecting. Page through the\nfull set with the cursors the response returns.\n\nEach account also carries the state WhatsApp last reported for it. That covers\nits own status, how far WhatsApp's review of it has got, whether Meta has\nverified the business behind it, the Meta business portfolio that owns it, and\n`ban` on an account WhatsApp has banned. These are the same fields\n[Get a WhatsApp Business Account](/docs/api/reference/get-whatsapp-business-account)\nreturns, and that operation documents them.\n","tags":["whatsapp-business-accounts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"sort","in":"query","required":false,"description":"Field to sort by.","schema":{"$ref":"#/components/schemas/WhatsAppBusinessAccountSortField"}},{"$ref":"#/components/parameters/OrderDesc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"A page of the WhatsApp Business Accounts your workspace has connected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppBusinessAccountList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/business-accounts/{business_account_ref}":{"parameters":[{"name":"business_account_ref","in":"path","required":true,"description":"WhatsApp Business Account ID (`waa_` prefix) or the WhatsApp Business Account ID Meta reports in `waba`. A value that parses as a valid ID resolves by ID; any other value resolves as Meta's ID.\n","schema":{"type":"string","minLength":1,"maxLength":64},"example":"102290129340001"}],"get":{"operationId":"getWhatsAppBusinessAccount","x-snippet-key":"whatsapp.business_accounts.get","summary":"Get a WhatsApp Business Account","description":"Returns one WhatsApp Business Account your workspace has connected, addressed\nby either the `id` the account list reports (`waa_` prefix) or the `waba` value\nWhatsApp reports for it. Both forms resolve to the same account.\n\nOnly accounts whose setup finished can be read: an account is readable once\nWhatsApp has reported its name and at least one of its phone numbers has\nfinished connecting. An account the list hides is `404` here too, in either form.\n\nThe account carries the state WhatsApp last reported for it: its own status,\nhow far WhatsApp's review of it has got, whether Meta has verified the\nbusiness behind it, and the Meta business portfolio that owns it.\n\nAn account WhatsApp has banned carries `ban`, with an `appeal_url` to Meta Business\nSupport once Bird knows the account's portfolio. `ban` is what WhatsApp announced on\nits own notification, not part of the reading `meta_synced_at` dates, because WhatsApp\nreports a ban's state and timing nowhere else. It is absent on an account in good\nstanding and on one whose ban Bird was never told about, so `status` is what says\nwhether an account can send.\n","tags":["whatsapp-business-accounts"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"responses":{"200":{"description":"The WhatsApp Business Account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppBusinessAccount"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/keyword-rules":{"get":{"operationId":"listWhatsAppKeywordRules","x-snippet-key":"whatsapp.keyword_rules.list","summary":"List WhatsApp keyword rules","description":"Returns the keyword rules that apply to inbound messages, most specific first. Bird's own rules are included, so opt-out and opt-in work on every inbound-capable number before you configure anything.\n\nUse the filters to narrow the full, unpaginated list. Set `scope=system` for Bird's rules only, or `scope=workspace` for the ones you created.\n","tags":["whatsapp-keyword-rules"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"country","in":"query","required":false,"description":"Keep only rules that apply to someone messaging from this country, as an ISO 3166-1 alpha-2 code. Omit for every rule, whichever country it covers.\n","schema":{"type":"string","minLength":2,"maxLength":2,"example":"US"}},{"name":"waba","in":"query","required":false,"description":"Keep only the rules that apply to this WhatsApp Business Account of yours, identified by its WhatsApp-issued account ID or by the `waa_` ID Bird gives it. Either form finds the same rules.\n","schema":{"type":"string","example":"102290129340398"}},{"name":"operation","in":"query","required":false,"description":"Keep only rules for this operation. Omit for all of them. Open on the same terms as the response, so an operation Bird gains later can be filtered for without a client update; one Bird does not answer matches nothing rather than failing.\n","schema":{"$ref":"#/components/schemas/WhatsAppKeywordOperation"}},{"name":"scope","in":"query","required":false,"description":"Keep only Bird's own rules (`system`) or only the rules you created (`workspace`). Omit for both.\n","schema":{"$ref":"#/components/schemas/WhatsAppKeywordRuleScope"}}],"responses":{"200":{"description":"The keyword rules that apply to your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppKeywordRuleList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]},"post":{"operationId":"createWhatsAppKeywordRule","x-snippet-key":"whatsapp.keyword_rules.create","summary":"Create a WhatsApp keyword rule","description":"Creates a keyword rule. Use it to replace the reply Bird sends for `opt_out` or `opt_in`, or to add keywords your customers actually type.\n\nYour rule takes precedence over Bird's at the same grain and keeps Bird's keywords unless you add more, so replacing a reply takes two fields. A keyword Bird has bound to one operation cannot be reused for the other.\n","tags":["whatsapp-keyword-rules"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppKeywordRuleCreate"}}}},"responses":{"201":{"description":"The created rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppKeywordRule"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/whatsapp/keyword-rules/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"ID of the keyword rule, as returned by the list operation. Bird's own rules and yours share one ID space.","schema":{"$ref":"#/components/schemas/WhatsAppKeywordRuleID"}}],"get":{"operationId":"getWhatsAppKeywordRule","x-snippet-key":"whatsapp.keyword_rules.get","summary":"Get a WhatsApp keyword rule","description":"Returns one keyword rule, either one of Bird's own or one you created, with its `effective_keywords` and the reply it sends.\n","tags":["whatsapp-keyword-rules"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"}],"responses":{"200":{"description":"The keyword rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppKeywordRule"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]},"patch":{"operationId":"updateWhatsAppKeywordRule","x-snippet-key":"whatsapp.keyword_rules.update","summary":"Update a WhatsApp keyword rule","description":"Changes the reply or the added keywords of a rule you created. Bird's own rules cannot be changed. To replace one, create a rule with the same operation and country and yours takes precedence.\n\nWhat the rule applies to is fixed once created, so this changes the reply and the keywords only.\n","tags":["whatsapp-keyword-rules"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppKeywordRuleUpdate"}}}},"responses":{"200":{"description":"The updated rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppKeywordRule"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]},"delete":{"operationId":"deleteWhatsAppKeywordRule","x-snippet-key":"whatsapp.keyword_rules.delete","summary":"Delete a WhatsApp keyword rule","description":"Deletes a rule you created. The next rule in the ladder answers that scope straight away, so Bird's own keywords keep opting people out; the keywords the rule added go with it, and a word only that rule matched stops meaning anything. Which rule answers next is not always one of yours: the order runs from your rule for an account and country, through your rule for the account, your rule for the country, Bird's rule for the sender's country, your worldwide rule, and finally Bird's worldwide one. So deleting your rule for a country hands the scope to Bird's rule for that country before your own worldwide rule. List the rules to read the order for your workspace. Bird's own rules cannot be deleted.\n","tags":["whatsapp-keyword-rules"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"The rule was deleted."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/inbox-insights/placement":{"get":{"x-snippet-key":"email.inboxInsights.placement","operationId":"getEmailInboxInsightsPlacement","summary":"Get inbox placement for a sending domain","description":"Returns where a sending domain's measured mail landed over the period\n(inbox or spam): the domain-wide rates, a per-provider table, a time\nseries, the Gmail tab split, and optionally per-IP detail for the domain's\nsending infrastructure.\n\nPlacement figures are estimates from a measurement panel of real\nmailboxes. Every rate is a percentage of measured placements, never of\ndelivered volume, and the domain-wide summary is weighted against the\naudience mix described in `measurement`, so it can legitimately differ\nfrom any single provider row. Delta fields appear only when the request\nasks for a comparison and the prior period has data; their absence means\nno comparable prior data, never zero change.\n\nThe series is sparse: buckets with no measured placement are omitted, not\nreturned as zeros, so charts index by date rather than by position. Each\nsection carries its own status, and a successful response never implies\nevery section is populated.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-inbox-insights"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"sending_domain","in":"query","required":true,"description":"The sending domain to report on: one of the workspace's verified sending domains, exactly as it appears there. A domain that is not verified in this workspace answers not-found.\n","schema":{"type":"string","minLength":1,"example":"mail.acme.com"}},{"name":"from","in":"query","required":false,"description":"First UTC day of the period, inclusive, in YYYY-MM-DD: the same window\nconvention as the email statistics endpoints, so figures from the two\nsources cover the same days. Defaults to 30 days before `to`.\n\nIt may be at most 30 days before `to`, which is also the default, so a\nrequest naming neither date is already at the limit. Asking for more\nanswers `422`: the page pairs these figures with Bird's own per-provider\nsending statistics, and those are kept for 30 days, so a longer period\ncould only describe two different spans side by side.\n","schema":{"type":"string","format":"date","example":"2026-07-19"}},{"name":"to","in":"query","required":false,"description":"Last UTC day of the period, inclusive, in YYYY-MM-DD. Defaults to today.","schema":{"type":"string","format":"date","example":"2026-08-17"}},{"name":"group_by","in":"query","required":false,"description":"Bucket size for the series. Defaults to day.","schema":{"$ref":"#/components/schemas/EmailInboxInsightsGroupBy"}},{"name":"compare","in":"query","required":false,"description":"Include the prior equal-length period, populating `compared_to` and every delta field.\n","schema":{"$ref":"#/components/schemas/EmailInboxInsightsCompare"}},{"name":"series_providers","in":"query","required":false,"description":"Providers to break the series down by, named as the provider table names them. Each named provider adds one series line; without this, the series carries the domain-wide line only. The provider table is never filtered by this parameter.\n","schema":{"type":"array","maxItems":10,"items":{"type":"string","minLength":1},"example":["gmail","yahoo"]}},{"name":"include_ip_details","in":"query","required":false,"description":"Include per-IP placement detail for the domain's sending infrastructure. Off by default; only the sending-infrastructure view needs it.\n","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Placement for the requested domain and period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailInboxInsightsPlacement"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"412":{"$ref":"#/components/responses/PreconditionFailed"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/inbox-insights/authentication":{"get":{"x-snippet-key":"email.inboxInsights.authentication","operationId":"getEmailInboxInsightsAuthentication","summary":"Get email authentication standing for a sending domain","description":"Returns whether the domain's mail authenticates and who sends as the\ndomain: SPF and DKIM pass rates, the DMARC standing with its published\npolicy and a conservative ready-for-reject judgement, and a per-source\ntable showing every system observed sending under the domain's name,\nforwarders and unidentified senders included.\n\nThe DMARC figures name their source: authoritative aggregate reporting\nthat covers every sender, or Google Postmaster as a fallback covering\nonly mail Google received. Aggregate reports arrive on reporters' own\nschedules, routinely a day or more behind, so the source table names its\nlatest included day; label from it rather than reading the newest days'\nsparseness as a regression. For a domain with neither reporting source\nconfigured, sections report `not_configured` with a setup path, not an\nerror.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-inbox-insights"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"sending_domain","in":"query","required":true,"description":"The sending domain to report on: one of the workspace's verified sending domains, exactly as it appears there. A domain that is not verified in this workspace answers not-found.\n","schema":{"type":"string","minLength":1,"example":"mail.acme.com"}},{"name":"from","in":"query","required":false,"description":"First UTC day of the period, inclusive, in YYYY-MM-DD: the same window\nconvention as the email statistics endpoints. Defaults to 30 days\nbefore `to`.\n\nIt may be at most 30 days before `to`, which is also the default, so a\nrequest naming neither date is already at the limit. Asking for more\nanswers `422`: the page pairs these figures with Bird's own per-provider\nsending statistics, and those are kept for 30 days, so a longer period\ncould only describe two different spans side by side.\n","schema":{"type":"string","format":"date","example":"2026-07-19"}},{"name":"to","in":"query","required":false,"description":"Last UTC day of the period, inclusive, in YYYY-MM-DD. Defaults to today.","schema":{"type":"string","format":"date","example":"2026-08-17"}},{"name":"compare","in":"query","required":false,"description":"Include the prior equal-length period, populating `compared_to`.","schema":{"$ref":"#/components/schemas/EmailInboxInsightsCompare"}}],"responses":{"200":{"description":"Authentication standing for the requested domain and period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailInboxInsightsAuthentication"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"412":{"$ref":"#/components/responses/PreconditionFailed"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/inbox-insights/complaints":{"get":{"x-snippet-key":"email.inboxInsights.complaints","operationId":"getEmailInboxInsightsComplaints","summary":"Get the Google-reported spam rate for a sending domain","description":"Returns how often the domain's mail is reported as spam by Gmail\nrecipients, as Google Postmaster measures it: the rate for the period and\na time series for charting.\n\nThis is Google's number for Gmail-received mail only. The feedback-loop\ncomplaint rate across all providers is a Bird-measured figure served by\nthe email statistics endpoints; the two count different mail and are\nrendered as separate lines, never combined. For a domain without a\ncompleted Google Postmaster connection, sections report `not_configured`\nwith a setup path, not an error.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-inbox-insights"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"sending_domain","in":"query","required":true,"description":"The sending domain to report on: one of the workspace's verified sending domains, exactly as it appears there. A domain that is not verified in this workspace answers not-found.\n","schema":{"type":"string","minLength":1,"example":"mail.acme.com"}},{"name":"from","in":"query","required":false,"description":"First UTC day of the period, inclusive, in YYYY-MM-DD: the same window\nconvention as the email statistics endpoints. Defaults to 30 days\nbefore `to`.\n\nIt may be at most 30 days before `to`, which is also the default, so a\nrequest naming neither date is already at the limit. Asking for more\nanswers `422`: the page pairs these figures with Bird's own per-provider\nsending statistics, and those are kept for 30 days, so a longer period\ncould only describe two different spans side by side.\n","schema":{"type":"string","format":"date","example":"2026-07-19"}},{"name":"to","in":"query","required":false,"description":"Last UTC day of the period, inclusive, in YYYY-MM-DD. Defaults to today.","schema":{"type":"string","format":"date","example":"2026-08-17"}},{"name":"group_by","in":"query","required":false,"description":"Bucket size for the series. Defaults to day.","schema":{"$ref":"#/components/schemas/EmailInboxInsightsGroupBy"}},{"name":"compare","in":"query","required":false,"description":"Include the prior equal-length period, populating `compared_to` and the rate's delta.\n","schema":{"$ref":"#/components/schemas/EmailInboxInsightsCompare"}}],"responses":{"200":{"description":"The Google-reported spam rate for the requested domain and period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailInboxInsightsComplaints"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"412":{"$ref":"#/components/responses/PreconditionFailed"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/inbox-insights/spam-traps":{"get":{"x-snippet-key":"email.inboxInsights.spamTraps","operationId":"getEmailInboxInsightsSpamTraps","summary":"Get spam-trap hits for a sending domain","description":"Returns the spam-trap hits recorded against a sending domain over the\nperiod: the total, a split by the kind of trap, a split by the trap\nnetwork that observed them, and the individual hits with the sending IP\nand trap age behind each.\n\nSpam traps are addresses that exist only to catch senders mailing lists\nthey should not be mailing, so the kind of trap says more than the count.\nHits on pristine traps, which never belonged to a real person, point at\nharvested or guessed addresses; hits on recycled traps point at stale list\ndata. Zero hits is a measured zero and a good result, so the totals are\nreal figures rather than an empty state.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-inbox-insights"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"sending_domain","in":"query","required":true,"description":"The sending domain to report on: one of the workspace's verified sending domains, exactly as it appears there. A domain that is not verified in this workspace answers not-found.\n","schema":{"type":"string","minLength":1,"example":"mail.acme.com"}},{"name":"from","in":"query","required":false,"description":"First UTC day of the period, inclusive, in YYYY-MM-DD: the same window\nconvention as the email statistics endpoints. Defaults to 30 days\nbefore `to`.\n\nIt may be at most 30 days before `to`, which is also the default, so a\nrequest naming neither date is already at the limit. Asking for more\nanswers `422`: the page pairs these figures with Bird's own per-provider\nsending statistics, and those are kept for 30 days, so a longer period\ncould only describe two different spans side by side.\n","schema":{"type":"string","format":"date","example":"2026-07-19"}},{"name":"to","in":"query","required":false,"description":"Last UTC day of the period, inclusive, in YYYY-MM-DD. Defaults to today.","schema":{"type":"string","format":"date","example":"2026-08-17"}},{"name":"compare","in":"query","required":false,"description":"Include the prior equal-length period, populating `compared_to` and `delta`.\n","schema":{"$ref":"#/components/schemas/EmailInboxInsightsCompare"}}],"responses":{"200":{"description":"Spam-trap hits for the requested domain and period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailInboxInsightsSpamTraps"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"412":{"$ref":"#/components/responses/PreconditionFailed"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/inbox-insights/blocklists":{"get":{"x-snippet-key":"email.inboxInsights.blocklists","operationId":"getEmailInboxInsightsBlocklists","summary":"Check whether a sending domain's infrastructure is blocklisted","description":"Checks the sending IPs behind a sending domain against the blocklists\nreceivers consult, and returns what is listed now plus the listings seen\nrecently against each target. The vendor's default target selection includes\nIPs seen sending in the last 30 days and the domain itself. The returned\ntargets and their statuses describe the coverage of this lookup; an empty\ntarget list does not establish that the domain or its IPs are clear.\nThe 30-day period selects targets; listing status reflects the current lookup.\n\nThe check runs when the request is made, so this is a live lookup rather\nthan a measurement over a period: there is no window, and only the\nfreshness lag hint applies. Providers that publish several lists are\nreported per list, because what a listing means and how it is cleared\ndiffer between them.\n\nEach target is looked up separately, so one can fail while the rest\nsucceed. A target nobody managed to check comes back with its `status`\nreporting that and its `checked_at` null, rather than as a target that\ncame back clear. `active_count` is null when the lookup service supplies no\ncount; do not treat null as zero. Zero does not establish complete coverage:\ninspect the returned targets and their statuses. A `503` means Bird\ncould not reach the lookup service at all, which is a different answer from\na lookup that ran and reported nothing.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-inbox-insights"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"sending_domain","in":"query","required":true,"description":"The sending domain to check: one of the workspace's verified sending domains, exactly as it appears there. Inspect the returned targets and their statuses for lookup coverage. A domain that is not verified in this workspace answers not-found.\n","schema":{"type":"string","minLength":1,"example":"mail.acme.com"}}],"responses":{"200":{"description":"Blocklist standing for the requested domain's sending infrastructure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailInboxInsightsBlocklists"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"412":{"$ref":"#/components/responses/PreconditionFailed"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/inbox-insights/benchmarks/industry":{"get":{"x-snippet-key":"email.inboxInsights.benchmarks.industry","operationId":"getEmailInboxInsightsIndustryBenchmark","summary":"Get the industry placement benchmark for a sending domain","description":"Returns how senders in a sending domain's industry place: the median inbox\nrate across the industry's measured senders, how many senders that median\ncovers, and the industry the domain was classified into.\n\nThe benchmark describes the industry rather than the domain, so it carries\nno comparison of its own. Compare it against the domain's own inbox rate\nfrom the placement resource. Its weighting is a general default rather than\nany one account's audience mix, a deliberate asymmetry with the placement\nfigure it is compared against, and one worth naming wherever the two\nappear together.\n\nThe status is `no_data` when too few measured senders share the industry\nfor a median to be meaningful, when the domain's industry is not\nclassified, or before the industry figures have been computed. That state\nis normal for a young cohort rather than an edge case, so handle it from\nthe start.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-inbox-insights"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"sending_domain","in":"query","required":true,"description":"The sending domain whose industry to benchmark: one of the workspace's verified sending domains, exactly as it appears there. A domain that is not verified in this workspace answers not-found.\n","schema":{"type":"string","minLength":1,"example":"mail.acme.com"}}],"responses":{"200":{"description":"The industry benchmark for the requested domain's industry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailInboxInsightsIndustryBenchmark"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"412":{"$ref":"#/components/responses/PreconditionFailed"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/inbox-insights/domains":{"get":{"x-snippet-key":"email.inboxInsights.domains.list","operationId":"getEmailInboxInsightsDomains","summary":"List sending domains and their Inbox Insights status","description":"Returns a page of sending domains this workspace can report on, in alphabetical\norder by default, and whether Inbox Insights is switched on for each.\n\nOnly verified domains appear. Verifying a domain proves it is yours, which is\nwhat Inbox Insights needs before it will report on it, and a domain that loses\nits verification drops out of this list even if it was switched on.\n\nA domain does not have to be ready to send to appear here. Verification and\nsending readiness are reported separately on your sending domains, and this\nlist follows the first.\n\nUse this list to select a verified domain for placement and reputation reports.\nThe `monitored` field records the workspace's monitoring preference; report access\ndepends on verified ownership and remains available when monitoring is off.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-inbox-insights"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"sending_domain","in":"query","required":false,"description":"Exact sending domain to return. Matching is case-insensitive.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Substring match against the sending domain (case-insensitive).","schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"description":"Field to sort by. Defaults to `domain`.","schema":{"$ref":"#/components/schemas/EmailInboxInsightsDomainSort"}},{"$ref":"#/components/parameters/OrderAsc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"A page of the workspace's sending domains and their Inbox Insights status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailInboxInsightsDomains"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/inbox-insights/domains/{sending_domain}":{"patch":{"x-snippet-key":"email.inboxInsights.domains.update","operationId":"updateEmailInboxInsightsDomain","summary":"Switch Inbox Insights on or off for a sending domain","description":"Changes the workspace's monitoring preference for one of its verified sending\ndomains. Enabling enrolls the domain with eDataSource before saving the preference.\nDisabling removes the preference without removing vendor enrollment or history.\nReport reads remain available for verified owned domains regardless of this setting.\n\nA domain switched on for the first time has to be measured before it has\nanything to report, so its results start empty and fill in as its mail is\nseen. Switching off keeps everything measured so far: switching the domain\nback on restores it in full and takes effect immediately, rather than starting\nthe domain over.\n\nSetting the value it already has changes nothing and answers normally, so this\nis safe to repeat.\n\nAPI-key and service-account calls require Insights preview access for your organization.\n","tags":["email-inbox-insights"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"sending_domain","in":"path","required":true,"description":"The sending domain to change, exactly as it appears in your sending domains. A domain that is not verified in this workspace answers not-found.\n","schema":{"type":"string","minLength":1,"example":"mail.acme.com"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailInboxInsightsDomainUpdate"},"example":{"monitored":true}}}},"responses":{"200":{"description":"The domain's Inbox Insights setting after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailInboxInsightsDomain"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/inbox-insights/domain-monitoring":{"post":{"x-snippet-key":"email.inboxInsights.domainMonitoring.upsert","operationId":"upsertEmailInboxInsightsDomainMonitoring","summary":"Switch Inbox Insights on for the workspace's main sending domain","description":"Switches Inbox Insights on for the workspace's main sending domain, so a\nworkspace opening the product for the first time has something to read\nwithout having to pick a domain first.\n\nThe main sending domain is the one verified domain if there is only one, and\notherwise the verified domain that has sent the most mail over the last 30\ndays. Where the main domain cannot be identified, nothing is switched on and\nthe response says so. Which domain matters most is the customer's call, and\nnot a guess worth making on their behalf.\n\nSafe to repeat. A workspace that already has a domain switched on is left\nexactly as it is, and the response says nothing changed.\n\nThis chooses a starting point, not a permanent setting: the domain it picks\nis switched on the same way as one chosen by hand, and can be switched off\nor added to at any time.\n\nAPI-key and service-account calls require Insights preview access for your organization.\n","tags":["email-inbox-insights"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"What the call did: a domain switched on, nothing to change, or nothing this endpoint is willing to decide.\n","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailInboxInsightsDomainMonitoringResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/stats/daily":{"get":{"operationId":"getEmailStatsDaily","summary":"Get daily sending statistics","description":"Returns one row of aggregate sending statistics per calendar day for the workspace: UTC days by default, or your local days when `timezone` is set. Days with no activity are included with zero counts, so the series charts without client-side gap handling. Suited to charts and trend lines; for per-message exact accounting use the message detail endpoints.\n\nRows use event time. For example, a complaint received on Wednesday for a message sent the prior Monday is counted in Wednesday's row.\n\nThe maximum window is 365 days; requesting a longer range returns `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.daily","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive), `YYYY-MM-DD`. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive), `YYYY-MM-DD`. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Restrict the statistics to a single category: `transactional` or `marketing`. Mutually exclusive with the other dimension filters; only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"transactional"}},{"name":"sending_domain","in":"query","required":false,"description":"Restrict the statistics to a single sending domain (the part of the From address after @). Mutually exclusive with the other dimension filters; only one may be set per request.","schema":{"type":"string","minLength":1,"example":"mail.acme.com"}},{"name":"tag","in":"query","required":false,"description":"Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters; only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"campaign:spring_launch"}},{"name":"sending_ip","in":"query","required":false,"description":"Restrict the statistics to a single sending IP. Mutually exclusive with the other dimension filters; only one may be set per request. A sending IP is assigned only after a message reaches delivery, so this filter reports delivery-side metrics only. Accepted, processed, rejected, complaint, and engagement counts are `0`, and processing latency is `null`. Complaint, open, and click rates are `0` when deliveries exist and `null` otherwise.\n","schema":{"type":"string","minLength":1,"example":"192.0.2.55"}},{"name":"recipient_domain","in":"query","required":false,"description":"Restrict the statistics to a single recipient mailbox domain (the part of the recipient address after the `@`, for example `gmail.com`). Mutually exclusive with the other dimension filters; only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"gmail.com"}},{"$ref":"#/components/parameters/EmailStatsTemplateFilter"}],"responses":{"200":{"description":"Daily aggregate stats for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/hourly":{"get":{"operationId":"getEmailStatsHourly","summary":"Get hourly sending statistics","description":"Returns one row of aggregate sending statistics per hour for the workspace: UTC hours by default, or your local hours when `timezone` is set (a timezone with a sub-hour offset gets correctly aligned hours). Useful for inspecting send rate, deliverability, and engagement inside a single day or a recent window; hours with no activity are included with zero counts.\n\nRows use event time. For example, a click recorded at 14:07 for a message sent at 09:00 lands in the 14:00 row.\n\nA single request may span at most 30 days (720 hourly rows); for longer ranges use the daily endpoint, which has a 365-day window. An hourly window longer than 30 days, or a `from` after `to`, returns `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.hourly","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start of the window (ISO 8601 instant). Rounded down to the start of its hour (the local hour when `timezone` is set, otherwise the UTC hour), and that hour is included. When `timezone` is set, a numeric UTC offset here (for example `+05:45`) is rejected; use a `Z` (UTC) instant. Defaults to 7 days before `to` when omitted.","schema":{"type":"string","format":"date-time","example":"2026-05-25T00:00:00Z"}},{"name":"to","in":"query","required":false,"description":"End of the window (ISO 8601 instant). Rounded down to the start of its hour (the local hour when `timezone` is set, otherwise the UTC hour), and that hour is included (both bounds inclusive). When `timezone` is set, a numeric UTC offset here is rejected; use a `Z` (UTC) instant. Defaults to the current hour when omitted. Window may not exceed 30 days (720 hours).","schema":{"type":"string","format":"date-time","example":"2026-05-25T23:59:59Z"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Restrict the statistics to a single category: `transactional` or `marketing`. Mutually exclusive with the other dimension filters; only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"transactional"}},{"name":"sending_domain","in":"query","required":false,"description":"Restrict the statistics to a single sending domain (the part of the From address after @). Mutually exclusive with the other dimension filters; only one may be set per request.","schema":{"type":"string","minLength":1,"example":"mail.acme.com"}},{"name":"tag","in":"query","required":false,"description":"Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters; only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"campaign:spring_launch"}},{"name":"sending_ip","in":"query","required":false,"description":"Restrict the statistics to a single sending IP. Mutually exclusive with the other dimension filters; only one may be set per request. A sending IP is assigned only after a message reaches delivery, so this filter reports delivery-side metrics only. Accepted, processed, rejected, complaint, and engagement counts are `0`, and processing latency is `null`. Complaint, open, and click rates are `0` when deliveries exist and `null` otherwise.\n","schema":{"type":"string","minLength":1,"example":"192.0.2.55"}},{"name":"recipient_domain","in":"query","required":false,"description":"Restrict the statistics to a single recipient mailbox domain (the part of the recipient address after the `@`, for example `gmail.com`). Mutually exclusive with the other dimension filters; only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"gmail.com"}},{"$ref":"#/components/parameters/EmailStatsTemplateFilter"}],"responses":{"200":{"description":"Hourly aggregate stats for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/tags":{"get":{"operationId":"getEmailStatsByTag","summary":"Get statistics by tag","description":"Returns delivery and engagement counts for the requested period, grouped by tag. Use it to compare performance across the tags you set at send time. Rows are ranked by the `sort` metric, `processed` by default, and capped at the requested `limit` (50 by default, 200 at most).\n\nRows are computed against event time rather than send time, so engagement received during the period counts even for messages that were sent earlier.\n\nThe window can span at most 365 days. Ask for more and you get a `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.byTag","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `processed`.\n","schema":{"$ref":"#/components/schemas/EmailStatsSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of tag rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also gets a `trend` array: a short per-bucket series showing that tag's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-tag breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsTagsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/summary":{"get":{"operationId":"getEmailStatsSummary","summary":"Get aggregate email statistics","description":"Returns a single-row aggregate across the requested period covering delivery, bounce, complaint, open, and click counts plus the derived rates, along with processing, delivery, and total latency percentiles (p50/p95/p99). Suitable for KPI tiles, campaign reports, and email digests; the daily and hourly endpoints have the same metrics per time bucket.\n\nThe aggregate is computed against event time (not send time), so engagement received during the period for messages sent earlier is included. Rate fields are `null` when their denominator is zero.\n\nThe window grain follows the form of `from` and `to`: calendar days (`YYYY-MM-DD`, up to 365 days) or RFC 3339 instants (hour grain, up to 720 hours, 30 days). A rolling window such as the last 24 hours is a single request. Mixing the two forms returns `422`. Set `timezone` to compute day and hour boundaries in a local zone instead of UTC, and `compare=previous_period` to include the preceding equal-length window in the same response.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.summary","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window: a calendar day (`YYYY-MM-DD`) or an RFC 3339 instant (rounded down to the hour). Interpreted in `timezone` (a calendar day names a local day; an instant is rounded down to the local hour), or in UTC when `timezone` is omitted. A numeric UTC offset (for example `+05:45`) is rejected when `timezone` is set; use a calendar day or a `Z` (UTC) instant. Must use the same form as `to`. Defaults to 30 days before `to` for day windows, or 168 hours (7 days) before `to` for hour windows, when omitted.\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window: a calendar day (`YYYY-MM-DD`) or an RFC 3339 instant (rounded down to the hour). Interpreted in `timezone` (a calendar day names a local day; an instant is rounded down to the local hour), or in UTC when `timezone` is omitted. A numeric UTC offset is rejected when `timezone` is set; use a calendar day or a `Z` (UTC) instant. Must use the same form as `from`. Defaults to today for day windows, or the current hour for hour windows, in that timezone, when omitted. Day windows may not exceed 365 days; hour windows may not exceed 720 hours (30 days).\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Restrict the statistics to a single category: `transactional` or `marketing`. Mutually exclusive with the other dimension filters; only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"transactional"}},{"name":"sending_domain","in":"query","required":false,"description":"Restrict the statistics to a single sending domain (the part of the From address after @). Mutually exclusive with the other dimension filters; only one may be set per request.","schema":{"type":"string","minLength":1,"example":"mail.acme.com"}},{"name":"tag","in":"query","required":false,"description":"Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters; only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"campaign:spring_launch"}},{"name":"sending_ip","in":"query","required":false,"description":"Restrict the statistics to a single sending IP. Mutually exclusive with the other dimension filters; only one may be set per request. A sending IP is assigned only after a message reaches delivery, so this filter reports delivery-side metrics only. Accepted, processed, rejected, complaint, and engagement counts are `0`, and processing latency is `null`. Complaint, open, and click rates are `0` when deliveries exist and `null` otherwise.\n","schema":{"type":"string","minLength":1,"example":"192.0.2.55"}},{"name":"recipient_domain","in":"query","required":false,"description":"Restrict the statistics to a single recipient mailbox domain (the part of the recipient address after the `@`, for example `gmail.com`). Mutually exclusive with the other dimension filters; only one may be set per request.\n","schema":{"type":"string","minLength":1,"example":"gmail.com"}},{"$ref":"#/components/parameters/EmailStatsTemplateFilter"},{"name":"compare","in":"query","required":false,"description":"Set to `previous_period` to also include the same statistics for the immediately preceding window of equal length, plus the change between the two, so you can show \"+X% vs last period\" without a second request.\n","schema":{"type":"string","enum":["previous_period"]}}],"responses":{"200":{"description":"Aggregate summary for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsSummary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/sending-ips":{"get":{"operationId":"getEmailStatsBySendingIp","summary":"Get statistics by sending IP","description":"Returns delivery and deliverability counts for the requested period, grouped by the specific IP address used to send each message. Use it to spot a reputation problem on one IP. Block bounces concentrated on a single IP usually mean that IP's reputation has taken a hit, and sorting by `bounces.block` puts those IPs first.\n\nA sending IP is only known once the receiving mail server reports an outcome: a delivery, a bounce, a deferral, or a late bounce. So this breakdown starts from the delivery stage onward. Accepted, processed, and rejected counts aren't included at all, and neither are engagement counts or processing latency. Complaints and out-of-band bounces aren't attributed to a sending IP either, so `complained` and `oob_bounces` are included but always read `0` here. Bounced, deferred, delivery latency, and total latency are the ones that have real numbers. For workspace-wide figures, use `GET /v1/email/stats/daily`. Rows are computed against event time rather than send time.\n\nRows are ranked by the `sort` field, `delivered` by default, and capped at the requested `limit` (50 by default, 200 at most). The window can span at most 365 days. Ask for more and you get a `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.bySendingIp","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"sort","in":"query","required":false,"description":"Metric to rank IPs by, applied descending. Sorting by `bounces.block` puts the IPs whose reputation is most likely degraded at the top. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `delivered`. A sending IP has no engagement, so engagement metrics aren't sortable here, and neither are `processed`, `rejected`, or `oob_bounces`.\n","schema":{"type":"string","default":"delivered","enum":["delivered","bounced","complained","deferred","bounces.hard","bounces.soft","bounces.admin","bounces.block","bounces.undetermined","delivery_rate","bounce_rate","complaint_rate","bounces.hard_rate","bounces.soft_rate","bounces.admin_rate","bounces.block_rate","bounces.undetermined_rate"],"x-enum-varnames":["GetEmailStatsBySendingIpParamsSortDelivered","GetEmailStatsBySendingIpParamsSortBounced","GetEmailStatsBySendingIpParamsSortComplained","GetEmailStatsBySendingIpParamsSortDeferred","GetEmailStatsBySendingIpParamsSortBouncesHard","GetEmailStatsBySendingIpParamsSortBouncesSoft","GetEmailStatsBySendingIpParamsSortBouncesAdmin","GetEmailStatsBySendingIpParamsSortBouncesBlock","GetEmailStatsBySendingIpParamsSortBouncesUndetermined","GetEmailStatsBySendingIpParamsSortDeliveryRate","GetEmailStatsBySendingIpParamsSortBounceRate","GetEmailStatsBySendingIpParamsSortComplaintRate","GetEmailStatsBySendingIpParamsSortBouncesHardRate","GetEmailStatsBySendingIpParamsSortBouncesSoftRate","GetEmailStatsBySendingIpParamsSortBouncesAdminRate","GetEmailStatsBySendingIpParamsSortBouncesBlockRate","GetEmailStatsBySendingIpParamsSortBouncesUndeterminedRate"]}},{"name":"limit","in":"query","required":false,"description":"Maximum number of IP rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also gets a `trend` array: a short per-bucket series showing that IP's delivery rates over the window. A trend point's open and click rates read `0` in a bucket that had deliveries and `null` in one that had none, because a sending IP has no engagement data. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-sending-IP breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsBySendingIpResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/sending-domains":{"get":{"operationId":"getEmailStatsBySendingDomain","summary":"Get statistics by sending domain","description":"Returns delivery, engagement, and deliverability counts for the requested period, grouped by sending domain: the portion of the `From` address after the `@`. Use it to compare deliverability across multiple verified domains in your workspace, for example transactional versus marketing domains, or sub-domain segregation during IP warming.\n\nRows are computed against event time rather than send time, so engagement and bounces received during the period count even for messages that were sent earlier.\n\nRows are ranked by the `sort` metric, `processed` by default, and capped at the requested `limit` (50 by default, 200 at most). The window can span at most 365 days. Ask for more and you get a `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.bySendingDomain","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `processed`.\n","schema":{"$ref":"#/components/schemas/EmailStatsSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of domain rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also gets a `trend` array: a short per-bucket series showing that domain's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-sending-domain breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsBySendingDomainResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/categories":{"get":{"operationId":"getEmailStatsByCategory","summary":"Get statistics by category","description":"Returns delivery and engagement counts for the requested period, grouped by category, so you can compare deliverability and engagement between your transactional and marketing traffic. Rows are ranked by the `sort` metric, `processed` by default, and capped at the requested `limit` (50 by default, 200 at most).\n\nRows are computed against event time rather than send time, so engagement received during the period counts even for messages that were sent earlier.\n\nThe window can span at most 365 days. Ask for more and you get a `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.byCategory","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `processed`.\n","schema":{"$ref":"#/components/schemas/EmailStatsSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of category rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also gets a `trend` array: a short per-bucket series showing that category's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-category breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsByCategoryResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/mailbox-providers":{"get":{"operationId":"getEmailStatsByMailboxProvider","summary":"Get statistics by mailbox provider","description":"Returns delivery, engagement, and deliverability counts for the requested period, grouped by recipient mailbox provider, for example `gmail`, `yahoo`, `microsoft`, or `apple`. Use it to compare how each major inbox provider treats your mail, for example to spot a delivered-rate dip or a complaint spike at one provider before it spreads. For a per-region split within a provider, use the mailbox-provider-region breakdown.\n\nA recipient's mailbox provider is only known once the receiving mail system reports an outcome, so this breakdown covers the delivery stage onward. Accepted, processed, and rejected counts and processing latency are not included. Rows are computed against event time rather than send time.\n\nRows are ranked by the `sort` metric, `delivered` by default, and capped at the requested `limit` (50 by default, 200 at most). The window can span at most 365 days. Ask for more and you get a `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.byMailboxProvider","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints; supplying it returns `422`. To compare categories use `GET /v1/email/stats/categories`; the summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `delivered`. `processed`, `rejected`, and `oob_bounces` are not part of this breakdown's rows, so they are not sortable here.\n","schema":{"$ref":"#/components/schemas/EmailMailboxProviderSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of mailbox-provider rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also gets a `trend` array: a short per-bucket series showing that provider's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-mailbox-provider breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsByMailboxProviderResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/mailbox-provider-regions":{"get":{"operationId":"getEmailStatsByMailboxProviderRegion","summary":"Get statistics by mailbox provider region","description":"Returns delivery, engagement, and deliverability counts for the requested period, grouped by mailbox provider and provider region pair, for example `gmail` in `NA` or `microsoft` in `EU`. The provider region is the regional grouping the receiving mail system reports for the recipient's provider. Pairing it with the provider tells apart a region label that several providers share. Use it to spot a deliverability problem isolated to one provider in one region. For a per-provider view without the region split, use the mailbox-provider breakdown.\n\nA provider region is only known once the receiving mail system reports an outcome, so this breakdown covers the delivery stage onward. Accepted, processed, and rejected counts and processing latency are not included. Rows are computed against event time rather than send time.\n\nRows are ranked by the `sort` metric, `delivered` by default, and capped at the requested `limit` (50 by default, 200 at most). The window can span at most 365 days. Ask for more and you get a `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.byMailboxProviderRegion","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `delivered`. `processed`, `rejected`, and `oob_bounces` are not part of this breakdown's rows, so they are not sortable here.\n","schema":{"$ref":"#/components/schemas/EmailMailboxProviderSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of provider-region rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also gets a `trend` array: a short per-bucket series showing that provider region's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-(mailbox provider, provider region) breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsByMailboxProviderRegionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/recipient-domains":{"get":{"operationId":"getEmailStatsByRecipientDomain","summary":"Get statistics by recipient domain","description":"Returns delivery and engagement counts for the requested period, grouped by recipient mailbox domain: the part of each recipient address after the `@`, for example `gmail.com`, `yahoo.com`, or `outlook.com`. This is the finest-grained deliverability view. Where the mailbox-provider breakdown groups recipients into provider buckets such as `gmail` or `microsoft`, this keys on the exact destination domain. Use it to spot a delivery-rate dip or a complaint spike at a specific domain.\n\nRows are ranked by the `sort` metric, `processed` by default, and capped at the requested `limit` (50 by default, 200 at most). Rows are computed against event time rather than send time, so engagement received during the period counts even for messages that were sent earlier.\n\nThe window can span at most 365 days. Ask for more and you get a `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.byRecipientDomain","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `processed`.\n","schema":{"$ref":"#/components/schemas/EmailStatsSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of recipient-domain rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also gets a `trend` array: a short per-bucket series showing that recipient domain's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-recipient-domain breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsByRecipientDomainResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/templates":{"get":{"operationId":"getEmailStatsByTemplate","summary":"Get statistics by template","description":"Returns aggregate delivery and engagement counts grouped by the template each message was sent with, so a template's deliverability and engagement can be compared side by side. Attribution is by the template used at send time; only messages sent with a template appear here, so a workspace that has sent none returns an empty list rather than an error. Each row is keyed by the template ID (`emt_…`); a template deleted after sending still appears by its ID.\n\nRows are ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, hard maximum 200). Rows are computed against event time (not send time), so engagement received during the period for messages sent earlier is included.\n\nThe maximum window is 365 days; requesting a longer range returns `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.byTemplate","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to 29 days before `to`, keeping the defaulted window within the 720-hour trend cap.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints; supplying it returns `422`. To compare categories use `GET /v1/email/stats/categories`; the summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any count or rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `processed`.\n","schema":{"$ref":"#/components/schemas/EmailStatsSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of template rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_trend","in":"query","required":false,"description":"When true, each row also has a `trend` array: a short per-bucket series of that template's delivery and engagement rates over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns `422`. When `from` is omitted and `trend_grain=hourly`, the default start tightens to 29 days before `to`, keeping the window inside 720 hours, so a request built entirely from defaults always fits the cap.\n","schema":{"type":"boolean","default":false}},{"name":"trend_grain","in":"query","required":false,"description":"Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.","schema":{"$ref":"#/components/schemas/StatsTrendGrain"}}],"responses":{"200":{"description":"Per-template breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsByTemplateResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/locations":{"get":{"operationId":"getEmailStatsByLocation","summary":"Get engagement by location","description":"Returns engagement counts (opens and clicks) for the requested period, grouped by the location they were recorded from. Use it to see where your audience engages, for example the top countries by unique opens. The reading location is only known from open and click events, so rows have engagement counts but no delivery counts or rates.\n\nUse `group_by` to choose the granularity: `country` (the default), `region`, or `city`. Each row has the location hierarchy down to the requested level, so a `city` grouping also reports that row's region and country. Rows are ranked by the `sort` metric, `unique_opens` by default, and capped at the requested `limit` (50 by default, 200 at most).\n\nRows are computed against event time rather than send time. The window can span at most 365 days. Ask for more and you get a `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.byLocation","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints; supplying it returns `422`. To compare categories use `GET /v1/email/stats/categories`; the summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"group_by","in":"query","required":false,"description":"Location granularity for each row. `country` (default) groups by country; `region` groups by region within country; `city` groups by city within region. Each row reports the location hierarchy down to the chosen level.\n","schema":{"type":"string","default":"country","enum":["country","region","city"],"x-enum-varnames":["GetEmailStatsByLocationParamsGroupByCountry","GetEmailStatsByLocationParamsGroupByRegion","GetEmailStatsByLocationParamsGroupByCity"]}},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. It defaults to `unique_opens`. Only engagement counts are sortable. This breakdown has no rates.\n","schema":{"$ref":"#/components/schemas/EmailEngagementSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of location rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-location engagement breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsByLocationResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/clients":{"get":{"operationId":"getEmailStatsByClient","summary":"Get engagement by email client","description":"Returns engagement counts (opens and clicks) for the requested period, grouped by the email client, operating system, or device type they were recorded from. Use it for the classic view of opens by mail client, for example the share of opens from Apple Mail compared with Gmail and Outlook. The reading environment is only known from open and click events, so rows have engagement counts but no delivery counts or rates.\n\nUse `group_by` to choose the facet: `email_client` (the default), `os`, or `device_type`. Each row fills in the facet you chose and leaves the other two `null`. Rows are ranked by the `sort` metric, `unique_opens` by default, and capped at the requested `limit` (50 by default, 200 at most).\n\nRows are computed against event time rather than send time. The window can span at most 365 days. Ask for more and you get a `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.byClient","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints; supplying it returns `422`. To compare categories use `GET /v1/email/stats/categories`; the summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"group_by","in":"query","required":false,"description":"Which reading-environment facet to group rows by. `email_client` (default) groups by mail client; `os` groups by operating system; `device_type` groups by device type. Each row populates the chosen facet and leaves the other two `null`.\n","schema":{"type":"string","default":"email_client","enum":["email_client","os","device_type"],"x-enum-varnames":["GetEmailStatsByClientParamsGroupByEmailClient","GetEmailStatsByClientParamsGroupByOs","GetEmailStatsByClientParamsGroupByDeviceType"]}},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. It defaults to `unique_opens`. Only engagement counts are sortable. This breakdown has no rates.\n","schema":{"$ref":"#/components/schemas/EmailEngagementSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of client rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-client engagement breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsByClientResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/bounce-codes":{"get":{"operationId":"getEmailStatsByBounceCode","summary":"Get bounces by SMTP error code","description":"Returns bounce counts for the requested period, grouped by the SMTP error code the receiving mail server returned. It answers the question of which SMTP responses are driving your bounces. Each row reports how many recipients bounced with that code, plus the hard, soft, admin, block, and undetermined split for that code.\n\nThis failure-only breakdown omits delivered, open, click, and rate fields because bounce codes occur only on bounce events.\n\nRows are ranked by the `sort` metric, `bounced` by default, and capped at the requested `limit` (50 by default, 200 at most). They are computed against event time rather than send time. The window can span at most 365 days. Ask for more and you get a `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.byBounceCode","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. It defaults to `bounced`. Only the bounce counts are sortable here, because this breakdown has no rate fields.\n","schema":{"type":"string","default":"bounced","enum":["bounced","bounces.hard","bounces.soft","bounces.admin","bounces.block","bounces.undetermined"],"x-enum-varnames":["GetEmailStatsByBounceCodeParamsSortBounced","GetEmailStatsByBounceCodeParamsSortBouncesHard","GetEmailStatsByBounceCodeParamsSortBouncesSoft","GetEmailStatsByBounceCodeParamsSortBouncesAdmin","GetEmailStatsByBounceCodeParamsSortBouncesBlock","GetEmailStatsByBounceCodeParamsSortBouncesUndetermined"]}},{"name":"limit","in":"query","required":false,"description":"Maximum number of bounce-code rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-SMTP-code bounce breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsByBounceCodeResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/complaint-types":{"get":{"operationId":"getEmailStatsByComplaintType","summary":"Get complaints by type","description":"Returns spam-complaint counts for the requested period, grouped by the feedback-loop complaint type the mailbox provider reported, for example `abuse`, `fraud`, or `virus`. Use it to see what kind of complaints your mail attracts.\n\nThis breakdown only covers the complaint side. Each row has the complained count for one type and nothing else, because a complaint type is only ever recorded on a spam-complaint event.\n\nRows are ranked by `complained` descending, and capped at the requested `limit` (default 50, hard maximum 200). They are computed against event time rather than send time. The window can span at most 365 days. Ask for more and you get a `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.byComplaintType","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. It defaults to `complained`, the only sortable metric for this breakdown.\n","schema":{"type":"string","default":"complained","enum":["complained"],"x-enum-varnames":["GetEmailStatsByComplaintTypeParamsSortComplained"]}},{"name":"limit","in":"query","required":false,"description":"Maximum number of complaint-type rows to return, ranked by `complained` descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-complaint-type breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsByComplaintTypeResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/stats/broadcasts":{"get":{"operationId":"getEmailStatsByBroadcast","summary":"Get statistics by broadcast","description":"Returns aggregate delivery and engagement counts grouped by broadcast for the requested period, so each broadcast's deliverability and engagement can be compared side by side. Only messages sent as part of a broadcast appear here. One-off and transactional sends are not included, so a workspace that has not sent broadcasts returns an empty list rather than an error.\n\nRows are ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, hard maximum 200). Rows are computed against event time (not send time), so engagement received during the period for messages sent earlier is included.\n\nThe maximum window is 365 days. Requesting a longer range returns a `422`. This breakdown is computed from per-message activity retained for 30 days, so it reflects roughly the last 30 days of activity even when the requested window reaches further back.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.stats.byBroadcast","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, UTC. Defaults to 30 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-01"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, UTC. Defaults to today (UTC) when omitted. Window may not exceed 365 days.","schema":{"type":"string","format":"date","example":"2026-05-25"}},{"name":"category","in":"query","required":false,"description":"Not supported on breakdown endpoints. Supplying it returns a `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter.","schema":{"type":"string","minLength":1}},{"name":"sort","in":"query","required":false,"description":"Metric to rank rows by, applied descending. Any count or rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `processed`.\n","schema":{"$ref":"#/components/schemas/EmailStatsSortMetric"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of broadcast rows to return, ranked by the `sort` field descending.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Per-broadcast breakdown for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailStatsByBroadcastResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/health":{"get":{"operationId":"getEmailHealth","summary":"Get sending health and deliverability limits","description":"Returns your workspace's sending-health verdict for the requested window, together with reference deliverability limits and the boundaries used to classify risk. Use it to render a health badge, label the bounce-rate and complaint-rate limits, and draw the risk lines on a deliverability chart without hard-coding thresholds that we may retune.\n\nThe overall `status` is `healthy`, `watching`, or `throttled`, taken as the worst of the delivery-rate, bounce-rate, and complaint-rate signals. It describes deliverability risk and never pauses your sending on its own. For the counts and rates the verdict is derived from, call [Get aggregate email statistics](/docs/api/reference/get-email-stats-summary) over the same window.\n\nRates follow each event's occurrence time. A bounce or complaint that occurred during the window counts toward it even when the message was sent earlier. When you omit both dates the window ends today (UTC) and starts 7 days earlier. A window longer than 365 days returns `422`.\n","tags":["email-stats"],"x-audiences":["public","command"],"x-snippet-key":"email.health","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start date (inclusive) in `YYYY-MM-DD`, UTC. Defaults to 7 days before `to` when omitted.","schema":{"type":"string","format":"date","example":"2026-05-19"}},{"name":"to","in":"query","required":false,"description":"End date (inclusive) in `YYYY-MM-DD`, UTC. Defaults to today (UTC) when omitted. Window may not exceed 365 days. Day boundaries are always UTC; unlike the statistics reads, this one takes no `timezone`.","schema":{"type":"string","format":"date","example":"2026-05-25"}}],"responses":{"200":{"description":"Current sending-health verdict, reference limits, and risk classification boundaries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailHealth"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/domains":{"post":{"operationId":"createDomain","summary":"Create a sending domain","description":"Registers a new sending domain and returns the DNS records to publish\nfor it. The DKIM TXT record proves ownership, and together with the\nreturn-path CNAME (which also covers SPF, so no separate SPF record is\nneeded) and a DMARC policy it gates sending. The tracking CNAME is\noptional and gates branded link tracking only. Publish the records at\nyour DNS provider, then check progress with\n[Trigger domain verification](/docs/api/reference/verify-domain). Published\nrecords are also re-checked for you automatically. Setup walkthrough:\n[Sending domains](/docs/guides/email/sending-domains).\n\nThe domain starts in `pending` status. A domain already registered in\nthis workspace returns `409`, and creation beyond your organization's\ndomain quota returns `422` `E10000`. A domain that never verifies\nownership is removed after about 14 days, with a reminder email first.\n","tags":["domains"],"x-audiences":["public","command"],"x-snippet-key":"domains.create","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"examples":{"domain-bounce":{"summary":"A sending domain with a custom bounce return path","value":{"domain":"mail.acme.com","return_path":{"name":"bounce"}}},"domain-tracking":{"summary":"A sending domain with a custom click-tracking host","value":{"domain":"mail.acme.com","tracking":{"name":"click"}}}},"schema":{"$ref":"#/components/schemas/DomainCreate"}}}},"responses":{"201":{"description":"Domain created.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"get":{"operationId":"listDomains","summary":"List sending domains","description":"Returns all sending domains for the current workspace, newest first by default. Each item is the full domain object, including capability statuses and `dns_records`, so no per-domain follow-up read is needed. Filter with `name` to find a specific domain.\n","tags":["domains"],"x-audiences":["public","command"],"x-snippet-key":"domains.list","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"name","in":"query","required":false,"description":"Substring match against the domain name (case-insensitive).","schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"description":"Field to sort by. Defaults to `created_at`.","schema":{"type":"string","enum":["created_at","name"],"default":"created_at"}},{"$ref":"#/components/parameters/OrderDesc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"$ref":"#/components/parameters/IncludeTotal"}],"responses":{"200":{"description":"A page of sending domains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/domains/{domain_id}":{"patch":{"operationId":"updateDomain","summary":"Update a sending domain","description":"Updates settings and configuration on a sending domain. `settings`\nchanges apply immediately. Changes to `return_path`, `tracking`, or\n`dkim` on a verified capability are staged: the current configuration\nkeeps serving until the new one's DNS records verify, then the change\nis promoted automatically. Staged values are visible under\n`capabilities.*.pending`. The records to publish appear in\n`dns_records` with `state: pending`.\n\nInvalid combinations are rejected. Enabling tracking toggles without a\ntracking domain, or removing the tracking domain while a toggle is on,\nreturns `409`. Enabling inbound receiving has verification\nprerequisites that return `422`. Each rule is detailed on its field.\n","tags":["domains"],"x-audiences":["public","command"],"x-snippet-key":"domains.update","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"description":"ID of the domain to update.","schema":{"$ref":"#/components/schemas/DomainID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainUpdate"}}}},"responses":{"200":{"description":"Domain updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"get":{"operationId":"getDomain","summary":"Get a sending domain","description":"Returns the domain with its capability statuses and every DNS record's current verification state. This read reports the stored result of the last check. To run a fresh DNS check, use [Trigger domain verification](/docs/api/reference/verify-domain).\n","tags":["domains"],"x-audiences":["public","command"],"x-snippet-key":"domains.get","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"description":"ID of the domain to fetch.","schema":{"$ref":"#/components/schemas/DomainID"}}],"responses":{"200":{"description":"The sending domain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"delete":{"operationId":"deleteDomain","summary":"Delete a sending domain","description":"Removes the domain and revokes its sender authorization. New sends from a deleted domain are rejected. Historical statistics and events for past sends from this domain are preserved.\n","tags":["domains"],"x-audiences":["public","command"],"x-snippet-key":"domains.delete","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"description":"ID of the domain to delete.","schema":{"$ref":"#/components/schemas/DomainID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Domain deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/domains/{domain_id}/events":{"get":{"operationId":"listDomainEvents","summary":"List domain events","description":"Returns the domain's activity history, newest first by default:\n\n- Registration.\n- A configuration change to settings, return path, or tracking.\n- A verification status transition for the domain or one of its individual DNS records.\n\nUse it to see when and why a domain's status changed, rather than\npolling [Get a sending domain](/docs/api/reference/get-domain).\n","tags":["domains"],"x-audiences":["public"],"x-snippet-key":"none","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"description":"ID of the domain whose events to list.","schema":{"$ref":"#/components/schemas/DomainID"}},{"name":"sort","in":"query","required":false,"description":"Field to sort by. Defaults to `created_at`.","schema":{"type":"string","enum":["created_at"],"default":"created_at"}},{"$ref":"#/components/parameters/OrderDesc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"A page of domain events.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainEventList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["make","n8n"]}},"/v1/email/domains/{domain_id}/verify":{"post":{"operationId":"verifyDomain","summary":"Verify a domain","description":"Runs a fresh DNS check across the domain's records (DKIM, return path,\nDMARC, tracking, inbound MX, and any staged changes) and returns the\nupdated domain. Use it for an immediate result after publishing or\ncorrecting records. [Get a sending domain](/docs/api/reference/get-domain)\nonly reports the last stored result. Published records are also re-checked\nfor you automatically in the background.\n\nA `200` with records still `pending` is not a failure: the records were\nnot found yet, which is normal while DNS propagates (minutes to hours).\nRecently verified records are not re-queried, so the call is safe to\nrepeat while you wait.\n","tags":["domains"],"x-audiences":["public","command"],"x-snippet-key":"domains.verify","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"description":"ID of the domain to verify.","schema":{"$ref":"#/components/schemas/DomainID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Verification result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/domains/{domain_id}/tracking/release":{"post":{"operationId":"releaseTrackingDomain","summary":"Release link tracking configuration","description":"Removes the link tracking configuration from this domain and releases\nthe claim on its tracking subdomain, so another organization can\nconfigure the same hostname. Click and open tracking are switched off\nas part of the release. Tracking links in previously delivered messages\nstop resolving once no other domain in your organization uses the same\ntracking hostname.\n\nThis is the hard removal. To swap or remove tracking while keeping\npreviously sent links working, use\n[Update a sending domain](/docs/api/reference/update-domain) instead,\nwhich retires the old records gradually. A domain with no tracking\nconfigured returns `422`.\n","tags":["domains"],"x-audiences":["public"],"x-snippet-key":"none","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"description":"ID of the domain to release tracking for.","schema":{"$ref":"#/components/schemas/DomainID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Updated domain after tracking release.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","n8n"]}},"/v1/email/domains/{domain_id}/dns-records/share":{"post":{"operationId":"shareDomainDnsRecords","summary":"Share a domain's DNS records by email","description":"Emails the domain's current DNS records to up to three recipients, so a\ncolleague who manages your DNS can publish them without an account.\nOne email is sent: the first recipient receives it directly and the\nrest are copied. It lists each record's type, name, value, and current\nverification status, and names who requested it.\n\nAn invalid recipient address, or none, returns `422`. Rate limited per\nuser (default 5 calls per hour). Further calls return `429`.\n","tags":["domains"],"x-audiences":["public"],"x-snippet-key":"none","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"description":"ID of the domain whose DNS records to share.","schema":{"$ref":"#/components/schemas/DomainID"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareDomainDnsRequest"}}}},"responses":{"204":{"description":"DNS records shared."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","n8n"]}},"/v1/email/suppressions":{"get":{"operationId":"listSuppressions","summary":"List email suppressions","description":"Returns the workspace's suppressed email addresses as a paginated list, newest first. Pass an address in the `email` parameter to narrow the page to that address.\n\nThe `email` filter matches by prefix rather than exactly, so `bob@example.com` also returns a suppressed `bob@example.com.au`. Compare the `email` on each record before treating the address you asked about as suppressed.\n\nAn address can appear more than once because Bird keeps one suppression record per reason. Delivery stays blocked while any blocking record for the address remains.\n","tags":["email-suppressions"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"suppressions.list","parameters":[{"name":"email","in":"query","required":false,"description":"Case-insensitive prefix filter on the address. Returns every suppression whose address starts with this value. A full address finds that address's records, while a fragment such as `alice` finds every address beginning with it. The same address can match several records, one per suppression reason.\n","schema":{"type":"string","minLength":1},"example":"user@example.com"},{"name":"reason","in":"query","required":false,"description":"Return only suppressions with this reason:\n\n- `hard_bounce`: Delivery permanently failed.\n- `complaint`: The recipient reported a message as spam.\n- `unsubscribe`: The recipient opted out. Deprecated: unsubscribes are now\n  recorded as messaging preferences rather than suppressions, so no new\n  records carry this reason. The filter returns legacy records until they\n  are moved to messaging preferences.\n- `manual`: Added through the API or dashboard.\n","schema":{"$ref":"#/components/schemas/SuppressionReasonFilter"}},{"name":"scope_type","in":"query","required":false,"description":"Return only suppressions with this scope.\n\nEvery suppression is workspace-wide, so `workspace` returns all of\nthem without narrowing the results. The other five values,\n`category`, `audience`, `topic`, `contact` and `domain`, always come\nback with an empty page.\n","schema":{"$ref":"#/components/schemas/SuppressionScopeTypeFilter"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of suppressions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"post":{"operationId":"createSuppression","summary":"Create an email suppression","description":"Adds an email address to the suppression list, stopping all email to it. The record is created with reason `manual` and blocks every message category, including transactional.\n\nAdding is idempotent: a `201` means a new record was created, and a `200` means a `manual` suppression for the address already existed and is returned unchanged. An address suppressed for another reason (for example `hard_bounce`) gets a separate `manual` record, and delivery stays blocked until every blocking record is removed.\n","tags":["email-suppressions"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"suppressions.add","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionCreate"},"example":{"email":"jane@example.com"}}}},"responses":{"200":{"description":"A manual suppression for this address already existed. The existing record is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Suppression"}}}},"201":{"description":"Suppression created.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Suppression"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/suppressions/{suppression_id}":{"parameters":[{"name":"suppression_id","in":"path","required":true,"description":"ID of the suppression record, as returned when the suppression was created or listed.\n","schema":{"$ref":"#/components/schemas/SuppressionID"},"example":"sup_01krdgeqcxet5s7t44vh8rt9mg"}],"get":{"operationId":"getSuppression","summary":"Get an email suppression","description":"Returns one suppression record:\n\n- The address.\n- Why it is suppressed (`reason`).\n- How the record came to exist (`origin`).\n- Which message categories it blocks (`applies_to`).\n\nTo find a record when you only know the address, use `GET /v1/email/suppressions` with the `email` parameter. An ID that does not exist in the workspace returns `404`.\n","tags":["email-suppressions"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"suppressions.get","responses":{"200":{"description":"Suppression object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Suppression"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"delete":{"operationId":"deleteSuppression","summary":"Delete an email suppression","description":"Permanently deletes the selected suppression record. Other blocking records for the address remain in effect. Deletion cannot be undone. If the address hard-bounces or the recipient complains again, a new suppression is created automatically.\n\nMost records exist because the address bounced or complained. Resuming delivery without cause can harm sender reputation. An address suppressed for several reasons has one record per reason. Delete each blocking record to re-enable delivery. To find a record by address, use `GET /v1/email/suppressions` with the `email` parameter. An ID that does not exist in the workspace returns `404`.\n\nA record with reason `complaint` can only be deleted by a signed-in dashboard user; an API key gets `422` (`SuppressionNotRemovableByAPIKey`). `hard_bounce` and `manual` records are unaffected and stay removable either way.\n","tags":["email-suppressions"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"suppressions.remove","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Suppression deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/competitive/watchlist":{"get":{"x-snippet-key":"email.competitive.watchlist.get","operationId":"getEmailCompetitiveWatchlist","summary":"Get the competitor watchlist with its latest figures","description":"Returns every competitor brand on the workspace's watchlist, plus a row for\nyour own sending, each with estimated send volume and how it changed against\nthe previous period, how often the brand sends, inbox placement, estimated\nread rate, audience overlap with you, and the most recent campaign seen.\n\nFigures about a competitor are estimates from an email panel, which observes a\nsample of real inboxes and scales what it sees up to a whole audience. They\nare fetched while the request runs, so they are current rather than cached,\nand two requests minutes apart can differ slightly. Figures about your own\nsending are counted rather than estimated wherever that is possible. The\n`provenance` object on each row records which source each figure came from.\n\nA figure reads `null` when it is unavailable for that brand, so a `0` always\nmeans a real measurement. When a whole row has no figures, `panel_status` says\nwhy: the panel may not track the brand's sending domain, may track it but have\nseen no mail in the period, or may have been briefly unreachable.\n\n`esp` and `list_size` are the exception, and are always `null` here. Read a\nsingle brand to get them.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-competitive"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"$ref":"#/components/parameters/EmailCompetitiveRange"}],"responses":{"200":{"description":"The watchlist with its figures for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCompetitiveWatchlist"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/competitive/watchlist/notable":{"get":{"x-snippet-key":"email.competitive.watchlist.notable","operationId":"getEmailCompetitiveNotableCampaigns","summary":"Get the notable campaigns across watched brands","description":"Returns the campaigns worth a second look across every brand the workspace watches,\nsurfaced for what they did rather than for when they were sent.\n\nEach campaign carries the signal that surfaced it: an unusually big send for its brand,\na campaign read unusually well for its brand, or heavy spam placement at one mailbox\nprovider. Every signal compares a campaign against its own brand's history, never\nagainst your other brands, so several brands can carry the same signal in one period.\n\nUp to 100 findings are returned. Selection takes turns across brands in watchlist order\nuntil the response is full, prioritizing spam placement, biggest sends, then read-rate\nstandouts within a brand. Returned findings retain watchlist, signal, domain, and source\norder. Your own sending is never included.\n\nAn empty list is an ordinary answer, not a failure: a signal only fires on a campaign\nthat stands out for its own brand, and a watchlist of steady senders produces nothing.\nCheck `panel_status` to tell that apart from the panel being unreachable.\n\nThis is a separate request from the watchlist on purpose, so a slow or degraded panel\nread cannot delay the watchlist itself.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-competitive"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"$ref":"#/components/parameters/EmailCompetitiveRange"}],"responses":{"200":{"description":"The notable campaigns for the period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCompetitiveNotableFeed"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/competitive/watchlist/brands":{"post":{"x-snippet-key":"email.competitive.watchlist.brands.create","operationId":"createEmailCompetitiveWatchlistBrand","summary":"Add a competitor brand to the watchlist","description":"Adds a brand to the workspace's watchlist so its figures appear next to your\nown. Pass a `brand_id` from a brand search.\n\nAdding a brand records the one domain the panel sees the most of its mail\nfrom, and every figure reported for the brand describes that domain. A brand\nthat mails from several domains therefore reports less than its full volume.\nA brand the panel has never seen send cannot be measured at all and is\nrefused.\n\nHow many brands can be watched is capped per organization, counted across\nevery workspace it owns, so the same competitor watched from two workspaces\nuses two of the allowance.\n\nAPI-key, OAuth, and service-account calls require Insights preview access for your organization.\n","tags":["email-competitive"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCompetitiveWatchlistBrandCreate"},"example":{"brand_id":"81531"}}}},"responses":{"201":{"description":"The brand was added to the watchlist.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCompetitiveWatchlistBrand"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/competitive/watchlist/brands/{watchlist_brand_id}":{"parameters":[{"name":"watchlist_brand_id","in":"path","required":true,"description":"The watchlist entry to act on.","schema":{"$ref":"#/components/schemas/CompetitiveWatchlistBrandID"}}],"get":{"x-snippet-key":"email.competitive.watchlist.brands.get","operationId":"getEmailCompetitiveBrand","summary":"Get a watched brand's figures","description":"Returns one watched brand's figures for the period, together with how each mailbox\nprovider treated its mail and how that compares with your own.\n\nThe headline figures are the ones the watchlist reports for this brand, derived the same\nway from the same fields. Estimated volume can differ very slightly between the two\nviews, because each request asks the panel about a different set of domains and the panel\nscales its estimate per request. The figures the two views share are either rates or\nbuilt from raw counts, and are identical. The per-provider breakdown, `esp`, and\n`list_size` are available only here.\n\nEvery figure is an estimate from an email panel, fetched while the request runs,\nexcept your own inbox rate where noted.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-competitive"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"$ref":"#/components/parameters/EmailCompetitiveRange"}],"responses":{"200":{"description":"The brand's figures for the period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCompetitiveBrandProfile"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]},"delete":{"x-snippet-key":"email.competitive.watchlist.brands.delete","operationId":"deleteEmailCompetitiveWatchlistBrand","summary":"Remove a competitor brand from the watchlist","description":"Takes a brand off the workspace's watchlist and frees its place in the\norganization's allowance. Nothing about the brand is retained, so adding it\nagain starts a fresh entry.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-competitive"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"The brand was removed from the watchlist."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/competitive/watchlist/brands/{watchlist_brand_id}/campaigns":{"parameters":[{"name":"watchlist_brand_id","in":"path","required":true,"description":"The watchlist entry whose campaigns to return.","schema":{"$ref":"#/components/schemas/CompetitiveWatchlistBrandID"}}],"get":{"x-snippet-key":"email.competitive.watchlist.brands.campaigns.list","operationId":"getEmailCompetitiveBrandCampaigns","summary":"Get the campaigns a watched brand sent","description":"Returns a page of campaigns an email panel observed a watched brand sending over\nthe period, in the requested order. Sampled statistics describe eligible campaigns\nin the first 300 newest panel rows for each tracked domain, independently of the page.\n\nEach campaign is one send the panel saw reach real inboxes, so the subject and\ntiming are what the brand's subscribers received rather than anything the brand\npublished. Volume and read rate are panel estimates, fetched while the request\nruns.\n\nThe panel folds a day's low-volume sending into a single synthetic entry with no\ncreative and no volume. Those are left out, so the count here is lower than the\nnumber of rows the panel holds and describes campaigns a person would recognise\nas campaigns.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-competitive"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"$ref":"#/components/parameters/EmailCompetitiveRange"},{"name":"sort","in":"query","required":false,"description":"Field to sort campaigns by.","schema":{"$ref":"#/components/schemas/EmailCompetitiveCampaignSort"}},{"$ref":"#/components/parameters/OrderDesc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"The brand's campaigns for the period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCompetitiveCampaignFeed"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/competitive/watchlist/brands/{watchlist_brand_id}/campaigns/{campaign_id}":{"parameters":[{"name":"watchlist_brand_id","in":"path","required":true,"description":"The watched brand that sent the campaign.","schema":{"$ref":"#/components/schemas/CompetitiveWatchlistBrandID"}},{"name":"campaign_id","in":"path","required":true,"description":"The campaign to return, taken from the brand's campaign list.","schema":{"type":"string","minLength":1,"maxLength":19,"pattern":"^[0-9]+$","example":"3914827265"}}],"get":{"x-snippet-key":"email.competitive.watchlist.brands.campaigns.get","operationId":"getEmailCompetitiveBrandCampaign","summary":"Get one campaign a watched brand sent","description":"Returns one campaign an email panel observed a watched brand sending. It carries the\nsame figures the brand's campaign list gives for that campaign, so a page can open one\ncampaign without reading the whole list first.\n\nThe campaign has to be one the brand in the path sent. An identifier that belongs to\nanother brand's campaign comes back as not found, whether or not the panel holds it. A\nworkspace can read the campaigns of the brands it watches, and no others.\n\nThe panel folds a day of low-volume sending into one synthetic entry, and the campaign\nlist leaves those out. They stand for a day of sending rather than for a campaign\nanyone sent, so they come back as not found here as well.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-competitive"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"responses":{"200":{"description":"The campaign.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCompetitiveCampaign"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/competitive/watchlist/brands/{watchlist_brand_id}/send-time":{"parameters":[{"name":"watchlist_brand_id","in":"path","required":true,"description":"The watchlist entry whose sending pattern to return.","schema":{"$ref":"#/components/schemas/CompetitiveWatchlistBrandID"}}],"get":{"x-snippet-key":"email.competitive.watchlist.brands.sendTime","operationId":"getEmailCompetitiveBrandSendTime","summary":"Get when a watched brand sends","description":"Returns how a watched brand's sending is spread across the week: one figure per\nweekday and hour of the day, over the last 90 days, with the hour of the day it\nsends most of its mail in.\n\nEach hour counts when the brand **sent**, not when its subscribers opened or\nreceived the mail. It answers \"when does this brand mail its list\", which is what\na competing send has to be timed against. It says nothing about how busy a\nsubscriber's inbox was at that hour.\n\nHours are reported in the timezone you ask for, echoed back in `timezone`, and the\nweek is folded into that zone before it is totalled, so a send at 02:00 UTC on\nMonday counts as Sunday evening for a reader in New York, which is when it arrived\nfor them. Label an axis from `timezone` rather than from what you asked for: a\nresponse the panel could not answer reports UTC regardless.\n\nExpect the weekday axis to look flat. For most brands the hour of the day is where\nthe pattern is, and which day of the week it is barely moves the figure; a grid\nwith little variation down its rows is a real finding about how the brand mails\nrather than a gap in the data.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-competitive"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"$ref":"#/components/parameters/EmailCompetitiveTimezone"}],"responses":{"200":{"description":"The brand's sending pattern for the period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCompetitiveSendTimeGrid"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/competitive/brands/search":{"get":{"x-snippet-key":"email.competitive.brands.search","operationId":"searchEmailCompetitiveBrands","summary":"Search brands to add to the watchlist","description":"Searches for brands by name and returns the ones that can be watched, each\nwith the domain its figures would describe and the identifier to add it with.\nPaste a domain instead of a name to find the brand that sends from it.\n\nBrands the panel has never seen send are left out, since no figure could be\nreported for them. An empty result for a real brand name therefore means the\npanel does not track that brand rather than that the search failed.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-competitive"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"name":"q","in":"query","required":true,"description":"A brand name, or a sending domain to look up the brand behind it.","schema":{"type":"string","minLength":2,"maxLength":128,"example":"everlane"}}],"responses":{"200":{"description":"Brands matching the search.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCompetitiveBrandSearchResults"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/competitive/volume-series":{"get":{"x-snippet-key":"email.competitive.volumeSeries","operationId":"getEmailCompetitiveVolumeSeries","summary":"Get send volume over time for watched brands","description":"Returns daily send volume for the watched brands you name, plus a line for your\nown sending, over one shared axis. Intended for a chart comparing a handful of\ncompetitors against yourself rather than the whole watchlist: each brand you name\nand each month of range adds to how long the request takes, so ask for the few\nyou are plotting.\n\nCompetitor volume is an estimate from an email panel, fetched while the request\nruns. Your own line counts messages accepted for delivery. The `source` field on each line records which of the two it is, and\nthe two are not measuring the same thing, so a chart putting them on one axis\nshould say so.\n\nEvery line carries one point per day of the period, oldest first, with a `0` for\na day nothing was observed, so the lines need no aligning before plotting. The last\npoint is the last whole UTC day, not the one in progress, so your own line and a\ncompetitor's cover the same days. A line with no points at all has a `panel_status`\nsaying why.\n\nAPI-key calls require Insights preview access for your organization.\n","tags":["email-competitive"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","dashboard","command"],"parameters":[{"$ref":"#/components/parameters/EmailCompetitiveRange"},{"name":"brand_ids","in":"query","required":false,"description":"Which watched brands to plot, in the order you want the lines. Omit to get only your own line. An id your workspace does not watch is rejected rather than skipped, so a chart cannot quietly lose a line.\n","style":"form","explode":false,"schema":{"type":"array","minItems":1,"maxItems":20,"uniqueItems":true,"items":{"$ref":"#/components/schemas/CompetitiveWatchlistBrandID"}}}],"responses":{"200":{"description":"The requested lines for the period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCompetitiveVolumeSeries"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/templates":{"post":{"operationId":"createEmailTemplate","summary":"Create an email template","description":"Creates a template and its first editable draft.\n\nSend the template's `slug` (the name you send the template by), an optional display name that\ndefaults to the slug, a category, the authoring format (`source`), and the draft's content in one\nor more languages. Leave the content out to start from an empty draft. A slug already used in the\nworkspace returns a conflict.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateCreate"}}}},"responses":{"201":{"description":"Email template created.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplate"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]},"get":{"operationId":"listEmailTemplates","summary":"List email templates","description":"Returns a paginated list of email templates, newest first.\n\nThe list covers both the workspace's own templates and our built-in `system` templates. Use\n`scope` to get only one of the two, or leave it out to get both. When you leave it out, the\nworkspace's own templates come first, newest first, and our built-in templates fill the rest of\nthe list once the workspace's templates run out.\n\nFilter further by category or authoring format, or search with `q`, a case-insensitive substring\nmatch against the template's slug, name, and description.\n\nOur built-in templates come in five visual themes. Each theme ships its own set of eight\nemails, and the sets overlap only partly. Use `theme` to see one of them. A template your\nworkspace authored has no theme, so naming one returns our built-ins alone.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.templates.list","parameters":[{"name":"scope","in":"query","required":false,"description":"Filter by who owns the template. Use `system` for our built-in templates and `workspace` for the ones your workspace created. Leave it out to get both.","schema":{"$ref":"#/components/schemas/TemplateScope"}},{"name":"category","in":"query","required":false,"description":"Return only `transactional` or `marketing` templates; omit to return both categories.","schema":{"$ref":"#/components/schemas/EmailTemplateCategory"}},{"name":"source","in":"query","required":false,"description":"Return only templates authored in this format.","schema":{"$ref":"#/components/schemas/EmailTemplateSource"}},{"name":"theme","in":"query","required":false,"description":"Filter by the visual theme a built-in template is designed in. Only our built-in templates have a theme, so naming one returns built-ins alone.","schema":{"$ref":"#/components/schemas/EmailTemplateThemeFilter"}},{"name":"q","in":"query","required":false,"description":"A case-insensitive substring search across the template's slug, name, and description.","schema":{"type":"string","minLength":1}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of email templates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/email/templates/{template_ref}":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's id (`emt_…`) or slug. On read, a built-in `system` template's `bird_` slug also resolves here. Write operations (update, delete) accept a workspace template only, because a `system` template is immutable.\n","schema":{"type":"string","minLength":1},"example":"bird_welcome"}],"get":{"operationId":"getEmailTemplate","summary":"Get an email template","description":"Returns a template's metadata, language states, sendable languages, draft revision, and draft and published version IDs. Read a version's language to retrieve content.\n\nAccepts a workspace template ID (`emt_…`) or a built-in `system` template's `bird_` slug. A `system` template has `null` for the workspace, draft, revision, and timestamp fields.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","responses":{"200":{"description":"The requested email template metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplate"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]},"patch":{"operationId":"updateEmailTemplate","summary":"Update an email template","description":"Updates a template's metadata and draft settings, such as its name or default language. Only the fields you send are changed.\n\nContent is not edited here: save a language on the draft version instead.\n\nSend the draft `revision` you last read. If someone else changed the draft first, including by saving a language, the revision is stale and the request returns a conflict so you can reload and retry.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateUpdate"}}}},"responses":{"200":{"description":"The template with the updated metadata and draft settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplate"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]},"delete":{"operationId":"deleteEmailTemplate","summary":"Delete an email template","description":"Deletes the template and all its versions. The slug becomes available for reuse in the workspace, and the deletion cannot be undone.\n\nA template can't be deleted while a broadcast that has not started sending still uses it, because a `scheduled` or `accepted` broadcast has not pinned the content it will send yet. [List the broadcasts blocking a template delete](/docs/api/reference/list-email-template-broadcasts) to see which ones those are. A broadcast that has already started sending does not block the delete: it pinned its version when it started, so it keeps sending the content it froze.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Email template deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/templates/{template_ref}/duplicate":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The source template to copy: a workspace template's id (`emt_…`) or slug, or a built-in `system` template's `bird_` slug. The copy is always a new workspace template.\n","schema":{"type":"string","minLength":1},"example":"welcome-email"}],"post":{"operationId":"duplicateEmailTemplate","summary":"Duplicate an email template","description":"Creates a new template by copying an existing one: one of your workspace\ntemplates, or a built-in `system` template (by its `bird_` slug). The copy\nis a new template with its own id and a single editable draft seeded from\nthe source's current content. It inherits the source's category, authoring\nformat (`source`), and description. Copying a workspace template also\ncarries over its default language and its missing-language policy, so the\ncopy behaves like what it was copied from. The copy starts unpublished.\n\nBy default the copy's slug derives from the source's slug, for example\n`welcome-email-copy`, with a numeric suffix added if that slug is already\ntaken. Supply `slug` to choose your own. A slug already in use in the\nworkspace returns a conflict.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateDuplicate"}}}},"responses":{"201":{"description":"The newly created template copy.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplate"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/templates/{template_ref}/preview":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template to preview: a workspace template's id (`emt_…`) or slug, or a built-in `system` template's `bird_` slug.\n","schema":{"type":"string","minLength":1},"example":"welcome-email"}],"post":{"operationId":"getEmailTemplatePreview","summary":"Get an email template preview","description":"Renders a template with the sample values you supply and returns the resulting subject, HTML, and plain-text bodies: the personalized email as it will look once sent. By default it renders the current draft, so you can check your changes before you submit it. Pass `version` to preview a specific published version instead; built-in `system` templates have no versions, so `version` on a `bird_` template returns a validation error. Pass `contact` to see the email the way one of your contacts would receive it. Works for your workspace templates and built-in `system` templates. Sample `parameters` are capped at 16 KB once serialized, and personalization that is not valid or not supported returns a validation error naming what to fix.\nThe response also reports what the HTML uses that mail clients remove, ignore, or render inconsistently. Each finding in `compatibility` names the pattern, the line and column it sits on, and what to use instead, and `compatibility_severity` reduces them to one word for the whole body. It is advisory: the preview renders either way.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplatePreviewRequest"}}}},"responses":{"200":{"description":"The rendered preview.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplatePreview"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/templates/{template_ref}/versions":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's id (`emt_…`) or slug. A built-in `system` template's `bird_` slug also resolves here, to its one permanently published version.\n","schema":{"type":"string","minLength":1},"example":"emt_01krdgeqcxet5s7t44vh8rt9mg"}],"get":{"operationId":"listEmailTemplateVersions","summary":"List email template versions","description":"Returns the template's versions as a cursor page (the current draft plus all published versions), newest first. Each entry names its languages without returning their content. Templates retain every language of every submitted version, so listing their content would grow the response with the template's history. Read a single version for its content.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"The template's versions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateVersionList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/templates/{template_ref}/broadcasts":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's id (`emt_…`) or slug. A built-in `system` template's `bird_` slug also resolves here, but a system template can never be referenced by a broadcast, so this always returns an empty page for one.\n","schema":{"type":"string","minLength":1},"example":"emt_01krdgeqcxet5s7t44vh8rt9mg"}],"get":{"operationId":"listEmailTemplateBroadcasts","summary":"List the broadcasts blocking a template delete","description":"Returns the broadcasts that block deleting this template, as a cursor page, newest first. Those are the ones that have not started sending, so they have not pinned the content they will send: `scheduled` and `accepted`. Clear every one of them and the delete goes through. Canceling clears either status; repointing at another template only works while the broadcast is `scheduled`, because an `accepted` broadcast is committed to send and an edit returns a conflict. A broadcast that is already sending is not listed and does not block the delete, because it froze its version when it started.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"The broadcasts blocking a delete of the template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateBroadcastList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/templates/{template_ref}/versions/{version_id}":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's id (`emt_…`) or slug. On read, a built-in `system` template's `bird_` slug also resolves here, to its one permanently published version. Discarding a draft requires a workspace template, because a `system` template has no draft and returns `404` `not_found_error`.\n","schema":{"type":"string","minLength":1},"example":"emt_01krdgeqcxet5s7t44vh8rt9mg"},{"name":"version_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/EmailTemplateVersionID"},"example":"emv_01krdgeqcxet5s7t44vh8rt9mg"}],"get":{"operationId":"getEmailTemplateVersion","summary":"Get an email template version","description":"Returns a single version of an email template:\n\n- Its lifecycle metadata (`status`, `version_number`, `published_at`).\n- The content it froze in every language.\n- The `variables` that content expects at send time.\n\nUse [List email template versions](/docs/api/reference/list-email-template-versions) to enumerate the draft and published versions. [Roll back an email template](/docs/api/reference/rollback-email-template) makes an earlier published version live again. Returns a `404 Not Found` error if the template or version does not exist in the workspace.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","responses":{"200":{"description":"Email template version object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateVersion"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]},"delete":{"operationId":"deleteEmailTemplateVersion","summary":"Delete an email template draft","description":"Discards the draft's unsaved work: its content resets to what is currently published (or to a single empty language when nothing has been published yet), so the draft matches what sends actually deliver again. Compare each language's `content_hash` against the published version's to see what a discard would throw away.\n\nThe draft itself remains, ready for new edits at a bumped `revision`, and what is published never changes. Only the draft can be discarded. Addressing a published version returns a `422` `validation_error`, because published versions are permanent history.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"The draft was reset."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/templates/{template_ref}/versions/{version_id}/languages":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's id (`emt_…`) or slug. A built-in `system` template's `bird_` slug also resolves here, to its one permanently published version.\n","schema":{"type":"string","minLength":1},"example":"emt_01krdgeqcxet5s7t44vh8rt9mg"},{"name":"version_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/EmailTemplateVersionID"},"example":"emv_01krdgeqcxet5s7t44vh8rt9mg"}],"get":{"operationId":"listEmailTemplateVersionLanguages","summary":"List a version's languages","description":"Returns every language the version holds, ordered by language tag, without the content itself, so listing a template with twenty-five languages stays small.\n\nEach entry has the language's `revision` (send it back when you save that language) and its `content_hash`, so you can tell which languages changed since you last read them and fetch only those. Read a single language for its subject and bodies.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","responses":{"200":{"description":"The version's languages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateLanguageList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/templates/{template_ref}/versions/{version_id}/languages/{language}":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's id (`emt_…`) or slug. On read, a built-in `system` template's `bird_` slug also resolves here. Writing or removing a language requires a workspace template, because a `system` template has no draft to edit and returns `404` `not_found_error`.\n","schema":{"type":"string","minLength":1},"example":"emt_01krdgeqcxet5s7t44vh8rt9mg"},{"name":"version_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/EmailTemplateVersionID"},"example":"emv_01krdgeqcxet5s7t44vh8rt9mg"},{"name":"language","in":"path","required":true,"schema":{"$ref":"#/components/schemas/LanguageTag"},"example":"pt-BR"}],"get":{"operationId":"getEmailTemplateLanguage","summary":"Get one language of a version","description":"Returns one language's content from a version (its subject and bodies) plus the `revision` to send back when saving it.\n\nWorks on any version, including a published version whose content is frozen. Returns a `404 Not Found` error if the version does not have the language.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","responses":{"200":{"description":"The version's content for this language.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateLanguage"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","sdk"]},"put":{"operationId":"upsertEmailTemplateLanguage","summary":"Upsert one language of a draft","description":"Saves one language's content on the template's draft, creating that language if the draft does not have it yet and replacing it in full if it does. Send the same content twice and the draft ends up the same way, so a sync job or CI run needs one call rather than a create-or-edit decision.\n\nSaving one language leaves every other language untouched, so a template with many languages can be edited a language at a time. Send the `revision` you last read to have a concurrent edit rejected with a `409` `conflict_error` instead of silently overwritten.\n\nOnly a draft can be saved: addressing a published version returns a `422` `validation_error`, because a published version never changes. A template holds at most 25 languages.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateLanguageUpsert"}}}},"responses":{"200":{"description":"The saved language's new revisions and fingerprint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateLanguageSaved"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]},"patch":{"operationId":"updateEmailTemplateLanguage","summary":"Update one language of a draft","description":"Changes part of one language's content on the template's draft: send only the fields you are changing and the rest keep their current values. Use this to fix a subject line without resending a megabyte of HTML.\n\nThe language must already exist on the draft. A language it does not have returns a `404` `not_found_error`. Save the language's full content instead to create it.\n\nSend the `revision` you last read to have a concurrent edit rejected with a `409` `conflict_error`. Only a draft can be edited. Addressing a published version returns a `422` `validation_error`.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateLanguageUpdate"}}}},"responses":{"200":{"description":"The edited language's new revisions and fingerprint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateLanguageSaved"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]},"delete":{"operationId":"deleteEmailTemplateLanguage","summary":"Delete one language from a draft","description":"Removes one language from the template's draft, along with its content. Every other language is untouched, and the change takes effect for sends when you next submit.\n\nThe draft's current default language cannot be removed on its own. Point the default at a different language first, then remove the old one; doing it in the other order returns a `422` `validation_error`. Removing a language the draft does not have returns a `404` `not_found_error`, and only a draft can be changed, so addressing a published version also returns a `422` `validation_error`.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"The language was removed."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/templates/{template_ref}/versions/{version_id}/rollback":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's id (`emt_…`) or slug. Rollback resets a draft, and a built-in `system` template has no draft, so its `bird_` slug returns `404` `not_found_error` here.\n","schema":{"type":"string","minLength":1},"example":"emt_01krdgeqcxet5s7t44vh8rt9mg"},{"name":"version_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/EmailTemplateVersionID"},"example":"emv_01krdgeqcxet5s7t44vh8rt9mh"}],"post":{"operationId":"rollbackEmailTemplate","summary":"Roll back an email template","description":"Makes an earlier published version the live version used by sends, and replaces the draft with that version's content so editing continues from it. The change is immediate. No new version is created, and the version history is unchanged. Include the draft `revision` you last read, so the request is rejected with a conflict if someone else changed the draft first. The draft's current content is replaced. Rolling back to the currently live version is allowed and resets the draft to it. Only published versions can be rolled back to.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateRollback"}}}},"responses":{"200":{"description":"The template, with its draft reset to the restored version's content.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplate"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/templates/{template_ref}/versions/{version_id}/submit":{"parameters":[{"name":"template_ref","in":"path","required":true,"description":"The template's id (`emt_…`) or slug. Submit freezes a draft, and a built-in `system` template has no draft, so its `bird_` slug returns `404` `not_found_error` here.\n","schema":{"type":"string","minLength":1},"example":"emt_01krdgeqcxet5s7t44vh8rt9mg"},{"name":"version_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/EmailTemplateVersionID"},"example":"emv_01krdgeqcxet5s7t44vh8rt9mh"}],"post":{"operationId":"submitEmailTemplateVersion","summary":"Submit an email template version","description":"Submits the template's draft as a new immutable, numbered version and\nmakes it the live version used by sends. The draft remains editable.\n\nEvery language the draft has must have a subject and a body, and the\ndefault language must be present. Submission is all or nothing: an\nincomplete language rejects the request and the response reports every\nlanguage error. A submit freezes every language the draft carries, so it\ncannot name a subset. The draft does not have to hold every language you\nplan to support: add more in a later version.\n\nSet `validate_only: true` to run the checks without creating a version.\nSet `expected_revision` to reject a concurrent edit. An unchanged draft is\nrejected. Submission is synchronous, and a returned `version` is live.\n","tags":["email-templates"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateSubmit"}}}},"responses":{"200":{"description":"The submit's outcome, including the frozen version unless this was a validation run.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateSubmitResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","sdk"]}},"/v1/email/inbound-addresses":{"get":{"operationId":"listInboundAddresses","summary":"List inbound addresses","description":"Returns a paginated list of the workspace's inbound addresses. An inbound address is one you forward a real mailbox to, so mail sent to that mailbox arrives as a received email.\n","tags":["email-inbound-addresses"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of inbound addresses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundAddressList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n"]},"post":{"operationId":"createInboundAddress","summary":"Create an inbound address","description":"Creates a new inbound address. Forward a real mailbox to the returned `address`, and every message sent there arrives as a received email.\n\nThe address works as soon as it is returned and requires no verification step.\n\nAn optional `label` records which source mailbox the address maps to.\n","tags":["email-inbound-addresses"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundAddressCreate"}}}},"responses":{"201":{"description":"Inbound address created.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundAddress"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n"]}},"/v1/email/inbound-addresses/{inbound_address_id}":{"parameters":[{"name":"inbound_address_id","in":"path","required":true,"description":"Inbound address identifier. Starts with `ina_`.","schema":{"$ref":"#/components/schemas/InboundAddressID"}}],"get":{"operationId":"getInboundAddress","summary":"Get an inbound address","description":"Returns a single inbound address: the `address` you forward a mailbox to, and the `label` you gave it. Use [List inbound addresses](/docs/api/reference/list-inbound-addresses) to find IDs. A deleted or unknown address returns `404`.\n","tags":["email-inbound-addresses"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"responses":{"200":{"description":"Inbound address object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundAddress"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n"]},"patch":{"operationId":"updateInboundAddress","summary":"Update an inbound address","description":"Updates the label on an inbound address. The address itself is fixed and cannot be changed.","tags":["email-inbound-addresses"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundAddressUpdate"}}}},"responses":{"200":{"description":"Inbound address updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundAddress"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n"]},"delete":{"operationId":"deleteInboundAddress","summary":"Delete an inbound address","description":"Deletes an inbound address. Mail forwarded to the address after deletion is no longer received. Previously received messages are unaffected.\n","tags":["email-inbound-addresses"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Inbound address deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n"]}},"/v1/email/inbound-messages":{"get":{"operationId":"listInboundMessages","summary":"List received emails","description":"Returns a paginated list of received emails for the workspace, newest first. Filter by sender address, by the inbound address that received the mail, or by a received-time range. Received emails are retained for 30 days.\n","tags":["email-inbound-messages"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"from","in":"query","required":false,"description":"Filter to messages whose sender address matches this value exactly. Sender addresses are stored lowercase, so pass the address in lowercase to match reliably.","schema":{"type":"string","format":"email","minLength":5},"example":"alice@example.com"},{"name":"inbound_address_id","in":"query","required":false,"description":"Filter to messages received on a specific inbound address.","schema":{"$ref":"#/components/schemas/InboundAddressID"}},{"name":"received_after","in":"query","required":false,"description":"Filter to messages received at or after this time.","schema":{"type":"string","format":"date-time"}},{"name":"received_before","in":"query","required":false,"description":"Filter to messages received at or before this time.","schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of received emails.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundEmailMessageList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n"]}},"/v1/email/inbound-messages/{inbound_message_id}":{"parameters":[{"name":"inbound_message_id","in":"path","required":true,"description":"Received email identifier. Starts with `rem_`.","schema":{"$ref":"#/components/schemas/InboundEmailMessageID"}}],"get":{"operationId":"getInboundMessage","summary":"Get a received email","description":"Returns the parsed metadata for a received email:\n\n- Addresses.\n- Subject.\n- Threading headers.\n- Authentication results.\n- The attachment manifest.\n\nContent is fetched separately:\n\n- Get the parsed body from [Get a received email's body](/docs/api/reference/get-inbound-message-body).\n- Get the original MIME from [Get a received email's raw content](/docs/api/reference/get-inbound-message-raw).\n- Get attachment bytes from [Get a received email's attachment](/docs/api/reference/get-inbound-message-attachment).\n\nReceived emails are retained for 30 days. An unknown or expired message returns `404`.\n","tags":["email-inbound-messages"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"responses":{"200":{"description":"Received email object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundEmailMessage"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n"]}},"/v1/email/inbound-messages/{inbound_message_id}/body":{"parameters":[{"name":"inbound_message_id","in":"path","required":true,"description":"Received email identifier. Starts with `rem_`.","schema":{"$ref":"#/components/schemas/InboundEmailMessageID"}}],"get":{"operationId":"getInboundMessageBody","summary":"Get a received email's body","description":"Returns the parsed, decoded body of a received email as JSON: the `html` and `text` parts, each `null` when the message had no such part. This is the reading-friendly form. For the exact original bytes (headers, signatures, full MIME structure) use [Get a received email's raw content](/docs/api/reference/get-inbound-message-raw). An unknown or expired message returns `404`. A message that exists but has no stored content returns both parts `null`.\n","tags":["email-inbound-messages"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"responses":{"200":{"description":"Parsed message body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundEmailMessageBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n"]}},"/v1/email/inbound-messages/{inbound_message_id}/raw":{"parameters":[{"name":"inbound_message_id","in":"path","required":true,"description":"Received email identifier. Starts with `rem_`.","schema":{"$ref":"#/components/schemas/InboundEmailMessageID"}}],"get":{"operationId":"getInboundMessageRaw","summary":"Get a received email's raw content","description":"Returns the original message exactly as received, in RFC 5322 (MIME) format, as a `message/rfc822` download. Use it when you need the exact bytes:\n\n- Full headers.\n- Signatures.\n- Your own MIME parsing.\n\nFor parsed fields use [Get a received email](/docs/api/reference/get-inbound-message) and [Get a received email's body](/docs/api/reference/get-inbound-message-body).\n\nReturns `404` when the message is unknown or its stored content has expired (received emails are retained for 30 days). A received message, including its attachments, cannot exceed 25 MB.\n","tags":["email-inbound-messages"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"responses":{"200":{"description":"The original message in RFC 5322 (MIME) format.","content":{"message/rfc822":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp"]}},"/v1/email/inbound-messages/{inbound_message_id}/attachments":{"parameters":[{"name":"inbound_message_id","in":"path","required":true,"description":"Received email identifier. Starts with `rem_`.","schema":{"$ref":"#/components/schemas/InboundEmailMessageID"}}],"get":{"operationId":"listInboundMessageAttachments","summary":"List a received email's attachments","description":"Returns metadata for each attachment on a received email:\n\n- The `attachment_id`.\n- The declared filename.\n- The MIME type.\n- The size.\n\nThe same manifest is embedded in [Get a received email](/docs/api/reference/get-inbound-message). Fetch an individual attachment's bytes with [Get a received email's attachment](/docs/api/reference/get-inbound-message-attachment).\n","tags":["email-inbound-messages"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"responses":{"200":{"description":"The attachments on the received email.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundAttachmentList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n"]}},"/v1/email/inbound-messages/{inbound_message_id}/attachments/{attachment_id}":{"parameters":[{"name":"inbound_message_id","in":"path","required":true,"description":"Received email identifier. Starts with `rem_`.","schema":{"$ref":"#/components/schemas/InboundEmailMessageID"}},{"name":"attachment_id","in":"path","required":true,"description":"Inbound attachment identifier. Starts with `rea_`.","schema":{"$ref":"#/components/schemas/InboundAttachmentID"}}],"get":{"operationId":"getInboundMessageAttachment","summary":"Get a received email's attachment","description":"Returns the raw bytes of a single attachment on a received email, served as `application/octet-stream`. The attachment's declared MIME type and filename are in its metadata, from [List a received email's attachments](/docs/api/reference/list-inbound-message-attachments), which is also where you find the `attachment_id`. Returns `404` when the attachment is not part of the message or its stored bytes have expired. A received message, including its attachments, cannot exceed 25 MB.\n","tags":["email-inbound-messages"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"responses":{"200":{"description":"The raw attachment bytes.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp"]}},"/v1/email/mailboxes":{"get":{"operationId":"listMailboxes","summary":"List mailboxes","description":"Returns a paginated list of the workspace's mailboxes, newest first. Search across addresses and display names with `q`, look a mailbox up by its exact address, or filter by lifecycle state or domain.\n","tags":["email-mailboxes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.list","parameters":[{"name":"address","in":"query","required":false,"description":"Filter to the mailbox with exactly this address.","schema":{"type":"string","format":"email","minLength":5},"example":"concierge@inbox.ai"},{"name":"q","in":"query","required":false,"description":"Case-insensitive search matching the mailbox's address or display name (substring).","schema":{"type":"string","minLength":1,"maxLength":320},"example":"concierge"},{"name":"state","in":"query","required":false,"description":"Return only `active` or `suspended` mailboxes. Use `include_deleted` for restorable deleted mailboxes.","schema":{"type":"string","enum":["active","suspended"]}},{"name":"domain","in":"query","required":false,"description":"Filter to mailboxes whose address is on this domain.","schema":{"type":"string","minLength":1},"example":"inbox.ai"},{"name":"include_deleted","in":"query","required":false,"description":"Include mailboxes deleted within their 30-day restore window. Defaults to false, so only active and suspended mailboxes are returned. A deleted mailbox has `deleted_at` set.","schema":{"type":"boolean","default":false}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of mailboxes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailboxList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"post":{"operationId":"createMailbox","summary":"Create a mailbox","description":"Creates a mailbox. The address is `local_part@domain`. The domain defaults to `inbox.ai`, Bird's shared mailbox domain, where creating the mailbox claims the address for your organization. It is first come, first served, and reserved to your organization even after the mailbox is deleted. You may instead name one of your own domains that is enabled for receiving email. An omitted local part is generated. On a custom domain, addresses of deleted mailboxes are quarantined for 30 days and remain reserved for your workspace.\n","tags":["email-mailboxes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.create","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"examples":{"mailbox-open":{"summary":"An agent mailbox that accepts mail from anyone","value":{"display_name":"My Agent","receive_policy":"open"}}},"schema":{"$ref":"#/components/schemas/MailboxCreate"}}}},"responses":{"201":{"description":"Mailbox created.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Mailbox"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/mailboxes/{mailbox_id}":{"parameters":[{"name":"mailbox_id","in":"path","required":true,"description":"Mailbox identifier. Starts with `mbx_`.","schema":{"$ref":"#/components/schemas/MailboxID"}}],"get":{"operationId":"getMailbox","summary":"Get a mailbox","description":"Returns a single mailbox by ID. A mailbox deleted within its 30-day restore window is still returned, with `deleted_at` set. Once the window closes it is permanently removed and returns `404`.\n","tags":["email-mailboxes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.get","responses":{"200":{"description":"Mailbox object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Mailbox"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"patch":{"operationId":"updateMailbox","summary":"Update a mailbox","description":"Updates a mailbox. The address and domain are immutable. Lowering the retention tier makes remembered messages older than the new cutoff eligible for deletion. If any exist, the request requires `confirm=true`. A tier change is applied to the mailbox's stored messages in the background; lowering the tier again while that is still being applied is refused with `E17050`. You can still raise it to a tier your plan permits.\n","tags":["email-mailboxes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.update","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"name":"confirm","in":"query","required":false,"description":"Set to `true` when lowering `retention_tier` would make remembered messages older than the new cutoff eligible for deletion. The request is rejected without it in that case.","schema":{"type":"boolean","default":false}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailboxUpdate"}}}},"responses":{"200":{"description":"Mailbox updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Mailbox"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"delete":{"operationId":"deleteMailbox","summary":"Delete a mailbox","description":"Deletes a mailbox. The address stops receiving mail immediately and enters quarantine. After 30 days, your workspace can bind the address to a new mailbox; the address remains reserved to your workspace. You can restore the mailbox for 30 days with `POST /email/mailboxes/{mailbox_id}/restore`. Normal message-retention expiry continues during that period. After 30 days, the mailbox and its remaining messages are permanently deleted. Returns `409` (`E01028`) if an enabled inbound route targets the mailbox. Disable, delete, or redirect those routes before retrying.\n","tags":["email-mailboxes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.delete","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Mailbox deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/mailboxes/{mailbox_id}/restore":{"parameters":[{"name":"mailbox_id","in":"path","required":true,"description":"Mailbox identifier. Starts with `mbx_`.","schema":{"$ref":"#/components/schemas/MailboxID"}}],"post":{"operationId":"restoreMailbox","summary":"Restore a deleted mailbox","description":"Restores receiving and access to unexpired messages. Returns `404` if deletion was 30 or more days ago or permanent erasure has started, and `409` if the mailbox is not deleted or its address is unavailable.\n","tags":["email-mailboxes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.restore","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Mailbox restored.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Mailbox"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/mailboxes/{mailbox_id}/stats":{"parameters":[{"name":"mailbox_id","in":"path","required":true,"description":"Mailbox identifier. Starts with `mbx_`.","schema":{"$ref":"#/components/schemas/MailboxID"}}],"get":{"operationId":"getMailboxStats","summary":"Get mailbox email statistics","description":"Returns the mailbox's sent and received email statistics over a time window: a period-wide summary plus a bucketed series. Sent-mail metrics have the same delivery, engagement, and latency breakdowns as the email stats endpoints. `received` counts mail that arrived at the mailbox.\n\nRows are bucketed by the time the event happened rather than the time the message was sent, so engagement that arrived during the period for a message sent earlier is counted here. Statistics start when the mailbox starts sending and receiving; the mailbox's all-time `message_count` and `thread_count` live on the mailbox resource itself.\n\n`from` and `to` accept either calendar days (`YYYY-MM-DD`, `day` granularity only) or RFC 3339 instants (`hour` granularity only). Both bounds must use the same form. Window caps depend on `granularity`: 365 days at `day`, 30 days at `hour`. Set `timezone` to report in a local zone instead of UTC.\n","tags":["email-mailboxes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.stats","parameters":[{"name":"from","in":"query","required":false,"description":"Inclusive start of the window: a calendar day (`YYYY-MM-DD`, `day` granularity only) or an RFC 3339 instant rounded down to the hour (`hour` granularity only). Interpreted in `timezone`, or in UTC when `timezone` is omitted. A numeric UTC offset is rejected when `timezone` is set; use a calendar day or a `Z` (UTC) instant. Must use the same form as `to`. Defaults to 30 days before `to` at `day` granularity and 7 days before `to` at `hour`, when omitted.\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-07-01"}},{"name":"to","in":"query","required":false,"description":"Inclusive end of the window: a calendar day (`YYYY-MM-DD`, `day` granularity only) or an RFC 3339 instant rounded down to the hour (`hour` granularity only). Interpreted in `timezone`, or in UTC when `timezone` is omitted. A numeric UTC offset is rejected when `timezone` is set; use a calendar day or a `Z` (UTC) instant. Must use the same form as `from`. Defaults to today (day) or the current hour (hour) in that timezone when omitted. Window may not exceed 365 days at `day` or 30 days at `hour` granularity.\n","schema":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","example":"2026-07-21"}},{"$ref":"#/components/parameters/StatsTimezone"},{"name":"granularity","in":"query","required":false,"description":"Granularity of the series: `day` (default) or `hour`. Echoed back as `period.grain`.\n","schema":{"type":"string","enum":["day","hour"],"default":"day"}}],"responses":{"200":{"description":"The mailbox's sent and received email statistics for the requested period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailboxStatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/mailboxes/{mailbox_id}/resume":{"parameters":[{"name":"mailbox_id","in":"path","required":true,"description":"Mailbox identifier. Starts with `mbx_`.","schema":{"$ref":"#/components/schemas/MailboxID"}}],"post":{"operationId":"resumeMailbox","summary":"Resume a suspended mailbox","description":"Resumes a mailbox that was suspended because the organization dropped below the plan needed to keep it active. The mailbox can send and receive again and its conversations and messages become visible. Resuming is refused when the organization has no room for another active mailbox, or for another custom inbox.ai handle, on its current plan. Free up a slot by deleting an active mailbox, or move to a bigger plan. Resuming a mailbox that is not suspended returns a conflict.\n","tags":["email-mailboxes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.resume","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Mailbox activated.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Mailbox"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/mailboxes/{mailbox_id}/receive-rules":{"parameters":[{"name":"mailbox_id","in":"path","required":true,"description":"Mailbox identifier. Starts with `mbx_`.","schema":{"$ref":"#/components/schemas/MailboxID"}}],"get":{"operationId":"listMailboxReceiveRules","summary":"List receive rules","description":"Returns a paginated list of the mailbox's receive rules, oldest first. Filter by action to see only allow or only block entries.\n","tags":["email-mailboxes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.receive_rules.list","parameters":[{"name":"action","in":"query","required":false,"description":"Return only `allow` or `block` rules; omit to return both actions.","schema":{"type":"string","enum":["allow","block"]}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of receive rules.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiveRuleList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"post":{"operationId":"createMailboxReceiveRule","summary":"Create a receive rule","description":"Adds an allow or block rule to the mailbox. Rules match the message's envelope sender. Domain entries also match subdomains. Block rules always win, both over allow rules and over the reply admission on allowlist mailboxes. An entry is either allow or block. Rules have no update operation, so a rule that needs the other action is a new rule and the old one is removed. A mailbox holds up to 200 rules.\n","tags":["email-mailboxes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.receive_rules.create","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiveRuleCreate"}}}},"responses":{"201":{"description":"Receive rule created.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiveRule"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/mailboxes/{mailbox_id}/receive-rules/{rule_id}":{"parameters":[{"name":"mailbox_id","in":"path","required":true,"description":"Mailbox identifier. Starts with `mbx_`.","schema":{"$ref":"#/components/schemas/MailboxID"}},{"name":"rule_id","in":"path","required":true,"description":"Receive-rule identifier. Starts with `erl_`.","schema":{"$ref":"#/components/schemas/ReceiveRuleID"}}],"delete":{"operationId":"deleteMailboxReceiveRule","summary":"Delete a receive rule","description":"Removes a receive rule from the mailbox. A rule's allow or block action cannot be changed after creation; delete it and create a replacement.\n","tags":["email-mailboxes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.receive_rules.delete","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Receive rule deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/inbound-routes":{"get":{"operationId":"listInboundRoutes","summary":"List inbound routes","description":"Returns a paginated list of the workspace's inbound routes in evaluation order, lowest priority number first. Filter by domain or by enabled state.\n","tags":["email-inbound-routes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"parameters":[{"name":"domain","in":"query","required":false,"description":"Filter to routes on this domain.","schema":{"type":"string","minLength":1},"example":"mail.acme.com"},{"name":"enabled","in":"query","required":false,"description":"Set to `true` for active routes or `false` for routes skipped during evaluation.","schema":{"type":"boolean"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of inbound routes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRouteList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["make","n8n"]},"post":{"operationId":"createInboundRoute","summary":"Create an inbound route","description":"Creates a routing rule that delivers matching inbound mail on one of your domains to a mailbox, or drops it. Routes are evaluated in order, lowest `priority` first, and a mailbox's own address always matches ahead of any route you create. Mail that no route matches is still received as a plain received email. The `domain` must be one of your workspace's inbound-enabled domains. Some fields depend on each other, and that dependency is described on the field itself. Breaking one of those rules returns `422`.\n","tags":["email-inbound-routes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRouteCreate"}}}},"responses":{"201":{"description":"Inbound route created.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRoute"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","n8n"]}},"/v1/email/inbound-routes/{route_id}":{"parameters":[{"name":"route_id","in":"path","required":true,"description":"Inbound route identifier. Starts with `ein_`.","schema":{"$ref":"#/components/schemas/EmailInboundRouteID"}}],"get":{"operationId":"getInboundRoute","summary":"Get an inbound route","description":"Returns a single inbound route:\n\n- Its match rule.\n- Its action.\n- Its target mailbox.\n- Its priority.\n- Its enabled state.\n\nUse [List inbound routes](/docs/api/reference/list-inbound-routes) to see every route in evaluation order.\n","tags":["email-inbound-routes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"responses":{"200":{"description":"Inbound route object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRoute"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["make","n8n"]},"patch":{"operationId":"updateInboundRoute","summary":"Update an inbound route","description":"Updates an inbound route. Omitted fields are unchanged. The `domain` is immutable. Fields that depend on each other are checked against the updated route, so a change that leaves it inconsistent (for example `deliver_to_mailbox` without a target mailbox) returns `422`. Disabled routes are kept but skipped during evaluation.\n","tags":["email-inbound-routes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRouteUpdate"}}}},"responses":{"200":{"description":"Inbound route updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRoute"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","n8n"]},"delete":{"operationId":"deleteInboundRoute","summary":"Delete an inbound route","description":"Deletes an inbound route. Mail the route matched falls through to the next route in priority order, or, when nothing matches, is received as a plain received email.\n","tags":["email-inbound-routes"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Inbound route deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","n8n"]}},"/v1/email/threads":{"get":{"operationId":"listEmailThreads","summary":"List threads","description":"Returns a paginated list of conversations across the workspace's mailboxes, most recently active first. `label` selects the view: the inbox (the default when omitted), `archive`, `spam`, `blocked`, or any custom label. You can also filter by mailbox, by linked contact, by participant address, or by a subject substring.\n\nThis listing filters; it does not search message content.\nConversations whose every message is trashed are excluded; restoring a message\nreturns the conversation to the list.\n\n`before` and `after` filter by time. To page through the results, pass the response cursors back as `starting_after` or `ending_before`.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.threads.list","parameters":[{"name":"mailbox_id","in":"query","required":false,"description":"Filter to conversations in a specific mailbox.","schema":{"$ref":"#/components/schemas/MailboxID"}},{"name":"contact_id","in":"query","required":false,"description":"Filter to conversations linked to a specific contact.","schema":{"$ref":"#/components/schemas/ContactID"}},{"name":"label","in":"query","required":false,"description":"Filter to conversations that have this label. Repeat the parameter to ask for more than one: only conversations that have every label you list are returned.\n\nA placement label picks a folder: `inbox`, `archive`, `spam`, or `blocked`. A custom label matches a conversation in any folder. Leave this out and you get the inbox.","schema":{"type":"array","maxItems":20,"items":{"type":"string","minLength":1,"maxLength":64}},"example":["urgent"]},{"name":"has_unread","in":"query","required":false,"description":"When `true`, only conversations with unread messages are returned. This filters on the conversation's unread state, so you can combine it with `label`, for example to get unread conversations in the archive. The `unread` label itself lives on individual messages; this filter uses the conversation's aggregate unread state.","schema":{"type":"boolean"}},{"name":"participant","in":"query","required":false,"description":"Conversations involving this address, matching the sender or any recipient. The match is case-insensitive and matches on any part of the address, so a fragment works as well as the whole address.","schema":{"type":"string","minLength":3,"maxLength":320},"example":"billing@acme.com"},{"name":"subject","in":"query","required":false,"description":"Conversations whose subject contains this text (case-insensitive).","schema":{"type":"string","minLength":3,"maxLength":256},"example":"quarterly invoice"},{"name":"after","in":"query","required":false,"description":"Filter to conversations whose most recent message is at or after this time. Use the response cursors for pagination.","schema":{"type":"string","format":"date-time"}},{"name":"before","in":"query","required":false,"description":"Filter to conversations whose most recent message is at or before this time. Use the response cursors for pagination.","schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of threads.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/email/threads/{thread_id}":{"parameters":[{"name":"thread_id","in":"path","required":true,"description":"Thread identifier. Starts with `thr_`.","schema":{"$ref":"#/components/schemas/ThreadID"}}],"get":{"operationId":"getEmailThread","summary":"Get a thread","description":"Returns a single conversation. Fetch the messages in the conversation with [List messages in a thread](/docs/api/reference/list-email-thread-messages). A thread whose retention tier has ended returns `410 Gone`.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.threads.get","responses":{"200":{"description":"Thread object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThread"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"patch":{"operationId":"updateEmailThread","summary":"Update a thread","description":"Applies label changes to a conversation, and links or unlinks a contact. Adding `spam` files the conversation, and its received messages, as spam. Adding `archive` files it away without deleting it. Adding `inbox`, or removing `spam`, `blocked`, or `archive`, returns it to the inbox, and its unread count recomputes to match. An archived conversation returns to the inbox by itself when a new message arrives that isn't spam or blocked; a junk reply or an outbound send leaves it archived. To block a sender going forward, add a receive rule instead. Any field you leave out stays unchanged.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.threads.update","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadUpdateRequest"}}}},"responses":{"200":{"description":"The updated thread.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThread"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"delete":{"operationId":"deleteEmailThread","summary":"Delete a thread","description":"Moves the conversation and all of its messages to the trash. Trashed messages are permanently deleted after 30 days, or sooner if the mailbox's retention period ends first. Pass `permanent=true` to permanently delete the conversation and its messages immediately.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.threads.delete","parameters":[{"name":"permanent","in":"query","required":false,"description":"Permanently delete the conversation and its messages immediately instead of moving them to the trash.","schema":{"type":"boolean","default":false}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Thread deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/threads/{thread_id}/messages":{"parameters":[{"name":"thread_id","in":"path","required":true,"description":"Thread identifier. Starts with `thr_`.","schema":{"$ref":"#/components/schemas/ThreadID"}}],"get":{"operationId":"listEmailThreadMessages","summary":"List messages in a thread","description":"Returns the messages in a conversation, newest first, both received and sent. To page through older messages, use `starting_after`. The sort order is fixed, so to render the messages in conversation order, reverse the page yourself.\n\nBy default, every message that is not in the trash is returned, whichever folder the conversation is in. Pass `label` to narrow the view instead: use `trash` for trashed messages, or any custom label.\n\nPass `include=extracted_text` to inline each message's extracted plain text. A thread whose retention tier has ended returns `410 Gone`.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.threads.messages.list","parameters":[{"name":"direction","in":"query","required":false,"description":"Filter to received (`inbound`) or sent (`outbound`) messages.","schema":{"$ref":"#/components/schemas/MessageDirection"}},{"name":"label","in":"query","required":false,"description":"Filter to messages that have this label. `trash` lists trashed messages. Any other label, whether that is `archive`, `spam`, `blocked`, `unread` or one of your own, lists the messages that have it and are not in the trash. When omitted, every message that is not trashed is returned, whichever folder the conversation is in.\n","schema":{"type":"string","minLength":1,"maxLength":64},"example":"unread"},{"name":"include","in":"query","required":false,"description":"Set to `extracted_text` to inline each message's extracted plain text.","schema":{"type":"string","enum":["extracted_text"]}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of messages in the conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadMessageList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/email/threads/{thread_id}/messages/{message_id}":{"parameters":[{"name":"thread_id","in":"path","required":true,"description":"Thread identifier. Starts with `thr_`.","schema":{"$ref":"#/components/schemas/ThreadID"}},{"name":"message_id","in":"path","required":true,"description":"Message ID (`rem_` for a received message, `em_` for a sent one).","schema":{"type":"string","minLength":1,"pattern":"^(rem|em)_[0-9a-hjkmnp-tv-z]{26}$"}}],"get":{"operationId":"getEmailThreadMessage","summary":"Get a message in a thread","description":"Returns a single message in a conversation, including its extracted plain text. Metadata and extracted text stay readable for the mailbox's retention tier. A message that has aged past its retention tier returns `410 Gone`. A message that exists but does not belong to this thread returns `404`.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.threads.messages.get","responses":{"200":{"description":"The message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadMessage"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"patch":{"operationId":"updateEmailThreadMessage","summary":"Update a message in a thread","description":"Applies read-state, label, and contact changes to a message in a conversation. Omitted fields are left unchanged. The read flag is only valid on received messages.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"x-snippet-key":"none","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadMessageUpdateRequest"}}}},"responses":{"200":{"description":"The updated message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadMessage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","n8n"]},"delete":{"operationId":"deleteEmailThreadMessage","summary":"Delete a message in a thread","description":"Moves the message to the trash. Trashed messages are permanently deleted after 30 days, or sooner if the mailbox's retention period ends first. Pass `permanent=true` to permanently delete the message immediately. When the last message in a conversation is permanently deleted, the conversation is deleted with it.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"x-snippet-key":"none","parameters":[{"name":"permanent","in":"query","required":false,"description":"Permanently delete the message immediately instead of moving it to the trash.","schema":{"type":"boolean","default":false}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Message deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["make","n8n"]}},"/v1/email/threads/{thread_id}/messages/{message_id}/body":{"parameters":[{"name":"thread_id","in":"path","required":true,"description":"Thread identifier. Starts with `thr_`.","schema":{"$ref":"#/components/schemas/ThreadID"}},{"name":"message_id","in":"path","required":true,"description":"Message ID (`rem_` for a received message, `em_` for a sent one).","schema":{"type":"string","minLength":1,"pattern":"^(rem|em)_[0-9a-hjkmnp-tv-z]{26}$"}}],"get":{"operationId":"getEmailThreadMessageBody","summary":"Get a thread message's original body","description":"Returns the original rendered HTML and plain-text body of a message in a conversation. The original body is available for 30 days after the message occurred. Later requests return `410 Gone`, while the message's extracted text stays readable on the message itself.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.threads.messages.body","responses":{"200":{"description":"The original rendered body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadMessageBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/threads/{thread_id}/messages/{message_id}/raw":{"parameters":[{"name":"thread_id","in":"path","required":true,"description":"Thread identifier. Starts with `thr_`.","schema":{"$ref":"#/components/schemas/ThreadID"}},{"name":"message_id","in":"path","required":true,"description":"Message ID (`rem_` for a received message, `em_` for a sent one).","schema":{"type":"string","minLength":1,"pattern":"^(rem|em)_[0-9a-hjkmnp-tv-z]{26}$"}}],"get":{"operationId":"getEmailThreadMessageRaw","summary":"Get a thread message's raw content","description":"Returns the original message exactly as received, in RFC 5322 (MIME) format. Available for received messages for 30 days after the message occurred. Later requests return `410 Gone`. Sent messages have no stored raw form and return `404`.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","responses":{"200":{"description":"The original message in RFC 5322 (MIME) format.","content":{"message/rfc822":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp"]}},"/v1/email/threads/{thread_id}/messages/{message_id}/attachments":{"parameters":[{"name":"thread_id","in":"path","required":true,"description":"Thread identifier. Starts with `thr_`.","schema":{"$ref":"#/components/schemas/ThreadID"}},{"name":"message_id","in":"path","required":true,"description":"Message ID (`rem_` for a received message, `em_` for a sent one).","schema":{"type":"string","minLength":1,"pattern":"^(rem|em)_[0-9a-hjkmnp-tv-z]{26}$"}}],"get":{"operationId":"listEmailThreadMessageAttachments","summary":"List a thread message's attachments","description":"Returns the attachments on a message in a conversation. Attachment bytes are downloadable for the mailbox's retention tier after the message occurred. Later requests return `410 Gone`, while the attachment metadata stays readable on the message's `attachment_manifest`.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.threads.messages.attachments","responses":{"200":{"description":"The message's attachments.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadMessageAttachmentList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/threads/{thread_id}/messages/{message_id}/attachments/{attachment_id}":{"parameters":[{"name":"thread_id","in":"path","required":true,"description":"Thread identifier. Starts with `thr_`.","schema":{"$ref":"#/components/schemas/ThreadID"}},{"name":"message_id","in":"path","required":true,"description":"Message ID (`rem_` for a received message, `em_` for a sent one).","schema":{"type":"string","minLength":1,"pattern":"^(rem|em)_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"attachment_id","in":"path","required":true,"description":"Attachment identifier. Starts with `ea_` for sent mail or `rea_` for received mail.","schema":{"type":"string","minLength":1,"pattern":"^(ea|rea)_[0-9a-hjkmnp-tv-z]{26}$"}}],"get":{"operationId":"getEmailThreadMessageAttachment","summary":"Get a thread message's attachment","description":"Returns the raw bytes of a single attachment on a conversation message. Works for both received messages (`rem_`) and sent messages (`em_`). Attachment bytes are downloadable for the mailbox's retention tier after the message occurred. Later requests return `410 Gone`.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"none","responses":{"200":{"description":"The raw attachment bytes.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp"]}},"/v1/email/threads/{thread_id}/messages/{message_id}/reply":{"parameters":[{"name":"thread_id","in":"path","required":true,"description":"Thread identifier. Starts with `thr_`.","schema":{"$ref":"#/components/schemas/ThreadID"}},{"name":"message_id","in":"path","required":true,"description":"Message ID (`rem_` for a received message, `em_` for a sent one).","schema":{"type":"string","minLength":1,"pattern":"^(rem|em)_[0-9a-hjkmnp-tv-z]{26}$"}}],"post":{"operationId":"replyEmailThreadMessage","summary":"Reply to a thread message","description":"Sends a reply to a specific message in a conversation, from the mailbox's own address. Recipients are derived from the message being replied to: for a received message, its Reply-To address when present, otherwise its From address; for a message the mailbox sent, its original To recipients. Set `reply_all` to copy the original To and Cc recipients in as `Cc`, leaving out the mailbox's own address. The subject and the threading headers that keep the reply in this conversation are set automatically, and the reply is recorded in the conversation. To reply to a conversation as a whole, target its newest received message.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.threads.messages.reply","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadMessageReplyRequest"}}}},"responses":{"202":{"description":"Reply accepted for asynchronous delivery and recorded in the conversation.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadMessage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"410":{"$ref":"#/components/responses/Gone"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["attio","cli","make","mcp","n8n","sdk"]}},"/v1/email/mailboxes/{mailbox_id}/messages":{"parameters":[{"name":"mailbox_id","in":"path","required":true,"description":"Mailbox identifier. Starts with `mbx_`.","schema":{"$ref":"#/components/schemas/MailboxID"}}],"get":{"operationId":"listMailboxMessages","summary":"List a mailbox's messages","description":"Returns the messages in a mailbox across all of its conversations,\nnewest first. By default only received messages in the inbox and all\nsent messages are returned. Pass `label` to see another view instead:\n\n- `archive`: Filed-away mail.\n- `spam` or `blocked`: Mail placed in either folder.\n- `trash`: Trashed messages.\n- `unread`: Messages you have not read yet, across all conversations.\n- Any custom label you have applied.\n\nFilter by direction or combined delivery status. Pass\n`include=extracted_text` to inline each message's extracted plain text.\n`before` and `after` filter by time. To page through results, pass the\nresponse cursors back as `starting_after` or `ending_before`.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"x-snippet-key":"none","parameters":[{"name":"label","in":"query","required":false,"description":"Filter to messages that have this label. `trash` lists trashed messages. Any other label, whether that is `archive`, `spam`, `blocked`, `unread` or one of your own, lists the messages that have it and are not in the trash. When omitted, received messages in the inbox and all sent messages are returned.\n","schema":{"type":"string","minLength":1,"maxLength":64},"example":"unread"},{"name":"direction","in":"query","required":false,"description":"Filter to received (`inbound`) or sent (`outbound`) messages.","schema":{"$ref":"#/components/schemas/MessageDirection"}},{"name":"status","in":"query","required":false,"description":"Filter sent messages by combined delivery status: `accepted`, `sent`, `delivered`, or `failed`.","schema":{"type":"string","enum":["accepted","sent","delivered","failed"]}},{"name":"after","in":"query","required":false,"description":"Filter to messages that occurred at or after this time. Page through results with `starting_after` or `ending_before` instead of this value.","schema":{"type":"string","format":"date-time"}},{"name":"before","in":"query","required":false,"description":"Filter to messages that occurred at or before this time. Page through results with `starting_after` or `ending_before` instead of this value.","schema":{"type":"string","format":"date-time"}},{"name":"include","in":"query","required":false,"description":"Set to `extracted_text` to inline each message's extracted plain text.","schema":{"type":"string","enum":["extracted_text"]}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of the mailbox's messages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadMessageList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["make","n8n"]},"post":{"operationId":"createMailboxMessage","summary":"Create a message from a mailbox","description":"Sends a new message from the mailbox's own address and starts a new conversation with it. The request mirrors the plain send request minus `from`, because the mailbox is who the message comes from. We set the RFC 5322 Message-ID, so later replies from the recipients thread back into the conversation automatically. The send is added to the mailbox's remembered messages and returned as the conversation's first message. A mailbox always sends immediately; scheduled sends are unavailable. A suspended mailbox cannot send and returns `403`.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.messages.create","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailMailboxComposeRequest"}}}},"responses":{"202":{"description":"Message accepted for asynchronous delivery and recorded as the first message of a new conversation.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailThreadMessage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/mailboxes/{mailbox_id}/labels":{"parameters":[{"name":"mailbox_id","in":"path","required":true,"description":"Mailbox identifier. Starts with `mbx_`.","schema":{"$ref":"#/components/schemas/MailboxID"}}],"get":{"operationId":"listMailboxLabels","summary":"List a mailbox's labels","description":"Returns the labels available in a mailbox. First, the built-in system\nlabels:\n\n- The placements `inbox`, `archive`, `spam`, `blocked`, and `sent`.\n- `trash`.\n- `unread`.\n\nThen, every custom label currently in use on its conversations and\nmessages. Apply and remove labels through the conversation and message\nupdate endpoints. These actions also create and remove custom labels. A\ncustom label exists while at least one message or conversation uses it.\n","tags":["email-threads"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"x-snippet-key":"email.mailboxes.labels","responses":{"200":{"description":"The mailbox's labels.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailMailboxLabelList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/email/smtp/configs":{"get":{"operationId":"listEmailSmtpConfigs","summary":"List SMTP configs","description":"Returns the SMTP send-option configs stored for API keys in this workspace, as a cursor page. Only keys with an explicitly stored config are included. A key with no config still accepts SMTP submissions, using your organization's default pool, the transactional category, and tracking on.\n","tags":["email-smtp-configs"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"parameters":[{"name":"sort","in":"query","required":false,"description":"Sort by `created_at`, the default and only supported field.","schema":{"type":"string","enum":["created_at"],"default":"created_at"}},{"$ref":"#/components/parameters/OrderDesc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"$ref":"#/components/parameters/IncludeTotal"}],"responses":{"200":{"description":"Paginated list of SMTP configs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailSmtpConfigList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["n8n"]}},"/v1/email/smtp/configs/{api_key_id}":{"parameters":[{"name":"api_key_id","in":"path","required":true,"description":"ID of the API key the config applies to, the same key your SMTP client authenticates with.\n","schema":{"$ref":"#/components/schemas/APIKeyID"},"example":"key_01krdgeqcxet5s7t44vh8rt9mg"}],"get":{"operationId":"getEmailSmtpConfig","summary":"Get an SMTP config","description":"Returns the SMTP send-option config stored for the given API key. Returns a `404` if you have not set a config for that key. The key still accepts SMTP submissions, using your organization's default pool, the transactional category, and tracking on.\n","tags":["email-smtp-configs"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"responses":{"200":{"description":"SMTP config object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailSmtpConfig"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["n8n"]},"patch":{"operationId":"updateEmailSmtpConfig","summary":"Update the SMTP config for an API key","description":"Sets the default send options applied to messages submitted over SMTP with this API key:\n\n- Which IP pool to send from.\n- The content category.\n- Tags.\n- Open and click tracking.\n\nSMTP submissions have no fields for these, so Bird stores them per key instead.\n\nA field you omit is left unchanged. If no config exists yet for this key, it is created first with the documented defaults (your organization's default pool, transactional category, no tags, tracking on), and omitted fields take those defaults. A key that does not belong to this workspace returns `404`.\n","tags":["email-smtp-configs"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailSmtpConfigUpdate"}}}},"responses":{"200":{"description":"The stored SMTP config.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailSmtpConfig"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["n8n"]}},"/v1/webhooks":{"post":{"operationId":"createWebhook","summary":"Create a webhook endpoint","description":"Registers an `active` webhook endpoint that receives the event types in `events` as signed HTTPS `POST` requests. See the [webhooks guide](/docs/guides/webhooks) for delivery, signing, and retry behavior.\n\nThe `201` response is the only response that includes the signing secret (`whsec_` prefix). Store it immediately; if it is lost, [rotate the signing secret](/docs/api/reference/rotate-webhook-secret).\n\nA non-HTTPS or non-public `url`, an unknown event type, or exceeding the organization's endpoint limit returns `422`.\n","tags":["webhooks"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-snippet-key":"webhooks.create","x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointCreate"}}}},"responses":{"201":{"description":"Created webhook endpoint, including its one-time signing secret.","headers":{"Idempotency-Replay":{"$ref":"#/components/headers/IdempotencyReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointCreated"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n","sdk"]},"get":{"operationId":"listWebhooks","summary":"List webhook endpoints","description":"Returns the workspace's webhook endpoints as a cursor-paginated list, newest first by default. Endpoint objects never include the signing secret; to inspect a single endpoint, use [Get a webhook endpoint](/docs/api/reference/get-webhook).\n","tags":["webhooks"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-snippet-key":"webhooks.list","x-audiences":["public","command"],"parameters":[{"name":"sort","in":"query","required":false,"schema":{"$ref":"#/components/schemas/WebhookSortField"}},{"$ref":"#/components/parameters/OrderDesc"},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"},{"$ref":"#/components/parameters/IncludeTotal"}],"responses":{"200":{"description":"Paginated list of webhook endpoints.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","mcp","n8n","sdk"]}},"/v1/webhooks/{webhook_id}":{"parameters":[{"name":"webhook_id","in":"path","required":true,"description":"ID of the webhook endpoint (`whk_` prefix), as returned when it was created.","schema":{"$ref":"#/components/schemas/WebhookEndpointID"},"example":"whk_01krdgeqcxet5s7t44vh8rt9mg"}],"get":{"operationId":"getWebhook","summary":"Get a webhook endpoint","description":"Returns one webhook endpoint's configuration and current delivery `status`, including its URL and subscribed event types. The signing secret is never included; if you lost it, mint a new one with [Rotate webhook signing secret](/docs/api/reference/rotate-webhook-secret).\n","tags":["webhooks"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-snippet-key":"webhooks.get","x-audiences":["public","command"],"responses":{"200":{"description":"Webhook endpoint with its current URL, subscriptions, and status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","mcp","n8n","sdk"]},"patch":{"operationId":"updateWebhook","summary":"Update a webhook endpoint","description":"Updates the webhook endpoint. Only the fields you send change: `events` replaces the\nwhole subscription set, and `status` pauses or re-enables delivery.\n\nThe `200` response is the updated endpoint. Invalid input (a non-HTTPS or non-public\n`url`, an event type outside the catalog) returns a `422`.\n","tags":["webhooks"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-snippet-key":"webhooks.update","x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointUpdate"}}}},"responses":{"200":{"description":"Webhook endpoint after the update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n","sdk"]},"delete":{"operationId":"deleteWebhook","summary":"Delete a webhook endpoint","description":"Permanently removes the webhook endpoint and stops all deliveries to it, including retries of earlier failed deliveries. This cannot be undone: recreating an endpoint later mints a new `id` and signing secret. To stop deliveries temporarily instead, set `status` to `paused` with [Update a webhook endpoint](/docs/api/reference/update-webhook).\n","tags":["webhooks"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-snippet-key":"webhooks.delete","x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Webhook endpoint deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n","sdk"]}},"/v1/webhooks/{webhook_id}/rotate-secret":{"parameters":[{"name":"webhook_id","in":"path","required":true,"description":"ID of the webhook endpoint (`whk_` prefix), as returned when it was created.","schema":{"$ref":"#/components/schemas/WebhookEndpointID"},"example":"whk_01krdgeqcxet5s7t44vh8rt9mg"}],"post":{"operationId":"rotateWebhookSecret","summary":"Rotate webhook signing secret","description":"Generates a new signing secret for the endpoint and returns it exactly once: store it\nimmediately, it cannot be retrieved after this response. For 24 hours every delivery\nis signed with both the old and the new secret, so a receiver verifying with either\nkeeps working while you roll the new one out. After the window the old secret stops\nsigning. Verification details are in the [webhooks guide](/docs/guides/webhooks).\n\nAn endpoint holds at most 5 concurrently valid secrets, so rotating repeatedly within\nthe overlap window fails with `WebhookTooManySecrets` until an older secret expires.\n","tags":["webhooks"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-snippet-key":"webhooks.rotate_secret","x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"New signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRotateSecretResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n","sdk"]}},"/v1/webhooks/{webhook_id}/test":{"parameters":[{"name":"webhook_id","in":"path","required":true,"description":"ID of the webhook endpoint (`whk_` prefix), as returned when it was created.","schema":{"$ref":"#/components/schemas/WebhookEndpointID"},"example":"whk_01krdgeqcxet5s7t44vh8rt9mg"}],"post":{"operationId":"testWebhook","summary":"Test a webhook with a sample event","description":"Sends a signed synthetic event and returns whether your endpoint accepted it, its HTTP status, and the round-trip latency. An unreachable endpoint returns `status: failed` in the response body. The endpoint has 10 seconds to respond.\n\nThe body is a minimal JSON object with the event `type`, signed like a real delivery. It does not mirror that event's payload. Tests work on paused endpoints and do not appear in [List delivery attempts](/docs/api/reference/list-webhook-attempts).\n\nThe operation returns `412` if the endpoint lacks a valid signing secret or, when `event_type` is omitted, has no subscribed event type to use.\n","tags":["webhooks"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-snippet-key":"webhooks.test","x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestRequest"}}}},"responses":{"200":{"description":"The test result, including whether your endpoint accepted the event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"412":{"$ref":"#/components/responses/PreconditionFailed"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","mcp","n8n","sdk"]}},"/v1/webhooks/{webhook_id}/replay":{"parameters":[{"name":"webhook_id","in":"path","required":true,"description":"ID of the webhook endpoint (`whk_` prefix), as returned when it was created.","schema":{"$ref":"#/components/schemas/WebhookEndpointID"},"example":"whk_01krdgeqcxet5s7t44vh8rt9mg"}],"post":{"operationId":"createWebhookReplay","summary":"Replay failed deliveries","description":"Queues redelivery of deliveries that failed. The window runs from `since`\n(default: the last 24 hours) to `until`, and events the endpoint already received\nsuccessfully are skipped, so a replay never double-delivers.\n\nOnly failed attempts are replayed. An event the endpoint was never sent, such as one\nthat arrived while it was paused, has no failed attempt to replay, so a replay does\nnot recover it.\n\nThe `202` response means the replay is queued. Events are redelivered asynchronously\nand retried like any other delivery; no count or task ID is returned, so track results\nwith [List delivery attempts](/docs/api/reference/list-webhook-attempts).\n\nReplays are limited to 20 per organization per UTC day; beyond that the request\nreturns a `429` `WebhookReplayQuotaExceeded`.\n","tags":["webhooks"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookReplayRequest"}}}},"responses":{"202":{"description":"Replay queued. Events are redelivered asynchronously; no count or task ID is returned."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["n8n"]}},"/v1/webhooks/{webhook_id}/attempts":{"parameters":[{"name":"webhook_id","in":"path","required":true,"description":"ID of the webhook endpoint (`whk_` prefix), as returned when it was created.","schema":{"$ref":"#/components/schemas/WebhookEndpointID"},"example":"whk_01krdgeqcxet5s7t44vh8rt9mg"}],"get":{"operationId":"listWebhookAttempts","summary":"List delivery attempts","description":"Returns the endpoint's recent delivery attempts, newest first. Each entry is one HTTP\nrequest, so a retried event appears once per try; use it to see what failed and why\nbefore requesting redelivery with\n[Replay failed deliveries](/docs/api/reference/create-webhook-replay).\n\nBound the window with the `before`/`after` timestamps and cap the page with `limit`.\nTo page further back without a cursor, pass the oldest `attempted_at`\nyou received as `before`.\n","tags":["webhooks"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-snippet-key":"webhooks.attempts","x-audiences":["public","command"],"parameters":[{"name":"limit","in":"query","required":false,"description":"Maximum number of attempts to return. Defaults to 50, capped at 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"before","in":"query","required":false,"description":"Only return attempts strictly before this timestamp.","schema":{"type":"string","format":"date-time","minLength":1}},{"name":"after","in":"query","required":false,"description":"Only return attempts strictly after this timestamp.","schema":{"type":"string","format":"date-time","minLength":1}}],"responses":{"200":{"description":"Recent delivery attempts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookAttemptList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","mcp","n8n","sdk"]}},"/v1/numbers":{"get":{"operationId":"listWorkspaceNumbers","x-snippet-key":"numbers.list","summary":"List your allocated numbers","description":"Returns a paginated list of the phone numbers currently allocated to your workspace, newest first. Each entry is either a dedicated number you bought or a shared number managed for you, as its `kind` field indicates. Pass `number` to look one up, or narrow the list with `country_code`, `number_type`, `prefix`, and `capabilities`. An allocated number is not always enough to send from it: some countries also require an approved registration for the sender.","tags":["numbers"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"number","in":"query","required":false,"description":"Return only the number matching these digits. Give a full number with its country code, however your own records spell it: `+12025550188`, `12025550188`, `0012025550188`, and `+1 202 555 0188` all resolve to the same number. Spacing and punctuation are fine once a leading `+` or `00` marks the country code, or when `country_code` names the country; a grouped spelling without either is refused rather than guessed at, and a national spelling (bare digits without the country code) matches only when `country_code` names the country. A short code is matched on its bare digits instead, and since the same short code can be allocated in more than one country, pass `country_code` alongside it to name which one. This filter narrows the list like the others rather than replacing them, so a country or capability filter still applies. To match a range of numbers rather than one, use `prefix`.","schema":{"type":"string","minLength":1,"maxLength":24,"pattern":"^[0-9+()\\-. ]+$"}},{"name":"country_code","in":"query","required":false,"description":"Filter by the country a number belongs to, as an ISO 3166-1 alpha-2 code.","schema":{"type":"string","minLength":2,"maxLength":2}},{"name":"number_type","in":"query","required":false,"description":"Return only allocated numbers of this physical type after applying the country and prefix filters.","schema":{"$ref":"#/components/schemas/NumberType"}},{"name":"prefix","in":"query","required":false,"description":"Return only numbers that start with these digits. With `country_code`, the digits are national ones, matched right after the country dial code: with `country_code=US`, `prefix=212` returns the +1 212 area-code numbers allocated to you. Leave out the dial code and any national dialing prefix such as a leading 0; short codes never match a country-anchored prefix search. Without `country_code`, the digits are matched from the start of each stored number, ignoring a leading `+`, so short codes are reachable and a full number in digits finds itself. Digits only: up to 10 of them with `country_code`, 15 without.","schema":{"type":"string","pattern":"^[0-9]{1,15}$"}},{"name":"capabilities","in":"query","required":false,"description":"Filter by capability. Repeat the parameter to require several at once: `capabilities=sms&capabilities=voice` returns only numbers that support both.","style":"form","explode":true,"schema":{"type":"array","items":{"type":"string","x-extensible-enum":["sms","voice"]}}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"List of the numbers allocated to the workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/numbers/available":{"get":{"operationId":"listAvailableNumbers","x-snippet-key":"numbers.available.list","summary":"List available phone numbers","description":"Returns phone numbers available for purchase in a country, newest first. Narrow the search with `number_type`, `capabilities`, and `prefix`. Inventory numbers are returned first and support pagination. The final page can include a live snapshot of numbers available from suppliers.","tags":["numbers"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"country_code","in":"query","required":true,"description":"ISO 3166-1 alpha-2 country code to search in.","schema":{"type":"string","minLength":2,"maxLength":2}},{"name":"number_type","in":"query","required":false,"description":"Return only numbers of this physical type after applying the country and prefix filters.","schema":{"$ref":"#/components/schemas/NumberType"}},{"name":"prefix","in":"query","required":false,"description":"Return only numbers that start with these digits, matched right after the country dial code: with `country_code=US`, `prefix=212` matches +1 212 area-code numbers and `prefix=833` matches 833 toll-free numbers. Digits only. Leave out the country dial code and any national dialing prefix such as a leading 0. Short codes never match a prefix search.","schema":{"type":"string","pattern":"^[0-9]{1,10}$"}},{"name":"capabilities","in":"query","required":false,"description":"Filter by capability. Repeat the parameter to require several at once: `capabilities=sms&capabilities=voice` returns only numbers that support both.","style":"form","explode":true,"schema":{"type":"array","items":{"type":"string","x-extensible-enum":["sms","voice"]}}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"List of available phone numbers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailableNumberList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/numbers/available/{number}":{"get":{"operationId":"getAvailableNumber","x-snippet-key":"numbers.available.get","summary":"Get an available phone number","description":"Returns a single phone number available for purchase, whether it is already in inventory or can be acquired for you. Numbers supplied through a carrier remain available only while the carrier has them, so a number listed a moment ago may already be gone. A `404` means the number is currently unavailable for sale.","tags":["numbers"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"number","in":"path","required":true,"description":"Phone number in E.164 format. The leading `+` is optional.","schema":{"type":"string","minLength":1}}],"responses":{"200":{"description":"The available phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailableNumber"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/numbers/orders":{"post":{"operationId":"createNumbersOrder","x-snippet-key":"numbers.orders.create","summary":"Create a number order","description":"Orders a number for your workspace and starts its monthly charge. Pass a\nnumber from `GET /v1/numbers/available`. Whether the number is already in\ninventory or acquired from a supplier, the response contains an order.\n\nMost orders complete immediately and return `201` with `status` of\n`completed` and `number_id` populated. Read the number with\n`GET /v1/numbers/{number_id}`. An order that cannot complete in the request\nreturns `202`; poll `GET /v1/numbers/orders/{order_id}` until it is\n`completed` or `failed`.\n\nA `412` means the workspace has not\ncompleted the identity verification required to acquire a sender. Complete\nit, then retry.\n","tags":["numbers"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumbersOrderCreate"}}}},"responses":{"201":{"description":"The order completed and the number is now allocated to your workspace. Its status is completed, with number and number_id populated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumbersOrder"}}}},"202":{"description":"The order was accepted but could not complete in this request. Poll it for completion.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumbersOrder"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"412":{"$ref":"#/components/responses/PreconditionFailed"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"get":{"operationId":"listNumbersOrders","x-snippet-key":"numbers.orders.list","summary":"List your number orders","description":"Returns your workspace's number orders, newest first. Filter by status to find in-progress or failed orders.","tags":["numbers"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"status","in":"query","required":false,"description":"Return only orders with status `charging`, `ordering`, `pending`, `completed`, or `failed`.","schema":{"$ref":"#/components/schemas/NumbersOrderStatus"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"List of the workspace's number orders.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumbersOrderList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/numbers/orders/{order_id}":{"get":{"operationId":"getNumbersOrder","x-snippet-key":"numbers.orders.get","summary":"Get a number order","description":"Returns a single number order by id, including its current lifecycle state and, once completed, the number it produced.","tags":["numbers"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"order_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/NumbersOrderID"},"description":"Identifier of the order."}],"responses":{"200":{"description":"The order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumbersOrder"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/numbers/{number_id}":{"get":{"operationId":"getWorkspaceNumber","x-snippet-key":"numbers.get","summary":"Get an allocated number","description":"Returns a single phone number allocated to your workspace, whether it is a dedicated number you bought or a shared number managed for you. Numbers you have released are no longer returned. An allocated number is not always enough to send from it: some countries also require an approved registration for the sender.","tags":["numbers"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"number_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/AllocatedNumberID"},"description":"Identifier of the number, as returned in the id field of GET /v1/numbers."}],"responses":{"200":{"description":"The number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Number"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]},"delete":{"operationId":"releaseWorkspaceNumber","x-snippet-key":"numbers.release","summary":"Release a dedicated number","description":"Releases one of your workspace's dedicated numbers and stops its monthly charge. Your workspace can no longer use the number after release. Shared numbers belong to Bird-managed shared infrastructure and cannot be released from your workspace.","tags":["numbers"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"$ref":"#/components/parameters/XWorkspaceId"},{"name":"number_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/AllocatedNumberID"},"description":"Identifier of the number to release, as returned in the id field of GET /v1/numbers."},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"Number released."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/voice/calls":{"get":{"operationId":"listVoiceCalls","x-snippet-key":"voice.list","summary":"List calls","description":"Returns a paginated list of the workspace's calls, ordered by start time\ndescending.\n\nThe `status` filter selects where in the lifecycle you look, and any\ncombination is a single page: in-flight statuses (`ringing`,\n`in_progress`), final ones, or both together. Omit it and you get\ncompleted calls, which is what this list has always returned.\n\nA call in flight carries no economics yet: `duration_ms`, `billable_ms`,\n`ended_at`, and `cost` are null until it ends. It keeps the same `id`\nthroughout, so the same call answers under one identity from the first\nring to settlement.\n","tags":["voice-calls"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"parameters":[{"name":"direction","in":"query","required":false,"description":"Return only calls in this direction.","schema":{"$ref":"#/components/schemas/VoiceCallDirection"}},{"name":"status","in":"query","required":false,"description":"Return only calls with one of these statuses, comma-separated.\nIn-flight and final statuses may be combined freely.\n","style":"form","explode":false,"schema":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/VoiceCallStatus"}}},{"name":"session_id","in":"query","required":false,"description":"Return only calls belonging to this session, which is how the legs of one multi-party or transferred call are correlated.","schema":{"$ref":"#/components/schemas/VoiceSessionID"}},{"name":"sip_trunk_id","in":"query","required":false,"description":"Return only calls carried by this SIP trunk.","schema":{"$ref":"#/components/schemas/SIPTrunkID"}},{"name":"from","in":"query","required":false,"description":"Return only calls placed from this calling party number, matched as a whole number rather than as a fragment. Give it in international form: `+14155551234`, `14155551234`, and `0014155551234` all select the same calls. A number given without a country code is read as an international one, so give the country code to be sure of what you are matching. Use `number` instead to match part of a number, or either side of the call.\n","schema":{"type":"string","minLength":1,"maxLength":32,"example":"+14155551234"}},{"name":"to","in":"query","required":false,"description":"Return only calls placed to this called party number, matched as a whole number rather than as a fragment. Give it in international form: `+16505559876`, `16505559876`, and `0016505559876` all select the same calls. A number given without a country code is read as an international one, so give the country code to be sure of what you are matching. Use `number` instead to match part of a number, or either side of the call.\n","schema":{"type":"string","minLength":1,"maxLength":32,"example":"+16505559876"}},{"name":"number","in":"query","required":false,"description":"Return only calls where the calling or called number contains this value. Matches a partial number, so a country or area-code prefix returns every call to or from it. Combines with `from`/`to`, which match one side exactly.","schema":{"type":"string","minLength":1,"maxLength":32}},{"$ref":"#/components/parameters/TagFilter"},{"name":"started_after","in":"query","required":false,"description":"Return only calls that started at or after this instant, inclusive. RFC 3339 timestamp.","schema":{"type":"string","format":"date-time"}},{"name":"started_before","in":"query","required":false,"description":"Return only calls that started at or before this instant, inclusive. RFC 3339 timestamp.","schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/PaginationLimit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Paginated list of call records.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceCallList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}},"/v1/voice/calls/{call_id}":{"parameters":[{"name":"call_id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/VoiceCallID"},"example":"vcl_01k0p3v9wera3v6q6xw3e9y2mh"}],"get":{"operationId":"getVoiceCall","x-snippet-key":"voice.get","summary":"Get a call","description":"Returns a single call at any point in its lifecycle. A call that is still ringing or connected answers with its in-flight `status` and no economics: `duration_ms`, `billable_ms`, `ended_at`, and `cost` fill in once it ends, at this same URL. Returns a 404 `not_found_error` if the call does not exist in the workspace.\n","tags":["voice-calls"],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"x-audiences":["public","command"],"responses":{"200":{"description":"Call leg with its current status, timing, and routing details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceCall"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-surfaces":["cli","make","mcp","n8n","sdk"]}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Pass the API key as a bearer token in the `Authorization` header. Keys use\nthe format `bk_{region}_*`. The prefix identifies the region and selects the\nAPI endpoint. Official Bird SDKs and the CLI derive the region from the key.\n"},"CookieAuth":{"type":"apiKey","in":"cookie","name":"bird_session","description":"Session cookie set after signing in to the Bird dashboard. The cookie\nvalue is an opaque session token; no session data is stored in the cookie\nitself.\n"},"RealtimeKey":{"type":"apiKey","in":"header","name":"X-Realtime-Key","description":"The Realtime app key. Together with `X-Realtime-Secret`, it authenticates a\nrequest to the Realtime API in addition to the workspace credential. Both\nvalues come from the app's credentials and must belong to the calling\nworkspace. Official Bird SDKs accept the pair as client configuration.\n"},"RealtimeSecret":{"type":"apiKey","in":"header","name":"X-Realtime-Secret","description":"The Realtime app secret paired with `X-Realtime-Key`. The API returns the\nsecret only when the key is created and does not store it. Create a new key\nand revoke the current key if you lose the secret. Official Bird SDKs accept\nthe pair as client configuration.\n"}},"schemas":{"WebhookEvent":{"description":"Webhook delivery body. `type` identifies the event variant, `timestamp` is when the event occurred, and `data` contains the event-specific payload. See the [webhooks guide](/docs/guides/webhooks) for signature verification.\n","oneOf":[{"$ref":"#/components/schemas/EventDomainFailed"},{"$ref":"#/components/schemas/EventDomainVerified"},{"$ref":"#/components/schemas/EventEmailAccepted"},{"$ref":"#/components/schemas/EventEmailBounced"},{"$ref":"#/components/schemas/EventEmailCanceled"},{"$ref":"#/components/schemas/EventEmailClicked"},{"$ref":"#/components/schemas/EventEmailComplained"},{"$ref":"#/components/schemas/EventEmailDeferred"},{"$ref":"#/components/schemas/EventEmailDelivered"},{"$ref":"#/components/schemas/EventEmailListUnsubscribed"},{"$ref":"#/components/schemas/EventEmailOpened"},{"$ref":"#/components/schemas/EventEmailOutOfBandBounce"},{"$ref":"#/components/schemas/EventEmailProcessed"},{"$ref":"#/components/schemas/EventEmailReceived"},{"$ref":"#/components/schemas/EventEmailRejected"},{"$ref":"#/components/schemas/EventEmailScheduled"},{"$ref":"#/components/schemas/EventEmailUnsubscribed"},{"$ref":"#/components/schemas/EventEmailMailboxMessageDelivered"},{"$ref":"#/components/schemas/EventEmailMailboxMessageFailed"},{"$ref":"#/components/schemas/EventEmailMailboxMessageReceived"},{"$ref":"#/components/schemas/EventEmailMailboxMessageSent"},{"$ref":"#/components/schemas/EventEmailMailboxSuspended"},{"$ref":"#/components/schemas/EventEmailMailboxThreadCreated"},{"$ref":"#/components/schemas/EventEmailSuppressionCreated"},{"$ref":"#/components/schemas/EventPreferenceDeleted"},{"$ref":"#/components/schemas/EventPreferenceGranted"},{"$ref":"#/components/schemas/EventPreferenceRevoked"},{"$ref":"#/components/schemas/EventSMSAccepted"},{"$ref":"#/components/schemas/EventSMSDelivered"},{"$ref":"#/components/schemas/EventSMSExpired"},{"$ref":"#/components/schemas/EventSMSFailed"},{"$ref":"#/components/schemas/EventSMSReceived"},{"$ref":"#/components/schemas/EventSMSRejected"},{"$ref":"#/components/schemas/EventSMSSent"},{"$ref":"#/components/schemas/EventSMSUndelivered"},{"$ref":"#/components/schemas/EventSMSSuppressionCreated"},{"$ref":"#/components/schemas/EventVerifyAttemptDelivered"},{"$ref":"#/components/schemas/EventVerifyAttemptSent"},{"$ref":"#/components/schemas/EventVerifyAttemptUndelivered"},{"$ref":"#/components/schemas/EventVerifyVerificationCreated"},{"$ref":"#/components/schemas/EventVerifyVerificationFailed"},{"$ref":"#/components/schemas/EventVerifyVerificationVerified"},{"$ref":"#/components/schemas/EventVoiceCallAnswered"},{"$ref":"#/components/schemas/EventVoiceCallEnded"},{"$ref":"#/components/schemas/EventVoiceCallInitiated"},{"$ref":"#/components/schemas/EventWhatsAppAccepted"},{"$ref":"#/components/schemas/EventWhatsAppDelivered"},{"$ref":"#/components/schemas/EventWhatsAppFailed"},{"$ref":"#/components/schemas/EventWhatsAppReacted"},{"$ref":"#/components/schemas/EventWhatsAppRead"},{"$ref":"#/components/schemas/EventWhatsAppReceived"},{"$ref":"#/components/schemas/EventWhatsAppRejected"},{"$ref":"#/components/schemas/EventWhatsAppSent"},{"$ref":"#/components/schemas/EventWhatsAppSuppressionCreated"}],"discriminator":{"propertyName":"type","mapping":{"domain.failed":"#/components/schemas/EventDomainFailed","domain.verified":"#/components/schemas/EventDomainVerified","email.accepted":"#/components/schemas/EventEmailAccepted","email.bounced":"#/components/schemas/EventEmailBounced","email.canceled":"#/components/schemas/EventEmailCanceled","email.clicked":"#/components/schemas/EventEmailClicked","email.complained":"#/components/schemas/EventEmailComplained","email.deferred":"#/components/schemas/EventEmailDeferred","email.delivered":"#/components/schemas/EventEmailDelivered","email.list_unsubscribed":"#/components/schemas/EventEmailListUnsubscribed","email.opened":"#/components/schemas/EventEmailOpened","email.out_of_band_bounce":"#/components/schemas/EventEmailOutOfBandBounce","email.processed":"#/components/schemas/EventEmailProcessed","email.received":"#/components/schemas/EventEmailReceived","email.rejected":"#/components/schemas/EventEmailRejected","email.scheduled":"#/components/schemas/EventEmailScheduled","email.unsubscribed":"#/components/schemas/EventEmailUnsubscribed","email_mailbox.message_delivered":"#/components/schemas/EventEmailMailboxMessageDelivered","email_mailbox.message_failed":"#/components/schemas/EventEmailMailboxMessageFailed","email_mailbox.message_received":"#/components/schemas/EventEmailMailboxMessageReceived","email_mailbox.message_sent":"#/components/schemas/EventEmailMailboxMessageSent","email_mailbox.suspended":"#/components/schemas/EventEmailMailboxSuspended","email_mailbox.thread_created":"#/components/schemas/EventEmailMailboxThreadCreated","email_suppression.created":"#/components/schemas/EventEmailSuppressionCreated","preference.deleted":"#/components/schemas/EventPreferenceDeleted","preference.granted":"#/components/schemas/EventPreferenceGranted","preference.revoked":"#/components/schemas/EventPreferenceRevoked","sms.accepted":"#/components/schemas/EventSMSAccepted","sms.delivered":"#/components/schemas/EventSMSDelivered","sms.expired":"#/components/schemas/EventSMSExpired","sms.failed":"#/components/schemas/EventSMSFailed","sms.received":"#/components/schemas/EventSMSReceived","sms.rejected":"#/components/schemas/EventSMSRejected","sms.sent":"#/components/schemas/EventSMSSent","sms.undelivered":"#/components/schemas/EventSMSUndelivered","sms_suppression.created":"#/components/schemas/EventSMSSuppressionCreated","verify.attempt.delivered":"#/components/schemas/EventVerifyAttemptDelivered","verify.attempt.sent":"#/components/schemas/EventVerifyAttemptSent","verify.attempt.undelivered":"#/components/schemas/EventVerifyAttemptUndelivered","verify.verification.created":"#/components/schemas/EventVerifyVerificationCreated","verify.verification.failed":"#/components/schemas/EventVerifyVerificationFailed","verify.verification.verified":"#/components/schemas/EventVerifyVerificationVerified","voice_call.answered":"#/components/schemas/EventVoiceCallAnswered","voice_call.ended":"#/components/schemas/EventVoiceCallEnded","voice_call.initiated":"#/components/schemas/EventVoiceCallInitiated","whatsapp.accepted":"#/components/schemas/EventWhatsAppAccepted","whatsapp.delivered":"#/components/schemas/EventWhatsAppDelivered","whatsapp.failed":"#/components/schemas/EventWhatsAppFailed","whatsapp.reacted":"#/components/schemas/EventWhatsAppReacted","whatsapp.read":"#/components/schemas/EventWhatsAppRead","whatsapp.received":"#/components/schemas/EventWhatsAppReceived","whatsapp.rejected":"#/components/schemas/EventWhatsAppRejected","whatsapp.sent":"#/components/schemas/EventWhatsAppSent","whatsapp_suppression.created":"#/components/schemas/EventWhatsAppSuppressionCreated"}}},"RootResponse":{"type":"object","additionalProperties":false,"required":["name","docs"],"properties":{"name":{"type":"string","minLength":1,"const":"bird","description":"The API service name."},"docs":{"type":"string","format":"uri","minLength":1,"description":"URL to the API documentation."}}},"ErrorDetail":{"type":"object","additionalProperties":false,"required":["param","message"],"properties":{"param":{"type":"string","minLength":1,"description":"Dotted field path, such as `to[0].email`, `subject`, or `.`. When the request was rejected for a query parameter the endpoint does not declare, this carries that parameter's name instead of a field path.\n"},"message":{"type":"string","minLength":1,"description":"What is wrong with this field."}}},"NextAction":{"type":"object","additionalProperties":false,"required":["kind","description"],"properties":{"kind":{"type":"string","minLength":1,"x-extensible-enum":["operation","external","wait","terminal"],"description":"What you do about this step.\n\n- `operation`: call the operation named in `operation`, then\n  read again.\n- `external`: act somewhere this API does not reach, then read\n  again.\n- `wait`: nothing is asked of you, so read again later.\n- `terminal`: nothing you do resolves this, so stop retrying.\n\nTolerate a value you do not recognize: show the `description` and\noffer no action.\n"},"description":{"type":"string","minLength":1,"description":"A short, human-readable label for the step, suitable for display."},"operation":{"type":"string","minLength":1,"description":"The operationId to call. Present only when `kind` is `operation`. The operation's own schema says how to call it; this says only which one, and what to address it with.\n"},"params":{"type":"object","additionalProperties":{"type":"string","minLength":1},"description":"The parameters that address the operation, by name: `{\"sender_id\": \"…\"}` for an operation on `/v1/sms/senders/{sender_id}/requirements`. A parameter the operation takes in its query string is given the same way, so an operation addressed as `?subject_id=` carries `{\"subject_id\": \"…\"}`. Every parameter the call needs is here, whether its value came from the thing you were acting on or is fixed for this step, so you can make the call from this object alone. Present only when `kind` is `operation` and the operation names a subject. A request body, when the operation takes one, is described by the operation's own schema and never appears here.\n"},"url":{"type":"string","format":"uri","description":"A URL to open. Present only when `kind` is `external`, and only when the step has one. An external step whose `description` says to go and do something with no URL to open is normal.\n"}}},"ErrorBody":{"type":"object","additionalProperties":false,"required":["type","code","name","message","doc_url","request_id"],"properties":{"type":{"type":"string","minLength":1,"description":"Broad category for coarse client branching.","enum":["auth_error","bad_request_error","billing_error","conflict_error","gone_error","internal_error","misdirected_error","not_found_error","not_implemented_error","payload_too_large_error","permission_error","precondition_error","rate_limit_error","service_unavailable_error","too_early_error","validation_error"]},"code":{"type":"string","minLength":1,"pattern":"^E\\d{5}$","description":"Opaque, stable, unique error identifier. Never reused."},"name":{"type":"string","minLength":1,"description":"Human-readable slug for log readability. Paired with code, never replaces it."},"message":{"type":"string","minLength":1,"description":"Human-readable description. Not stable; clients must not parse it."},"param":{"type":"string","minLength":1,"description":"Identifies the offending field. Omitted when not applicable."},"doc_url":{"type":"string","minLength":1,"format":"uri","description":"Stable link to the docs page for this error code."},"request_id":{"type":"string","minLength":1,"description":"Request correlation ID for support and troubleshooting. Also returned in the `X-Request-Id` response header."},"vendor_code":{"type":"string","minLength":1,"description":"Verbatim error code from an external system, such as an SMTP response code or a payment decline code. Present only when the code may help you resolve the error.\n"},"details":{"type":"array","description":"Per-field validation errors. Present only on validation_error responses.","items":{"$ref":"#/components/schemas/ErrorDetail"}},"remediation":{"type":"string","minLength":1,"description":"A human-readable next step to resolve this error. Present when a recovery is known."},"next":{"type":"array","description":"The steps that resolve this error. Perform them in order, re-reading after each; a `wait` or `terminal` step is always last. Present for errors with a well-defined recovery, such as unmet preconditions and conflicts.\n","items":{"$ref":"#/components/schemas/NextAction"}}}},"Error":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}}},"HealthResponse":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","minLength":1,"const":"ok","description":"Always \"ok\" when the service is healthy."}}},"SignupRequest":{"type":"object","additionalProperties":false,"required":["email","password"],"properties":{"email":{"type":"string","format":"email","minLength":5,"maxLength":254,"description":"Email address for the new account.","example":"alice@example.com"},"password":{"type":"string","minLength":12,"maxLength":1024,"x-sensitive":true,"description":"Account password. Minimum 12 characters.","example":"correct-horse-battery-staple"},"language":{"type":"string","enum":["en-US","en-GB","pt-BR"],"description":"Initial UI language preference. Defaults to en-US when omitted.","example":"en-US"},"verification_method":{"type":"string","enum":["link","code"],"default":"link","description":"How the account email is verified. The default `link` method sends a verification link to click. The `code` method sends a six-digit code and returns a challenge token in the response. Submit the code with that token to verify. Choose `code` when no browser is available to open a link.\n","example":"code"}},"example":{"email":"alice@example.com","password":"correct-horse-battery-staple","verification_method":"code"}},"SignupResult":{"type":"object","additionalProperties":false,"properties":{"challenge_token":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Present whenever `verification_method` was `code`, and absent otherwise. The same is true whether or not the address already has an account, so neither the presence of this field nor its length reveals which. Short-lived token identifying this attempt: submit it with the six-digit code from the verification email to verify the address. If that email does not arrive, check the address's inbox before retrying. An address that already has an account may be told so there instead, and this token will not verify it.\n","example":"ch_01abc"}}},"LoginRequest":{"type":"object","additionalProperties":false,"required":["email","password"],"properties":{"email":{"type":"string","format":"email","minLength":5,"maxLength":254,"description":"Registered email address.","example":"alice@example.com"},"password":{"type":"string","minLength":1,"maxLength":1024,"x-sensitive":true,"description":"Account password.","example":"correct-horse-battery-staple"}}},"UserID":{"type":"string","minLength":1,"pattern":"^usr_[0-9a-hjkmnp-tv-z]{26}$","example":"usr_01krdgeqcxet5s7t44vh8rt9mg"},"MFAFactorID":{"type":"string","minLength":1,"pattern":"^mfa_[0-9a-hjkmnp-tv-z]{26}$","example":"mfa_01krdgeqcxet5s7t44vh8rt9mg"},"MFAFactorSummary":{"type":"object","additionalProperties":false,"required":["id","type"],"properties":{"id":{"$ref":"#/components/schemas/MFAFactorID","readOnly":true,"description":"Factor ID."},"type":{"type":"string","minLength":1,"enum":["totp","sms"],"readOnly":true,"description":"Factor type.","example":"totp"},"phone_last4":{"type":["string","null"],"readOnly":true,"description":"Last 4 digits of phone. Present only for SMS factors.","example":"1234"}}},"LoginResponse":{"type":"object","additionalProperties":false,"properties":{"user_id":{"readOnly":true,"description":"ID of the authenticated user. Present on successful login. `null` when `mfa_required` or `email_otp_required` is `true` (session is not established yet).\n","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"mfa_required":{"type":"boolean","readOnly":true,"default":false,"description":"`true` when the account requires MFA verification before a session is established.","example":false},"email_otp_required":{"type":"boolean","readOnly":true,"default":false,"description":"`true` when a one-time passcode has been sent to the account email address because the login originated from an unrecognized device. The client must complete verification via `POST /v1/auth/sessions/verify-device` before a session is established.\n","example":false},"challenge_token":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Short-lived token for continuing a pending verification flow. Present when `mfa_required` or `email_otp_required` is `true`. For MFA, pass to `POST /v1/auth/mfa/challenge` or `POST /v1/auth/mfa/verify`. For device verification, pass to `POST /v1/auth/sessions/verify-device` or `POST /v1/auth/sessions/resend-device-otp`.\n","example":"ch_01abc"},"factors":{"type":["array","null"],"readOnly":true,"description":"Available MFA factors for the user. Present only when `mfa_required` is `true`. Use `factor_id` from one of these in the challenge step.\n","items":{"$ref":"#/components/schemas/MFAFactorSummary"}},"onboarding_ticket":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Single-use, short-lived ticket for a magic-link sign-in that created a brand-new account with no organization yet. Present only on the magic-link code path for a fresh signup; pass it to the onboarding call to create your organization and workspace without a browser session. It is not an API credential and cannot be used for anything else.\n","example":"obt_9mP2wR5tY8uI1oL4nJ"},"webauthn_available":{"type":"boolean","readOnly":true,"default":false,"description":"`true` when the user has a registered security key that can be used to sign in instead of an MFA factor. Present only when `mfa_required` is `true`. Start a sign-in ceremony with `POST /v1/auth/mfa/webauthn/authenticate/start`.\n","example":false}},"description":"Returned on login. On success, a session cookie is set via `Set-Cookie`. When `mfa_required` or `email_otp_required` is `true`, no session is created yet: the client must complete the verification step using the `challenge_token`.\n"},"MessageResponse":{"type":"object","additionalProperties":false,"required":["message"],"properties":{"message":{"type":"string","minLength":1,"description":"Human-readable confirmation of the completed action.","example":"Check your email to verify your account."}}},"VerifyEmailOtpRequest":{"type":"object","additionalProperties":false,"required":["challenge_token","code"],"properties":{"challenge_token":{"type":"string","minLength":1,"x-sensitive":true,"description":"Challenge token returned by the signup endpoint when the verification method was `code`.\n","example":"ch_01abc"},"code":{"type":"string","minLength":6,"maxLength":6,"description":"Six-digit one-time passcode sent to the account email address.","example":"123456"}},"example":{"challenge_token":"ch_01abc","code":"123456"}},"EmailVerificationResult":{"type":"object","additionalProperties":false,"required":["user_id","onboarding_ticket"],"properties":{"user_id":{"readOnly":true,"$ref":"#/components/schemas/UserID"},"onboarding_ticket":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Single-use, short-lived ticket proving this email was recently verified. Pass it to the onboarding call to create your organization and workspace. It is not an API credential and cannot be used for anything else.\n","example":"obt_9mP2wR5tY8uI1oL4nJ"}}},"ResendVerificationRequest":{"type":"object","additionalProperties":false,"required":["email"],"properties":{"email":{"type":"string","format":"email","minLength":5,"description":"Email address to resend the verification link to.","example":"alice@example.com"}}},"ForgotPasswordRequest":{"type":"object","additionalProperties":false,"required":["email"],"properties":{"email":{"type":"string","format":"email","minLength":5,"maxLength":254,"description":"Email address to send the password reset link to.","example":"alice@example.com"}}},"ResetPasswordTokenStatus":{"type":"object","additionalProperties":false,"required":["valid"],"properties":{"valid":{"type":"boolean","description":"Whether the password reset token is valid and not yet used or expired.","example":true}}},"ResetPasswordRequest":{"type":"object","additionalProperties":false,"required":["token","password"],"properties":{"token":{"type":"string","minLength":1,"x-sensitive":true,"description":"Password reset token from the emailed link (44-character base64url string).","example":"dGhpcyBpcyBhIHRva2VuIGV4YW1wbGUgdGVzdA=="},"password":{"type":"string","minLength":12,"maxLength":1024,"x-sensitive":true,"description":"New password. Minimum 12 characters.","example":"new-correct-horse-battery-staple"}}},"MagicLinkRequest":{"type":"object","additionalProperties":false,"required":["email"],"properties":{"email":{"type":"string","format":"email","minLength":1,"description":"Email address to send the magic link and code to.","example":"alice@example.com"}},"example":{"email":"alice@example.com"}},"MagicLinkTokenStatus":{"type":"object","additionalProperties":false,"properties":{"valid":{"type":"boolean","readOnly":true,"description":"Whether the link token is still valid and unused."}}},"MagicLinkVerifyRequest":{"type":"object","additionalProperties":false,"required":["token"],"properties":{"token":{"type":"string","minLength":1,"x-sensitive":true,"description":"The magic-link token from the emailed link."}}},"MagicLinkVerifyCodeRequest":{"type":"object","additionalProperties":false,"required":["email","code"],"properties":{"email":{"type":"string","format":"email","minLength":1,"description":"Email address the code was sent to.","example":"alice@example.com"},"code":{"type":"string","pattern":"^\\d{6}$","minLength":6,"x-sensitive":true,"description":"The 6-digit code from the email.","example":"123456"}},"example":{"email":"alice@example.com","code":"123456"}},"TenantName":{"type":"string","minLength":3,"maxLength":100,"description":"Must include at least one letter or number.","example":"Acme Corp"},"Region":{"type":"string","minLength":1,"enum":["us1","eu1"],"description":"Deployment region identifier.","example":"us1"},"OnboardingRequest":{"type":"object","additionalProperties":false,"required":["org_name","workspace_name","region"],"properties":{"org_name":{"$ref":"#/components/schemas/TenantName"},"workspace_name":{"$ref":"#/components/schemas/TenantName","example":"Production"},"region":{"allOf":[{"$ref":"#/components/schemas/Region"}],"description":"Region for the new organization. The request MUST be sent to that region's API host; the server rejects mismatches with `421`.\n"},"onboarding_ticket":{"type":"string","minLength":1,"x-sensitive":true,"description":"Single-use ticket from the email verification response. Required when onboarding without a signed-in browser session; it identifies the verified user creating the organization.\n","example":"obt_9mP2wR5tY8uI1oL4nJ"}},"example":{"org_name":"Acme Corp","workspace_name":"Production","region":"us1","onboarding_ticket":"obt_9mP2wR5tY8uI1oL4nJ"}},"OrganizationID":{"type":"string","minLength":1,"pattern":"^org_[0-9a-hjkmnp-tv-z]{26}$","example":"org_01krdgeqcxet5s7t44vh8rt9mg"},"OrganizationSummary":{"type":"object","additionalProperties":false,"required":["id","name"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"name":{"type":"string","minLength":1}}},"WorkspaceID":{"type":"string","minLength":1,"pattern":"^ws_[0-9a-hjkmnp-tv-z]{26}$","example":"ws_01krdgeqcxet5s7t44vh8rt9mg"},"WorkspaceSummary":{"type":"object","additionalProperties":false,"required":["id","name"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"name":{"type":"string","minLength":1}}},"OnboardingResponse":{"type":"object","additionalProperties":false,"required":["organization","workspace","access_token","token_type","expires_in"],"properties":{"organization":{"$ref":"#/components/schemas/OrganizationSummary"},"workspace":{"$ref":"#/components/schemas/WorkspaceSummary"},"access_token":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Access token scoped to the new organization and workspace. Send it as a Bearer token for subsequent calls; it supersedes the user-only token from email verification now that the organization exists.\n","example":"bt_us1_Ab3xKq9mP2wR5tY8uI1oL4nJ"},"token_type":{"type":"string","minLength":1,"readOnly":true,"description":"The token type; always `Bearer`.","example":"Bearer"},"expires_in":{"type":"integer","readOnly":true,"description":"Seconds until the access token expires.","example":3600},"refresh_token":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Refresh token used to obtain a new access token when this one expires, keeping the session going without signing in again.\n"}}},"AuthUser":{"type":"object","additionalProperties":false,"required":["id","email","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/UserID"},"email":{"type":"string","format":"email","minLength":5,"description":"Verified email address of the user.","example":"alice@example.com"},"email_verified_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the email was verified. Null if not yet verified (should not occur for authenticated users).","example":"2026-05-07T14:30:00Z"},"full_name":{"type":["string","null"],"minLength":1,"maxLength":100,"description":"Display name chosen by the user. Null until set.","example":"Alice Anderson"},"avatar_url":{"type":["string","null"],"format":"uri","readOnly":true,"description":"HTTPS URL to the current avatar image. Null when unset.","example":"https://cdn.bird.com/avatars/usr_01krdgeqcxet5s7t44vh8rt9mg.png"},"last_login_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp of the most recent successful login.","example":"2026-05-07T14:30:00Z"},"mfa_enabled":{"type":"boolean","readOnly":true,"description":"Whether multi-factor authentication is enabled for this user.","example":false},"has_password":{"type":"boolean","readOnly":true,"description":"True when the account has a password set; false for passwordless magic-link accounts.","example":true},"password_changed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the account password was last set. Null if no password change has been recorded.","example":"2026-03-07T14:30:00Z"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the user account was created.","example":"2026-05-01T09:00:00Z"},"scheduled_deletion_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this account is scheduled to be permanently deleted. Null unless a closure is scheduled.","example":"2026-05-08T09:00:00Z"},"scopes":{"type":"array","readOnly":true,"description":"Permission scopes the current credential carries, each as `<scope>:<level>` (for example `emails:write`). Empty for a full user session; populated for a scope-capped OAuth token or API key. A client can read this to re-request the union of its current scopes plus a newly required one during a step-up.\n","items":{"type":"string","minLength":1,"example":"emails:write"}}}},"AuthUserUpdate":{"type":"object","additionalProperties":false,"properties":{"full_name":{"type":["string","null"],"minLength":1,"maxLength":100,"description":"Display name chosen by the user. Send null to clear.","example":"Alice Anderson"}}},"AccountClosureOrgSummary":{"type":"object","additionalProperties":false,"required":["organization_id","name","member_count"],"properties":{"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Organization name.","example":"Acme Inc"},"member_count":{"type":"integer","minimum":1,"readOnly":true,"description":"Number of members currently in the organization.","example":3}}},"AccountClosureRequestResult":{"type":"object","additionalProperties":false,"required":["confirmation_sent","cascade","leave","blocking"],"properties":{"confirmation_sent":{"type":"boolean","readOnly":true,"description":"Whether a confirmation email was sent. `false` when one or more organizations block closure. Resolve those first, then request closure again.\n","example":true},"cascade":{"type":"array","readOnly":true,"description":"Organizations deleted with the account because they are solely owned and have no other members.","items":{"$ref":"#/components/schemas/AccountClosureOrgSummary"}},"leave":{"type":"array","readOnly":true,"description":"Organizations that continue after the account closes; the account holder's membership in them is removed.","items":{"$ref":"#/components/schemas/AccountClosureOrgSummary"}},"blocking":{"type":"array","readOnly":true,"description":"Organizations that must be resolved before the account can close: promote another member to owner, remove the other members, or delete the organization directly.\n","items":{"$ref":"#/components/schemas/AccountClosureOrgSummary"}}}},"ClosureCancelToken":{"type":"string","minLength":1,"x-sensitive":true,"description":"Opaque token from the closure-scheduled email, used to cancel the closure without an authenticated session."},"AccountClosureCancelRequest":{"type":"object","additionalProperties":false,"properties":{"token":{"$ref":"#/components/schemas/ClosureCancelToken","description":"The cancel token from the closure-scheduled email. Omit when calling this endpoint from an authenticated in-app session instead. One of the two is required.\n"}}},"ChangePasswordRequest":{"type":"object","additionalProperties":false,"required":["new_password"],"properties":{"current_password":{"type":"string","minLength":1,"maxLength":1024,"x-sensitive":true,"description":"The account's current password, required to authorize the change.","example":"old-correct-horse-battery-staple"},"new_password":{"type":"string","minLength":12,"maxLength":1024,"x-sensitive":true,"description":"New password. Minimum 12 characters.","example":"new-correct-horse-battery-staple"}}},"Timezone":{"type":"string","minLength":1,"description":"IANA timezone identifier, such as `America/New_York`, `Europe/Amsterdam`, or `UTC`.","example":"America/New_York"},"AuthUserPreferences":{"type":"object","additionalProperties":false,"required":["language"],"properties":{"language":{"type":"string","minLength":1,"enum":["en-US","en-GB","pt-BR"],"description":"BCP-47 language tag for the user's preferred UI language.","example":"en-US"},"timezone":{"oneOf":[{"$ref":"#/components/schemas/Timezone"},{"type":"null"}],"description":"IANA timezone identifier for the user's preferred display timezone. When absent or null, the dashboard uses the browser's local timezone for all time displays.\n"}}},"AuthUserPreferencesUpdate":{"type":"object","additionalProperties":false,"properties":{"language":{"type":"string","minLength":1,"enum":["en-US","en-GB","pt-BR"],"description":"BCP-47 language tag for the user's preferred UI language.","example":"en-US"},"timezone":{"oneOf":[{"$ref":"#/components/schemas/Timezone"},{"type":"null"}],"description":"IANA timezone identifier, or null to reset to browser timezone detection.\n"}}},"DashboardPinnedItem":{"type":"string","description":"Product or platform tool available as a personal sidebar shortcut.","enum":["email","sms","whatsapp","apple-messages","voice","realtime","verify","lookup","esim","automations","contacts","media-library","numbers","integrations","trust","api-keys","webhooks","logs"],"example":"email"},"DashboardPinnedItems":{"type":"array","maxItems":64,"uniqueItems":true,"description":"Ordered product and platform-tool keys pinned in this workspace's sidebar. An empty list hides all optional pins.","items":{"$ref":"#/components/schemas/DashboardPinnedItem"},"example":["email","sms","api-keys"]},"DashboardHomeWidget":{"type":"string","description":"Existing product metric section available on personal Home.","enum":["email.summary","email.delivery","sms.summary","sms.delivery","whatsapp.summary","whatsapp.delivery","voice.summary","voice.direction","verify.summary","verify.funnel","realtime.summary","realtime.messages"],"example":"email.summary"},"DashboardHomeWidgets":{"type":"array","maxItems":6,"uniqueItems":true,"description":"Ordered metric-section keys shown on Home in this workspace. An empty list shows no metric sections.","items":{"$ref":"#/components/schemas/DashboardHomeWidget"},"example":["email.summary","email.delivery"]},"DashboardIntroductionVersion":{"type":"string","enum":["navigation-home.v1"],"description":"Dashboard introduction release completed or dismissed by the caller, across all workspaces.","example":"navigation-home.v1"},"DashboardPreferences":{"type":"object","additionalProperties":false,"required":["pinned_items","home_widgets","introduction_version"],"properties":{"pinned_items":{"oneOf":[{"$ref":"#/components/schemas/DashboardPinnedItems"},{"type":"null"}],"description":"Saved sidebar pins, or null to use the dashboard defaults."},"home_widgets":{"oneOf":[{"$ref":"#/components/schemas/DashboardHomeWidgets"},{"type":"null"}],"description":"Saved Home metric sections, or null to use the dashboard defaults."},"introduction_version":{"oneOf":[{"$ref":"#/components/schemas/DashboardIntroductionVersion"},{"type":"null"}],"description":"Last dismissed introduction release, or null if none has been dismissed."}}},"DashboardPreferencesUpdate":{"type":"object","additionalProperties":false,"properties":{"pinned_items":{"$ref":"#/components/schemas/DashboardPinnedItems"},"home_widgets":{"$ref":"#/components/schemas/DashboardHomeWidgets"},"introduction_version":{"$ref":"#/components/schemas/DashboardIntroductionVersion"}}},"SortOrder":{"type":"string","enum":["asc","desc"],"description":"Sort direction, ascending or descending."},"AuthUserWorkspaceOrganization":{"type":"object","additionalProperties":false,"required":["id","name","region"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"region":{"$ref":"#/components/schemas/Region","readOnly":true},"name":{"type":"string","minLength":1,"description":"Display name of the organization.","example":"Acme Inc."},"role":{"type":["string","null"],"minLength":1,"description":"The caller's role in this organization, if any."}}},"AuthUserWorkspace":{"type":"object","additionalProperties":false,"required":["id","name","organization"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"name":{"type":"string","minLength":1,"description":"Display name of the workspace.","example":"Production"},"role":{"type":["string","null"],"minLength":1,"description":"The caller's role in this workspace, if any."},"organization":{"$ref":"#/components/schemas/AuthUserWorkspaceOrganization"}}},"_ListEnvelope":{"type":"object","required":["next_cursor","prev_cursor","refresh_cursor"],"properties":{"next_cursor":{"type":["string","null"],"description":"Cursor for the next page. Pass back as `starting_after` to advance forward. `null` when no next page exists.","example":"eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE0OjAzOjEwWlwiIiwiaSI6IjAxOTJmM2IxLTRjN2UtN2EyYi05ZDYxLThmM2E1YzJlN2I0MCJ9"},"prev_cursor":{"type":["string","null"],"description":"Cursor for the previous page. Pass back as `ending_before` to step backward. `null` when no previous page exists.","example":null},"refresh_cursor":{"type":["string","null"],"description":"Refresh anchor, the first row of this response. Pass back as `ending_before` to fetch what precedes it in the current sort order. On a newest-first sort those are the items that have appeared since; on any other sort they are the items that sort earlier, so refreshing such a list means re-fetching it instead. Non-`null` whenever `data` is non-empty; `null` only on an empty page. Distinct from `prev_cursor`.","example":"eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE2OjQyOjAxWlwiIiwiaSI6IjAxOTJmM2IxLTllMDQtN2NkMy1iODE3LTJhNmY0ZDFjOGUwOSJ9"}}},"_ListEnvelopeWithTotal":{"allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","properties":{"total":{"type":["integer","null"],"format":"int64","minimum":0,"description":"Total number of items matching the request's filters across all pages. Present only when `include_total=true` was passed; otherwise `null`."}}}]},"AuthUserWorkspaceList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Workspaces the authenticated user belongs to.","items":{"$ref":"#/components/schemas/AuthUserWorkspace"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AuthUserOrganization":{"type":"object","additionalProperties":false,"required":["id","name","region","role"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"name":{"type":"string","minLength":1,"description":"Display name of the organization.","example":"Acme Inc."},"region":{"$ref":"#/components/schemas/Region","readOnly":true},"role":{"type":["string","null"],"minLength":1,"description":"The caller's role in this organization."}}},"AuthUserOrganizationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Organizations the authenticated user has an org-level role in.","items":{"$ref":"#/components/schemas/AuthUserOrganization"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"NotifyChannel":{"type":"string","minLength":1,"description":"Product channel associated with a dashboard availability notification request.","enum":["sms","whatsapp","rcs","voice","lookup","verifications","realtime","push","numbers","esim"],"example":"whatsapp"},"ChannelNotification":{"type":"object","additionalProperties":false,"required":["channel","requested_at"],"properties":{"channel":{"$ref":"#/components/schemas/NotifyChannel"},"requested_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When you asked to be notified about this channel."}}},"ChannelNotificationList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The channels you have asked to be notified about.","items":{"$ref":"#/components/schemas/ChannelNotification"}}}},"ChannelNotificationCreate":{"type":"object","additionalProperties":false,"required":["channel"],"properties":{"channel":{"$ref":"#/components/schemas/NotifyChannel"}}},"InvitationPreview":{"type":"object","additionalProperties":false,"required":["email","organization_name","inviter_name","region"],"properties":{"email":{"type":"string","format":"email","minLength":1,"description":"Email address this invitation was sent to.","example":"alice@example.com"},"organization_name":{"type":"string","minLength":1,"description":"Name of the organization the recipient is invited to join.","example":"Acme Corp"},"inviter_name":{"type":"string","minLength":1,"description":"Display name of the person who sent the invitation.","example":"Bob Smith"},"region":{"$ref":"#/components/schemas/Region","readOnly":true,"description":"The data region the organization is hosted in. Send the accept request to that region's API host."}},"description":"Details about a pending invitation, identified by the invitation token."},"AcceptInvitationAndRegisterRequest":{"type":"object","additionalProperties":false,"required":["token","full_name","password"],"properties":{"token":{"type":"string","minLength":1,"x-sensitive":true,"description":"Invitation token from the invite email."},"full_name":{"type":"string","minLength":1,"maxLength":255,"description":"Display name for the new account.","example":"Alice Wanderer"},"password":{"type":"string","minLength":12,"maxLength":1024,"x-sensitive":true,"description":"Password for the new account. Minimum 12 characters.","example":"correct-horse-battery-staple"}}},"InvitationRegistrationResult":{"type":"object","additionalProperties":false,"required":["workspace_id"],"properties":{"workspace_id":{"readOnly":true,"description":"ID of the workspace the new account was granted access to. `null` when the invitation grants only an organization-level role with no workspace assignment.\n","oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}]}},"description":"Returned after a successful invitation acceptance with new account registration."},"InvitationID":{"type":"string","minLength":1,"pattern":"^ivt_[0-9a-hjkmnp-tv-z]{26}$","example":"ivt_01krdgeqcxet5s7t44vh8rt9mg"},"AuthUserInvitation":{"type":"object","additionalProperties":false,"required":["id","org_name","inviter_name","expires_at","region"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/InvitationID","description":"Invitation ID. Pass to the accept endpoint to accept without the email token."},"org_name":{"type":"string","minLength":1,"description":"Name of the organization the user has been invited to join."},"inviter_name":{"type":"string","minLength":1,"description":"Display name of the team member who sent the invitation."},"expires_at":{"type":"string","format":"date-time","readOnly":true,"minLength":1,"description":"When the invitation expires."},"region":{"$ref":"#/components/schemas/Region","readOnly":true,"description":"The data region the organization is hosted in. Send the accept request to that region's API host."}}},"AuthUserInvitationList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Pending invitations for the authenticated user.","items":{"$ref":"#/components/schemas/AuthUserInvitation"}}}},"OrgRoleEnum.gen":{"type":"string","enum":["billing_admin","owner"]},"WorkspaceRoleEnum.gen":{"type":"string","minLength":1,"enum":["admin","analyst","developer"]},"OrganizationInvitationWorkspaceRole":{"type":"object","additionalProperties":false,"required":["workspace_id","role"],"properties":{"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"role":{"$ref":"#/components/schemas/WorkspaceRoleEnum.gen","description":"Workspace role to grant on acceptance:\n\n- `admin`: full read and write access, including managing members.\n- `developer`: read and write access to messaging and sending\n  features; read-only on members and workspace settings.\n- `analyst`: read-only access to the workspace.\n"}}},"OrganizationInvitation":{"type":"object","additionalProperties":false,"required":["id","organization_id","email","org_role","workspace_roles","invited_by","status","expires_at","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/InvitationID","description":"Invitation ID."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"ID of the organization the invitation grants access to."},"email":{"type":"string","format":"email","minLength":5,"description":"Address the invitation was sent to.","example":"bob@acme.com"},"org_role":{"oneOf":[{"$ref":"#/components/schemas/OrgRoleEnum.gen"},{"type":"null"}],"description":"Org-level role to be granted on acceptance. `null` for workspace-only invitations."},"workspace_roles":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationInvitationWorkspaceRole"},"description":"Workspace role assignments to be granted on acceptance."},"invited_by":{"readOnly":true,"$ref":"#/components/schemas/UserID","description":"User ID of the person who created this invitation."},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["pending","accepted","revoked","expired"],"description":"Computed status of the invitation:\n\n- `pending`: not yet accepted, revoked, or expired.\n- `accepted`: the recipient accepted it.\n- `revoked`: canceled before it was accepted or expired.\n- `expired`: `expires_at` passed unaccepted.\n"},"expires_at":{"type":"string","minLength":1,"readOnly":true,"format":"date-time","description":"When the invitation expires."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the invitation was created."}}},"OAuthGrantSubject":{"type":"string","minLength":1,"readOnly":true,"enum":["user","service_account"],"description":"What the access belongs to once you approve.\n\n- `user`: it acts as you, and it stops working when your own access does.\n- `service_account`: it belongs to the workspace rather than to you, so it\n  keeps working after you leave.\n"},"OAuthScopeGrant":{"type":"object","additionalProperties":false,"description":"A permission scope paired with the access level it is granted at.","required":["scope","level"],"properties":{"scope":{"type":"string","minLength":1,"description":"The permission scope name, for example `emails` or `org:members`.","example":"emails"},"level":{"type":"string","minLength":1,"enum":["read","write"],"description":"The access level for this scope. `write` includes `read`.","example":"write"}}},"OAuthConsentTarget":{"type":"object","additionalProperties":false,"description":"One organization or workspace the user can grant the application access to, together with the subset of the requested scopes the user is actually able to delegate for it. A scope the user does not hold is never present here.\n","required":["kind","organization_id","organization_name","region","grantable_scopes"],"properties":{"kind":{"type":"string","minLength":1,"enum":["workspace","org"],"description":"Whether this target grants access to a single workspace or to the whole organization."},"organization_id":{"$ref":"#/components/schemas/OrganizationID","description":"The organization this target belongs to."},"organization_name":{"type":"string","minLength":1,"description":"Display name of the organization."},"region":{"$ref":"#/components/schemas/Region","description":"The data region the organization is hosted in."},"workspace_id":{"description":"The workspace this target grants access to. `null` for organization targets.","oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}]},"workspace_name":{"type":["string","null"],"description":"Display name of the workspace. `null` for organization targets."},"grantable_scopes":{"type":"array","description":"The requested scopes the user can delegate for this target, each capped to the level the user holds.","items":{"$ref":"#/components/schemas/OAuthScopeGrant"}}}},"OAuthConsentView":{"type":"object","additionalProperties":false,"description":"Data for rendering the consent screen for a pending authorization request. Includes the requesting application, creation time, requested scopes, and the organizations and workspaces the user can grant.","required":["client_id","client_name","client_trust","flow","requested_scopes","created_at","grant_subject","targets"],"properties":{"client_id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier of the application requesting access.","example":"bird-cli"},"client_name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the application requesting access.","example":"Bird CLI"},"client_trust":{"type":"string","minLength":1,"enum":["verified","local","unverified"],"readOnly":true,"description":"How far we vouch for the requesting application.\n\n- `verified`: one of our first-party applications or a known hosted\n  application whose redirect uses its own domain.\n- `local`: it runs on your device. Its name is self-reported, but the\n  authorization never leaves your machine.\n- `unverified`: self-registered and hosted. It chose its own display\n  name, so authorize it only if you initiated the connection.\n"},"flow":{"type":"string","minLength":1,"enum":["pkce","device"],"readOnly":true,"description":"The authorization flow that started this request. `device` requests were started on another device; review the creation time to confirm you initiated it.\n"},"requested_scopes":{"type":"array","readOnly":true,"description":"The scopes the application asked for. The user may decline any of them.","items":{"type":"string","minLength":1}},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the authorization request was created."},"grant_subject":{"$ref":"#/components/schemas/OAuthGrantSubject","readOnly":true},"targets":{"type":"array","readOnly":true,"description":"The organizations and workspaces the user can grant the application access to.","items":{"$ref":"#/components/schemas/OAuthConsentTarget"}}}},"OAuthConsentRequest":{"type":"object","additionalProperties":false,"description":"The user's decision for a pending authorization request. To approve, set `approve` to `true` and choose a target: provide `workspace_id` for a workspace grant, or `organization_id` alone for an organization grant. To deny, set `approve` to `false`; the target and scopes are then ignored.\n","required":["approve"],"properties":{"approve":{"type":"boolean","description":"Whether to approve the request. `false` denies it."},"organization_id":{"description":"The organization to grant access to. Required to approve an organization target.","oneOf":[{"$ref":"#/components/schemas/OrganizationID"},{"type":"null"}]},"workspace_id":{"description":"The workspace to grant access to. Provide this to approve a workspace target.","oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}]},"approved_scopes":{"type":"array","description":"The scopes the user agreed to grant. Each is capped to what the user holds for the chosen target.","items":{"$ref":"#/components/schemas/OAuthScopeGrant"}}}},"OAuthConsentResponse":{"type":"object","additionalProperties":false,"description":"The outcome of submitting a consent decision.","required":["flow","denied"],"properties":{"flow":{"type":"string","minLength":1,"enum":["pkce","device"],"readOnly":true,"description":"The authorization flow the request used."},"denied":{"type":"boolean","readOnly":true,"description":"Whether the request was denied."},"redirect_uri":{"type":["string","null"],"readOnly":true,"description":"For browser-based (PKCE) flows, the URL to send the browser back to, carrying the result to the application. `null` for device flows, where the application is polling and the user can return to it.\n"}}},"OAuthDeviceLookupResponse":{"type":"object","additionalProperties":false,"description":"The authorization request resolved from a device user code.","required":["request_id"],"properties":{"request_id":{"type":"string","minLength":1,"format":"uuid","readOnly":true,"description":"Identifier of the authorization request to load the consent screen for."}}},"OAuthGrantID":{"type":"string","minLength":1,"pattern":"^oag_[0-9a-hjkmnp-tv-z]{26}$","example":"oag_01krdgeqcxet5s7t44vh8rt9mg"},"OAuthGrant":{"type":"object","additionalProperties":false,"description":"A connected CLI or MCP application session: an active grant of access the user approved, including its target, the scopes it holds, and when it was last used.\n","required":["id","client_id","client_name","organization_id","region","scopes","created_at"],"properties":{"id":{"$ref":"#/components/schemas/OAuthGrantID","readOnly":true,"description":"Unique identifier of the grant."},"client_id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier of the connected application.","example":"bird-cli"},"client_name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the connected application.","example":"Bird CLI"},"organization_id":{"$ref":"#/components/schemas/OrganizationID","readOnly":true,"description":"The organization the grant gives access to."},"workspace_id":{"readOnly":true,"description":"The workspace the grant gives access to. `null` for organization-level grants.","oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}]},"region":{"$ref":"#/components/schemas/Region","readOnly":true,"description":"The data region the grant operates in."},"scopes":{"type":"array","readOnly":true,"description":"The scopes this grant holds.","items":{"$ref":"#/components/schemas/OAuthScopeGrant"}},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the user approved the grant."},"last_used_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the grant was last used to obtain an access token. `null` if never used."}}},"OAuthGrantList":{"type":"object","additionalProperties":false,"description":"The authenticated user's connected CLI and MCP application sessions.","required":["data"],"properties":{"data":{"type":"array","description":"The user's active grants, one per connected application and target.","items":{"$ref":"#/components/schemas/OAuthGrant"}}}},"OAuthClientID":{"type":"string","minLength":1,"pattern":"^oac_[0-9a-hjkmnp-tv-z]{26}$","example":"oac_01krdgeqcxet5s7t44vh8rt9mg"},"AdminOAuthClientRedirectURIs":{"type":"array","minItems":1,"maxItems":10,"uniqueItems":true,"description":"Where the authorization code may be delivered. Every URI must be an absolute `https` URL with no fragment, and must match the one the platform sends exactly.\n","items":{"type":"string","format":"uri","minLength":1,"maxLength":512,"example":"https://zapier.com/dashboard/auth/oauth/return/BirdAPI/"}},"AdminOAuthClientScopeAllowlist":{"type":"array","minItems":1,"maxItems":50,"uniqueItems":true,"description":"The scopes this client may ask a user for. Every entry names a workspace scope a user can delegate, without an access level: organization and admin scopes are never issuable to a platform, and how far into a scope a token reaches is the consenting user's own permissions to decide.\n","items":{"type":"string","minLength":1,"example":"emails"}},"AdminOAuthClient":{"type":"object","additionalProperties":false,"description":"A confidential OAuth client Bird registers for a platform integration. Its secret is never readable after the response that minted it.\n","required":["client_id","name","redirect_uris","scope_allowlist","require_pkce","verified","active_grant_count","created_at","updated_at","revoked_at","previous_secret_expires_at"],"properties":{"client_id":{"readOnly":true,"$ref":"#/components/schemas/OAuthClientID"},"name":{"type":"string","minLength":1,"maxLength":200,"description":"The platform's name, shown to the user on the consent screen.","example":"Zapier"},"redirect_uris":{"$ref":"#/components/schemas/AdminOAuthClientRedirectURIs"},"scope_allowlist":{"$ref":"#/components/schemas/AdminOAuthClientScopeAllowlist","description":"The scopes this client may ask a user for. A request for anything outside the list is refused rather than trimmed, so a misconfigured platform fails visibly. Narrowing the list applies to the next token an existing connection issues, without disconnecting it, unless the narrowing removes every scope that connection was granted, in which case its next token is refused.\n"},"require_pkce":{"type":"boolean","description":"Whether the platform must run PKCE. Leave it on unless the platform cannot send a code challenge, in which case its secret stands as the sole proof of possession.\n"},"verified":{"type":"boolean","readOnly":true,"description":"Always true for a client registered here. The consent screen vouches for the name above instead of warning the user about it.\n"},"active_grant_count":{"type":"integer","minimum":0,"readOnly":true,"description":"How many live connections the platform holds. One user's consent to one workspace is one connection, so a workspace several people connected counts once per person.\n"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the client was registered."},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the registration was last written. A client nobody has edited carries its creation time here, not `null`.\n"},"revoked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the registration was revoked, or `null` while it is live. Revocation is permanent.\n"},"previous_secret_expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the secret replaced by the last rotation stops being accepted, or `null` when no overlap window was opened: either the client has never been rotated, or its last rotation retired the previous secret immediately. A timestamp in the past means the window has closed. This is not a \"has it ever been rotated\" signal, since a zero-overlap rotation clears it.\n"}}},"AdminOAuthClientList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminOAuthClient"}}}},"AdminOAuthClientCreate":{"type":"object","additionalProperties":false,"required":["name","redirect_uris","scope_allowlist"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"The platform's name, shown to the user on the consent screen.","example":"Zapier"},"redirect_uris":{"$ref":"#/components/schemas/AdminOAuthClientRedirectURIs"},"scope_allowlist":{"$ref":"#/components/schemas/AdminOAuthClientScopeAllowlist"},"require_pkce":{"type":"boolean","default":true,"description":"Whether the platform must run PKCE. Leave it on unless the platform cannot send a code challenge, in which case its secret stands as the sole proof of possession.\n"}}},"AdminOAuthClientWithSecret":{"allOf":[{"$ref":"#/components/schemas/AdminOAuthClient"},{"type":"object","required":["client_secret"],"properties":{"client_secret":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"The client secret, returned only on create and rotate and never again. Hand it to the platform and store it securely.\n","example":"2Fh1kZ8bQ0nJ7yGx4Lc9tWv3sRp6eAdMuKiNoTbXqYs"}}}]},"AdminOAuthClientUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"description":"Only the properties present in the request are changed.","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"The platform's name, shown to the user on the consent screen.","example":"Zapier"},"redirect_uris":{"$ref":"#/components/schemas/AdminOAuthClientRedirectURIs","description":"Replaces the registered set."},"scope_allowlist":{"$ref":"#/components/schemas/AdminOAuthClientScopeAllowlist","description":"Replaces the allowlist. Narrowing it applies to the next token an existing connection issues, so a live integration loses the removed scopes without being disconnected. A connection left with no granted scope at all has its next token refused instead.\n"},"require_pkce":{"type":"boolean","description":"Whether the platform must run PKCE."}}},"AdminOAuthClientRotateSecret":{"type":"object","additionalProperties":false,"properties":{"overlap_hours":{"type":"integer","minimum":0,"maximum":168,"default":24,"description":"How long the replaced secret keeps working, giving the platform time to pick up the new one. Zero cuts it off immediately. Rotating again replaces the secret being held over, so rotating twice in quick succession stops the first secret working at once.\n"}}},"ExternalIdentity":{"type":"object","additionalProperties":false,"required":["provider","created_at"],"properties":{"provider":{"type":"string","minLength":1,"enum":["google","github"],"readOnly":true,"description":"The social provider this identity belongs to."},"email_at_link":{"type":["string","null"],"readOnly":true,"description":"The verified email seen when this provider was connected. Informational only.","example":"you@example.com"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the provider was connected."},"last_login_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this provider was last used to sign in."}},"description":"A social identity (Google or GitHub) connected to the user's account."},"ExternalIdentityList":{"type":"object","additionalProperties":false,"properties":{"data":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/ExternalIdentity"}}},"description":"The social identities connected to the user's account."},"SocialStartResponse":{"type":"object","additionalProperties":false,"required":["authorize_url","state"],"properties":{"authorize_url":{"type":"string","minLength":1,"readOnly":true,"description":"The provider authorization URL the browser should be redirected to.","example":"https://accounts.google.com/o/oauth2/v2/auth?client_id=..."},"state":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Opaque anti-forgery value. Store it, such as in a cookie, and confirm it matches the state returned on the callback before continuing.\n","example":"3q2-7Ycab1Z9..."}},"description":"Returned when a social login flow is started."},"SocialAuthResult":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","minLength":1,"enum":["session","mfa_required","collect_email","linked"],"readOnly":true,"description":"Outcome of the flow.\n\n- `session`: sign-in succeeded and a session cookie was set.\n- `mfa_required`: multi-factor verification is needed before creating a\n  session.\n- `collect_email`: the provider returned no verified email, so one must be\n  collected and verified.\n- `linked`: a provider was connected to the already-signed-in account.\n"},"user_id":{"readOnly":true,"description":"ID of the signed-in user. Present for `session` and `linked`.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"is_new_user":{"type":"boolean","readOnly":true,"default":false,"description":"`true` when `status` is `session` and the flow created the account. Continue to onboarding.\n"},"challenge_token":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Short-lived token for continuing MFA. Present only when `status` is `mfa_required`."},"factors":{"type":["array","null"],"readOnly":true,"description":"Available MFA factors. Present only when `status` is `mfa_required`.","items":{"$ref":"#/components/schemas/MFAFactorSummary"}},"webauthn_available":{"type":"boolean","readOnly":true,"default":false,"description":"`true` when the user has a registered security key that can be used to sign in instead of an MFA factor. Present only when `status` is `mfa_required`. Start a sign-in ceremony with POST /v1/auth/mfa/webauthn/authenticate/start.\n","example":false},"pending_signup_id":{"type":["string","null"],"readOnly":true,"description":"Identifies the pending signup to attach an email to. Present only when `status` is `collect_email`.","example":"8f1d2c34-..."},"binding_token":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Token that ties the email submission to this browser. Present only when `status` is `collect_email`. Include it in the subsequent email-submit request.","example":"tok_..."}},"description":"The result of a social login callback or email verification."},"SocialEmailRequest":{"type":"object","additionalProperties":false,"required":["pending_signup_id","email","binding_token"],"properties":{"pending_signup_id":{"type":"string","minLength":1,"description":"The pending signup identifier returned by the collect_email step.","example":"8f1d2c34-..."},"email":{"type":"string","format":"email","minLength":3,"description":"The email address to verify and associate with the new account.","example":"you@example.com"},"binding_token":{"type":"string","minLength":1,"x-sensitive":true,"description":"Token returned in the collect_email response. Ties this submission to the browser that started the social login.","example":"tok_..."}},"description":"Submits an email address to verify when a social account had no verified email."},"SSOLookupRequest":{"type":"object","additionalProperties":false,"required":["email"],"properties":{"email":{"type":"string","format":"email","minLength":5,"maxLength":254,"description":"The email address entered on the sign-in page. Only its domain is used to determine whether single sign-on is available; the rest of the address is ignored and is never used to check whether an account exists.\n","example":"alice@example.com"}},"description":"Identifies the sign-in domain to check for enterprise single sign-on."},"SSOProtocol":{"type":"string","minLength":1,"enum":["saml","oidc"],"description":"The protocol a single sign-on connection speaks. `oidc` uses OpenID Connect with an authorization-code flow; `saml` uses SAML 2.0. A connection's protocol is fixed when it is created.\n"},"SSOConnection":{"type":"object","additionalProperties":false,"required":["connection_id","protocol","display_name","initiate_url"],"properties":{"connection_id":{"type":"string","minLength":1,"format":"uuid","readOnly":true,"description":"Identifier of the single sign-on connection to begin the flow with.","example":"7f3a9c21-5b8e-4d0a-9f16-2c7e4b1d8a30"},"protocol":{"readOnly":true,"example":"oidc","allOf":[{"$ref":"#/components/schemas/SSOProtocol"}]},"display_name":{"type":"string","minLength":1,"readOnly":true,"description":"The organization name to show next to the sign-in button.","example":"Acme Corp"},"initiate_url":{"type":"string","minLength":1,"readOnly":true,"description":"The absolute URL to send the browser to, to start the single sign-on flow.","example":"https://bird.com/dashboard/auth/sso/oidc/7f3a9c21-5b8e-4d0a-9f16-2c7e4b1d8a30/authorize"}},"description":"A single sign-on connection available for the requested domain."},"SSOLookupResponse":{"type":"object","additionalProperties":false,"required":["sso_available","connections"],"properties":{"sso_available":{"type":"boolean","readOnly":true,"description":"Whether an active single sign-on connection handles the requested domain. Always present regardless of whether an account exists for the address.\n","example":true},"connections":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/SSOConnection"},"description":"The single sign-on connections available for the domain. Empty when none are available. Iterate this list rather than assuming a single entry.\n"}},"description":"The single sign-on availability for a sign-in domain."},"SessionLocation":{"type":"object","additionalProperties":false,"description":"Approximate location of a session, resolved from its IP address.","properties":{"city":{"type":["string","null"],"description":"City name. Null when only the country could be resolved.","example":"Warsaw"},"country":{"type":["string","null"],"description":"Country name.","example":"Poland"},"country_code":{"type":["string","null"],"description":"ISO 3166-1 alpha-2 country code.","example":"PL"}}},"SessionInfo":{"type":"object","additionalProperties":false,"required":["id","created_at","last_seen_at","is_current"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"Opaque, stable identifier for the session, safe to display and to pass to the revoke endpoint. Derived from the session, never the session token itself.\n","example":"a3f2c1d4e5b60789"},"user_agent":{"type":["string","null"],"readOnly":true,"description":"Browser or application identifier recorded when the session was created.","example":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"},"ip":{"type":["string","null"],"readOnly":true,"description":"IP address from when the session was created.","example":"203.0.113.42"},"device_name":{"type":["string","null"],"readOnly":true,"description":"Human-readable device label derived from the browser and operating system, for example \"Chrome on macOS\". Null when it could not be determined.","example":"Chrome on macOS"},"device_type":{"type":["string","null"],"enum":["desktop","mobile","tablet","unknown"],"readOnly":true,"description":"Coarse device category, suitable for choosing an icon. Null when it could not be determined.","example":"desktop"},"os":{"type":["string","null"],"readOnly":true,"description":"Operating system the session signed in from. Null when it could not be determined.","example":"macOS"},"browser":{"type":["string","null"],"readOnly":true,"description":"Browser the session signed in from. Null when it could not be determined.","example":"Chrome"},"location":{"description":"Approximate location of the session, resolved from its IP address. Null when it could not be determined.","oneOf":[{"$ref":"#/components/schemas/SessionLocation"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this session was created.","example":"2026-05-07T14:30:00Z"},"last_seen_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this session last made a request. Updated at most once per minute.","example":"2026-05-07T15:00:00Z"},"is_current":{"type":"boolean","readOnly":true,"description":"Whether this is the session making the current request.","example":true}}},"SessionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"All active sessions for the authenticated user.","items":{"$ref":"#/components/schemas/SessionInfo"}}},"description":"All active sessions, ordered by `created_at` descending. This list is not paginated.\n"},"VerifyDeviceOTPRequest":{"type":"object","additionalProperties":false,"required":["challenge_token","code"],"properties":{"challenge_token":{"type":"string","minLength":1,"x-sensitive":true,"description":"Short-lived challenge token returned by the login endpoint when `email_otp_required` is `true`.\n","example":"ch_01abc"},"code":{"type":"string","minLength":6,"maxLength":6,"description":"Six-digit one-time passcode sent to the account email address.\n","example":"123456"},"trust_device":{"type":"boolean","default":false,"description":"When `true`, the current device is remembered for future logins. A device trust cookie is set via `Set-Cookie`, valid for 30 days. On subsequent logins from this device the one-time passcode step is skipped until the cookie expires or is revoked.\n","example":false}}},"DeviceOTPVerifyResponse":{"type":"object","additionalProperties":false,"required":["user_id"],"properties":{"user_id":{"$ref":"#/components/schemas/UserID","readOnly":true,"description":"ID of the authenticated user."}},"description":"Returned when device OTP verification succeeds. A session cookie is set via `Set-Cookie`. When `trust_device` was `true` in the request, a device trust cookie is also set via `Set-Cookie`.\n"},"ResendDeviceOTPRequest":{"type":"object","additionalProperties":false,"required":["challenge_token"],"properties":{"challenge_token":{"type":"string","minLength":1,"x-sensitive":true,"description":"Short-lived challenge token returned by the login endpoint when `email_otp_required` is `true`.\n","example":"ch_01abc"}}},"MFAEnrollRequest":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","minLength":1,"enum":["totp","sms"],"description":"The type of MFA factor to enroll.","example":"totp"},"name":{"type":["string","null"],"description":"Optional label for this factor.","example":"My phone"},"phone_number":{"type":["string","null"],"description":"E.164 phone number. Required for SMS enrollment.","example":"+15555550100"}}},"MFAEnrollTOTPDetails":{"type":"object","additionalProperties":false,"readOnly":true,"description":"TOTP enrollment details. Present only when type is totp.","properties":{"uri":{"type":"string","x-sensitive":true,"description":"TOTP URI for use with authenticator apps.","example":"otpauth://totp/Bird:alice@example.com?secret=BASE32SECRET&issuer=Bird"},"secret":{"type":"string","x-sensitive":true,"description":"Base32-encoded TOTP secret.","example":"BASE32SECRET"},"qr_code":{"type":"string","x-sensitive":true,"description":"Base64-encoded QR code image (PNG) encoding the TOTP URI.","example":"iVBORw0KGgo..."}}},"MFAChallengeID":{"type":"string","minLength":1,"pattern":"^mfc_[0-9a-hjkmnp-tv-z]{26}$","example":"mfc_01krdgeqcxet5s7t44vh8rt9mg"},"MFAEnrollSMSDetails":{"type":"object","additionalProperties":false,"readOnly":true,"description":"SMS enrollment details. Present only when type is sms.","properties":{"phone_last4":{"type":"string","description":"Last 4 digits of the enrolled phone number.","example":"0100"},"enrollment_challenge_id":{"$ref":"#/components/schemas/MFAChallengeID","description":"Challenge ID returned by the SMS enrollment step. Use this in the verify step."}}},"MFAEnrollResponse":{"type":"object","additionalProperties":false,"required":["factor_id"],"properties":{"factor_id":{"$ref":"#/components/schemas/MFAFactorID","readOnly":true,"description":"ID of the newly created factor. Use this in the verify step."},"totp":{"type":["object","null"],"readOnly":true,"description":"TOTP enrollment details. Present only when type is totp.","allOf":[{"$ref":"#/components/schemas/MFAEnrollTOTPDetails"}]},"sms":{"type":["object","null"],"readOnly":true,"description":"SMS enrollment details. Present only when type is sms.","allOf":[{"$ref":"#/components/schemas/MFAEnrollSMSDetails"}]}}},"MFAEnrollVerifyRequest":{"type":"object","additionalProperties":false,"required":["factor_id","code"],"properties":{"factor_id":{"$ref":"#/components/schemas/MFAFactorID","description":"ID of the factor being verified."},"code":{"type":"string","minLength":6,"maxLength":6,"description":"6-digit OTP code from the authenticator app or SMS.","example":"123456"},"enrollment_challenge_id":{"$ref":"#/components/schemas/MFAChallengeID","description":"Challenge ID returned by the SMS enrollment step. Required for SMS factor verification."}}},"MFAFactorStatus":{"type":"string","minLength":1,"enum":["unverified","verified","disabled"],"description":"Lifecycle status of an MFA factor.","example":"verified"},"MFAEnrollVerifyResponse":{"type":"object","additionalProperties":false,"required":["factor_id","status"],"properties":{"factor_id":{"$ref":"#/components/schemas/MFAFactorID","readOnly":true,"description":"ID of the verified factor."},"status":{"$ref":"#/components/schemas/MFAFactorStatus","readOnly":true,"description":"Factor status after verification."},"recovery_codes":{"type":["array","null"],"readOnly":true,"x-sensitive":true,"description":"One-time recovery codes, generated only when you don't already have an unused set (typically your first verified factor). `null` on a later factor if you still have unused codes from before. Store them securely because they are shown only once.","items":{"type":"string"}}}},"MFAEnrollResendRequest":{"type":"object","additionalProperties":false,"required":["factor_id","enrollment_challenge_id"],"properties":{"factor_id":{"$ref":"#/components/schemas/MFAFactorID","description":"ID of the SMS factor with a pending enrollment."},"enrollment_challenge_id":{"$ref":"#/components/schemas/MFAChallengeID","description":"Challenge ID returned by the SMS enrollment step."}}},"MFAEnrollResendResponse":{"type":"object","additionalProperties":false,"required":["message_sent"],"properties":{"message_sent":{"type":"boolean","readOnly":true,"description":"Whether a new verification code was sent.","example":true}}},"MFAChallengeRequest":{"type":"object","additionalProperties":false,"required":["challenge_token","factor_id"],"properties":{"challenge_token":{"type":"string","minLength":1,"x-sensitive":true,"description":"Short-lived challenge token returned by the login endpoint.","example":"ch_01abc"},"factor_id":{"$ref":"#/components/schemas/MFAFactorID","description":"ID of the MFA factor to use for this challenge."}}},"MFAChallengeResponse":{"type":"object","additionalProperties":false,"required":["message_sent"],"properties":{"message_sent":{"type":"boolean","readOnly":true,"description":"`true` when an SMS was dispatched. `false` for TOTP because no message is required.","example":true},"phone_last4":{"type":["string","null"],"readOnly":true,"description":"Last 4 digits of the phone number the SMS was sent to. `null` for TOTP challenges.","example":"0100"}}},"MFAVerifyRequest":{"type":"object","additionalProperties":false,"required":["challenge_token"],"properties":{"challenge_token":{"type":"string","minLength":1,"x-sensitive":true,"description":"Short-lived challenge token returned by the login endpoint.","example":"ch_01abc"},"factor_id":{"description":"ID of the factor being used. Required unless using a recovery code.","oneOf":[{"$ref":"#/components/schemas/MFAFactorID"},{"type":"null"}]},"code":{"type":["string","null"],"description":"OTP code from authenticator app or SMS.","example":"123456"},"recovery_code":{"type":["string","null"],"x-sensitive":true,"description":"Recovery code. Use instead of `factor_id` + code to bypass MFA.","example":"ABCD-EFGH-IJKL"}}},"MFAVerifyResponse":{"type":"object","additionalProperties":false,"required":["user_id"],"properties":{"user_id":{"$ref":"#/components/schemas/UserID","readOnly":true,"description":"ID of the authenticated user."},"recovery_codes_remaining":{"type":["integer","null"],"readOnly":true,"description":"Number of recovery codes remaining. Present when a recovery code was used.","example":7}}},"MFAFactor":{"type":"object","additionalProperties":false,"required":["id","type","status","created_at"],"properties":{"id":{"$ref":"#/components/schemas/MFAFactorID","readOnly":true,"description":"Factor ID."},"type":{"type":"string","minLength":1,"enum":["totp","sms"],"readOnly":true,"description":"Factor type.","example":"totp"},"name":{"type":["string","null"],"readOnly":true,"description":"User-defined label for this factor.","example":"My phone"},"status":{"$ref":"#/components/schemas/MFAFactorStatus","readOnly":true,"description":"Current status of this factor."},"phone_last4":{"type":["string","null"],"readOnly":true,"description":"Last 4 digits of phone. Present only for SMS factors.","example":"0100"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this factor was created.","example":"2026-05-01T09:00:00Z"}}},"MFAFactorList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"List of MFA factors registered by the user.","items":{"$ref":"#/components/schemas/MFAFactor"}}}},"MFARecoveryCodesStatus":{"type":"object","additionalProperties":false,"required":["generated","remaining"],"properties":{"generated":{"type":"boolean","readOnly":true,"description":"Whether a set of recovery codes has been generated for this account.","example":true},"remaining":{"type":"integer","readOnly":true,"description":"Number of unused recovery codes still available. Zero when none have been generated or all have been used.","example":7}}},"MFARecoveryCodesResponse":{"type":"object","additionalProperties":false,"properties":{"recovery_codes":{"type":"array","readOnly":true,"x-sensitive":true,"description":"Current recovery codes. Each call regenerates the codes and invalidates the previous codes.","items":{"type":"string"}}}},"SMSCountry":{"type":"object","additionalProperties":false,"required":["code","calling_code","name"],"properties":{"code":{"type":"string","minLength":2,"maxLength":2,"readOnly":true,"description":"ISO 3166-1 alpha-2 country code.","example":"NL"},"calling_code":{"type":"string","minLength":1,"readOnly":true,"description":"International calling code with + prefix.","example":"+31"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Country display name.","example":"Netherlands"}}},"SMSCountryList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/SMSCountry"},"description":"Countries that accept SMS OTP delivery."}}},"WebAuthnChallengeID":{"type":"string","minLength":1,"pattern":"^wch_[0-9a-hjkmnp-tv-z]{26}$","example":"wch_01krdgeqcxet5s7t44vh8rt9mg"},"WebAuthnRegisterStartResponse":{"type":"object","additionalProperties":false,"required":["challenge_id","options"],"properties":{"challenge_id":{"$ref":"#/components/schemas/WebAuthnChallengeID","readOnly":true,"description":"ID of the pending registration ceremony. Send it back with the completion request."},"options":{"type":"object","additionalProperties":true,"readOnly":true,"x-sensitive":true,"description":"Public-key credential creation options to pass to the browser's WebAuthn API (navigator.credentials.create) to create the new security key.\n"}}},"WebAuthnAssertion":{"type":"object","additionalProperties":true,"x-sensitive":true},"WebAuthnRegisterCompleteRequest":{"type":"object","additionalProperties":false,"required":["challenge_id","name","credential"],"properties":{"challenge_id":{"$ref":"#/components/schemas/WebAuthnChallengeID","description":"ID of the pending registration ceremony, returned by the start request."},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Label for the new security key.","example":"My laptop"},"credential":{"$ref":"#/components/schemas/WebAuthnAssertion","description":"The browser's response to navigator.credentials.create(), passed through unchanged.\n"}}},"WebAuthnCredentialID":{"type":"string","minLength":1,"pattern":"^wac_[0-9a-hjkmnp-tv-z]{26}$","example":"wac_01krdgeqcxet5s7t44vh8rt9mg"},"WebAuthnCredential":{"type":"object","additionalProperties":false,"required":["id","name","created_at","transports"],"properties":{"id":{"$ref":"#/components/schemas/WebAuthnCredentialID","readOnly":true,"description":"Security key ID."},"name":{"type":"string","minLength":1,"readOnly":true,"description":"User-defined label for this security key.","example":"My laptop"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this security key was registered.","example":"2026-05-01T09:00:00Z"},"last_used_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this security key last completed authentication. Null if it has never been used.","example":"2026-05-02T09:00:00Z"},"transports":{"type":"array","readOnly":true,"description":"Transports the security key advertised at registration, such as `usb`, `nfc`, or `internal`.","items":{"type":"string"}}}},"WebAuthnRegisterCompleteResponse":{"type":"object","additionalProperties":false,"required":["credential"],"properties":{"credential":{"readOnly":true,"description":"The newly registered security key.","allOf":[{"$ref":"#/components/schemas/WebAuthnCredential"}]},"recovery_codes":{"type":["array","null"],"readOnly":true,"x-sensitive":true,"description":"One-time recovery codes. Present when this is the first strong factor on the account. Store them securely because they are shown only once.","items":{"type":"string"}}}},"WebAuthnCredentialList":{"type":"object","additionalProperties":false,"required":["credentials"],"properties":{"credentials":{"type":"array","description":"Security keys registered by the user.","items":{"$ref":"#/components/schemas/WebAuthnCredential"}}}},"WebAuthnCredentialRenameRequest":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"New label for this security key.","example":"My laptop"}}},"WebAuthnAuthenticateStartRequest":{"type":"object","additionalProperties":false,"required":["challenge_token"],"properties":{"challenge_token":{"type":"string","minLength":1,"x-sensitive":true,"description":"Short-lived challenge token returned by the login endpoint.","example":"ch_01abc"}}},"WebAuthnAuthenticateStartResponse":{"type":"object","additionalProperties":false,"required":["challenge_id","options"],"properties":{"challenge_id":{"$ref":"#/components/schemas/WebAuthnChallengeID","readOnly":true,"description":"ID of the pending sign-in ceremony. Send it back with the completion request."},"options":{"type":"object","additionalProperties":true,"readOnly":true,"x-sensitive":true,"description":"Public-key credential request options to pass to the browser's WebAuthn API (navigator.credentials.get) to sign in with a security key.\n"}}},"WebAuthnAuthenticateCompleteRequest":{"type":"object","additionalProperties":false,"required":["challenge_token","challenge_id","assertion"],"properties":{"challenge_token":{"type":"string","minLength":1,"x-sensitive":true,"description":"Short-lived challenge token returned by the login endpoint.","example":"ch_01abc"},"challenge_id":{"$ref":"#/components/schemas/WebAuthnChallengeID","description":"ID of the pending sign-in ceremony, returned by the start request."},"assertion":{"$ref":"#/components/schemas/WebAuthnAssertion","description":"The browser's response to navigator.credentials.get(), passed through unchanged.\n"}}},"WebAuthnAuthenticateCompleteResponse":{"type":"object","additionalProperties":false,"required":["user_id"],"properties":{"user_id":{"$ref":"#/components/schemas/UserID","readOnly":true,"description":"ID of the authenticated user."}}},"WebAuthnPasswordlessCompleteRequest":{"type":"object","additionalProperties":false,"required":["challenge_id","assertion"],"properties":{"challenge_id":{"$ref":"#/components/schemas/WebAuthnChallengeID","description":"ID of the pending passwordless ceremony, returned by the start request."},"assertion":{"$ref":"#/components/schemas/WebAuthnAssertion","description":"The browser's response to navigator.credentials.get(), passed through unchanged. Its user handle identifies the signing-in user.\n"}}},"StepUpMethod":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","minLength":1,"enum":["totp","sms","email","password_email","webauthn","recovery_code"],"readOnly":true,"description":"Verification method available for step-up:\n\n- `totp`: A time-based one-time code from an authenticator app.\n- `sms`: A one-time code sent to an enrolled phone number. Carries `factor_id`.\n- `email`: A one-time code sent to the account's primary email address.\n- `password_email`: The same email code as `email`, plus the account's current password submitted alongside it as a second factor.\n- `webauthn`: A registered security key. Carries `passkey_count`.\n- `recovery_code`: A one-time recovery code issued when MFA was enrolled.\n","example":"totp"},"factor_id":{"$ref":"#/components/schemas/MFAFactorID","readOnly":true,"description":"The enrolled factor backing this method. Present for totp and sms."},"masked_target":{"type":["string","null"],"readOnly":true,"description":"Masked destination for the code. Shows the last four digits for `sms` and a masked address for `email` and `password_email`.\n","example":"***1234"},"passkey_count":{"type":["integer","null"],"readOnly":true,"description":"Number of registered security keys. Present only for webauthn.","example":2}}},"StepUpMethodsResponse":{"type":"object","additionalProperties":false,"required":["methods"],"properties":{"methods":{"type":"array","readOnly":true,"maxItems":10,"description":"Verification methods this user may use to re-verify their identity, in preference order. Email-based fallbacks appear only when the user has at most one strong factor, so the last factor can always be managed.\n","items":{"$ref":"#/components/schemas/StepUpMethod"}}}},"StepUpChallengeRequest":{"type":"object","additionalProperties":false,"required":["method"],"properties":{"method":{"type":"string","minLength":1,"enum":["sms","email","password_email"],"description":"Which code delivery to start. `sms` requires `factor_id`. `email` and `password_email` both send a code to the account's primary address, but verifying `password_email` also requires the account's current password alongside the code, as a second factor.\n","example":"email"},"factor_id":{"$ref":"#/components/schemas/MFAFactorID","description":"The enrolled SMS factor to send the code to. Required when method is sms."}}},"StepUpChallengeResponse":{"type":"object","additionalProperties":false,"required":["challenge_id"],"properties":{"challenge_id":{"$ref":"#/components/schemas/MFAChallengeID","readOnly":true,"description":"ID of the pending step-up challenge. Send it back with the verify request."},"masked_target":{"type":["string","null"],"readOnly":true,"description":"Masked destination the code was sent to.","example":"p***@bird.com"}}},"StepUpWebAuthnStartResponse":{"type":"object","additionalProperties":false,"required":["challenge_id","options"],"properties":{"challenge_id":{"$ref":"#/components/schemas/WebAuthnChallengeID","readOnly":true,"description":"ID of the pending step-up ceremony. Send it back with the verify request."},"options":{"type":"object","additionalProperties":true,"readOnly":true,"x-sensitive":true,"description":"Public-key credential request options to pass to the browser's WebAuthn API (navigator.credentials.get) to re-verify with a security key.\n"}}},"StepUpVerifyRequest":{"type":"object","additionalProperties":false,"required":["method"],"properties":{"method":{"type":"string","minLength":1,"enum":["totp","sms","email","password_email","webauthn","recovery_code"],"description":"Which verification method is being presented.","example":"totp"},"factor_id":{"$ref":"#/components/schemas/MFAFactorID","description":"The enrolled factor the code comes from. Required when method is totp."},"challenge_id":{"type":"string","minLength":1,"description":"Pending challenge ID. Use the challenge ID for `sms`, `email`, and `password_email`, or the ceremony ID for `webauthn`. Required for these methods.\n","example":"mfc_01krdgeqcxet5s7t44vh8rt9mg"},"code":{"type":"string","minLength":1,"maxLength":32,"x-sensitive":true,"description":"One-time code. Required for totp, sms, email, and password_email; also carries the recovery code when method is recovery_code.\n","example":"123456"},"password":{"type":"string","minLength":1,"maxLength":1024,"x-sensitive":true,"description":"The account's current password. Required when method is password_email.","example":"correct-horse-battery-staple"},"assertion":{"type":"object","additionalProperties":true,"x-sensitive":true,"description":"The browser's response to navigator.credentials.get(), passed through unchanged. Required when method is webauthn.\n"}}},"DocsSearchResult":{"type":"object","additionalProperties":false,"required":["title","section","slug","url","doc_url","markdown_url","token_estimate","score"],"properties":{"title":{"type":"string","minLength":1,"readOnly":true,"description":"Title of the documentation page this result belongs to."},"section":{"type":"string","minLength":1,"readOnly":true,"description":"Heading of the matching section within the page."},"slug":{"type":"string","minLength":1,"readOnly":true,"description":"Stable identifier of the page. Pass it as the `slug` parameter of `GET /v1/docs/pages` to read the whole page as Markdown."},"url":{"type":"string","minLength":1,"readOnly":true,"description":"Absolute URL of the matching section, including the heading anchor."},"doc_url":{"type":"string","minLength":1,"readOnly":true,"description":"Absolute URL of the page, without the section anchor. Results from the same page share it, so it can be used to group them."},"markdown_url":{"type":"string","minLength":1,"readOnly":true,"description":"Absolute URL that returns the page's full content as Markdown; also the page's canonical source URL."},"snippet":{"type":"string","readOnly":true,"description":"Short excerpt of the matching content, with the query terms in context. Always returned."},"highlights":{"type":"array","readOnly":true,"description":"The passages of the section that match the query, longer than the snippet. Returned only when `contents` is `highlights`.","items":{"type":"string"}},"token_estimate":{"type":"integer","readOnly":true,"description":"Approximate token count of the full page returned by `markdown_url`, to budget reading it. Results from the same page share it."},"score":{"type":"number","readOnly":true,"description":"Relevance score. Higher is more relevant; results are ordered by descending score."}}},"DocsSearchResponse":{"type":"object","additionalProperties":false,"required":["query","locale","results"],"properties":{"query":{"type":"string","minLength":1,"readOnly":true,"description":"The search query that produced these results."},"locale":{"type":"string","minLength":1,"readOnly":true,"description":"The documentation locale the results were drawn from."},"results":{"type":"array","readOnly":true,"description":"Matching documentation sections, ordered by descending relevance.","items":{"$ref":"#/components/schemas/DocsSearchResult"}}}},"DocsPage":{"type":"object","additionalProperties":false,"required":["slug","locale","url","markdown"],"properties":{"slug":{"type":"string","minLength":1,"readOnly":true,"description":"Slug of the page, echoing the requested slug."},"locale":{"type":"string","minLength":1,"readOnly":true,"description":"Documentation locale the page was drawn from."},"url":{"type":"string","minLength":1,"readOnly":true,"description":"Absolute URL of the page, suitable to cite as the source of an answer."},"markdown":{"type":"string","minLength":1,"readOnly":true,"description":"The page's full content as Markdown."}}},"DmarcUnknownTag":{"type":"object","additionalProperties":false,"description":"A tag present in the DMARC record that this model does not interpret, preserved verbatim.","required":["tag"],"properties":{"tag":{"type":"string","minLength":1,"readOnly":true,"description":"The tag name."},"value":{"type":"string","readOnly":true,"description":"The tag value, verbatim. May be empty."}}},"DmarcPolicy":{"type":"object","additionalProperties":false,"description":"A parsed DMARC policy, with the tags the record sets. Tags left at their RFC 7489 default are omitted.","required":["p","rua","ruf"],"properties":{"p":{"type":"string","minLength":1,"enum":["none","quarantine","reject"],"readOnly":true,"description":"Requested handling for mail that fails DMARC for the domain itself."},"sp":{"type":"string","enum":["none","quarantine","reject"],"readOnly":true,"description":"Policy for subdomains. Omitted when subdomains inherit the domain policy."},"np":{"type":"string","enum":["none","quarantine","reject"],"readOnly":true,"description":"Policy for non-existent subdomains. Omitted when not set."},"pct":{"type":"integer","minimum":0,"maximum":100,"readOnly":true,"description":"Percentage of failing mail the policy is applied to. Omitted when the default (100) applies."},"adkim":{"type":"string","enum":["r","s"],"readOnly":true,"description":"DKIM identifier alignment, `r` (relaxed) or `s` (strict). Omitted when the default (relaxed) applies."},"aspf":{"type":"string","enum":["r","s"],"readOnly":true,"description":"SPF identifier alignment, `r` (relaxed) or `s` (strict). Omitted when the default (relaxed) applies."},"rua":{"type":"array","readOnly":true,"description":"Destinations for aggregate reports (for example `mailto:dmarc@example.com`).","items":{"type":"string","minLength":1}},"ruf":{"type":"array","readOnly":true,"description":"Destinations for failure (forensic) reports.","items":{"type":"string","minLength":1}},"fo":{"type":"array","readOnly":true,"description":"Failure-reporting options. Omitted when the default (`0`) applies.","items":{"type":"string","enum":["0","1","d","s"]}},"ri":{"type":"integer","minimum":1,"readOnly":true,"description":"Requested interval between aggregate reports, in seconds. Omitted when the default (86400) applies."},"rf":{"type":"string","readOnly":true,"description":"Failure report format. Omitted when not set."},"extra":{"type":"array","readOnly":true,"description":"Tags present in the record that this model does not interpret, preserved in source order.","items":{"$ref":"#/components/schemas/DmarcUnknownTag"}}}},"DmarcIssue":{"type":"object","additionalProperties":false,"description":"A validation error or warning about a single part of the DMARC record.","required":["message"],"properties":{"tag":{"type":"string","readOnly":true,"description":"The DMARC tag the issue relates to, when applicable (for example `p`, `rua`, or `pct`)."},"message":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable explanation of the issue."}}},"DmarcValidationResult":{"type":"object","additionalProperties":false,"description":"The result of validating a DMARC record, with the parsed policy and any errors and warnings.","required":["valid","errors","warnings"],"properties":{"valid":{"type":"boolean","readOnly":true,"description":"Whether the record parsed cleanly with no errors. Warnings do not affect this."},"policy":{"allOf":[{"$ref":"#/components/schemas/DmarcPolicy"}],"readOnly":true,"description":"The parsed policy. Absent when the record has errors that make it unusable."},"errors":{"type":"array","readOnly":true,"description":"Problems that make the record invalid. Empty when the record is valid.","items":{"$ref":"#/components/schemas/DmarcIssue"}},"warnings":{"type":"array","readOnly":true,"description":"Non-fatal issues worth addressing, such as a missing aggregate-report address.","items":{"$ref":"#/components/schemas/DmarcIssue"}}}},"BimiUnknownTag":{"type":"object","additionalProperties":false,"description":"A tag present in the BIMI record that this model does not interpret, preserved verbatim.","required":["tag"],"properties":{"tag":{"type":"string","minLength":1,"readOnly":true,"description":"The tag name."},"value":{"type":"string","readOnly":true,"description":"The tag value, verbatim. May be empty."}}},"BimiRecord":{"type":"object","additionalProperties":false,"description":"A parsed BIMI record.","properties":{"l":{"type":"string","readOnly":true,"description":"The `l=` tag specifies the HTTPS URI of the brand's SVG logo. An empty string is a valid `no logo` declaration."},"a":{"type":"string","readOnly":true,"description":"The `a=` tag specifies the HTTPS URI of the Verified Mark Certificate. Empty when no certificate is published; Gmail and Apple Mail require it to display the mark."},"extra":{"type":"array","readOnly":true,"description":"Tags present in the record that this model does not interpret, preserved in source order.","items":{"$ref":"#/components/schemas/BimiUnknownTag"}}}},"BimiIssue":{"type":"object","additionalProperties":false,"description":"A validation error or warning about a single part of the BIMI record.","required":["message"],"properties":{"tag":{"type":"string","readOnly":true,"description":"The BIMI tag the issue relates to, when applicable (for example `l` or `a`)."},"message":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable explanation of the issue."}}},"BimiValidationResult":{"type":"object","additionalProperties":false,"description":"The result of validating a BIMI record, with the parsed record and any errors and warnings.","required":["valid","errors","warnings"],"properties":{"valid":{"type":"boolean","readOnly":true,"description":"Whether the record parsed cleanly with no errors. Warnings do not affect this."},"record":{"allOf":[{"$ref":"#/components/schemas/BimiRecord"}],"readOnly":true,"description":"The parsed record. Absent when the record has errors that make it unusable."},"errors":{"type":"array","readOnly":true,"description":"Problems that make the record invalid. Empty when the record is valid.","items":{"$ref":"#/components/schemas/BimiIssue"}},"warnings":{"type":"array","readOnly":true,"description":"Non-fatal issues worth addressing.","items":{"$ref":"#/components/schemas/BimiIssue"}}}},"DnsSplitResult":{"type":"object","additionalProperties":false,"description":"A long DNS TXT value split into DNS-safe (≤255-octet) chunks.","required":["value","chunks","byte_length","max_chunk_bytes","exceeds_limit","has_escapes","output"],"properties":{"value":{"type":"string","minLength":1,"readOnly":true,"description":"The normalized value (any input quoting or zone-file wrapper stripped and rejoined)."},"chunks":{"type":"array","readOnly":true,"description":"The value split into parts, each at most `max_chunk_bytes` octets.","items":{"type":"string"}},"byte_length":{"type":"integer","readOnly":true,"description":"Total octet length of the value."},"max_chunk_bytes":{"type":"integer","readOnly":true,"description":"The effective per-chunk octet limit applied (the requested size, clamped to 1–255)."},"exceeds_limit":{"type":"boolean","readOnly":true,"description":"Whether the value is over the 255-octet single-string limit, so splitting is actually required."},"has_escapes":{"type":"boolean","readOnly":true,"description":"Whether the value contains characters that had to be escaped for quoting."},"output":{"type":"string","minLength":1,"readOnly":true,"description":"The value rendered as a ready-to-publish quoted, space-joined TXT string."}}},"AnalyzeEmailHeadersRequest":{"type":"object","additionalProperties":false,"description":"A raw email to analyze.","required":["raw"],"properties":{"raw":{"type":"string","minLength":1,"description":"The raw email headers and optional body. Content after the first blank line is ignored.","example":"Received: from mail.example.com (mail.example.com [203.0.113.10])\n  by mx.example.net with ESMTPS id abc123\n  for <recipient@example.net>; Tue, 4 Aug 2026 09:15:22 +0000\nAuthentication-Results: mx.example.net; spf=pass smtp.mailfrom=example.com; dkim=pass header.d=example.com; dmarc=pass header.from=example.com\nFrom: Sender <sender@example.com>\nTo: recipient@example.net\nSubject: Your receipt\n"}}},"EmailHeaderSummary":{"type":"object","additionalProperties":false,"description":"The headline header fields most people scan a message for. Every field is absent when the message does not have it.","properties":{"from":{"type":"string","readOnly":true,"description":"The `From` header."},"to":{"type":"string","readOnly":true,"description":"The `To` header."},"cc":{"type":"string","readOnly":true,"description":"The `Cc` header."},"subject":{"type":"string","readOnly":true,"description":"The `Subject` header, decoded from any RFC 2047 encoded words."},"date":{"type":"string","readOnly":true,"description":"The `Date` header, as written in the message."},"reply_to":{"type":"string","readOnly":true,"description":"The `Reply-To` header."},"return_path":{"type":"string","readOnly":true,"description":"The `Return-Path` header (the bounce address)."},"message_id":{"type":"string","readOnly":true,"description":"The `Message-ID` header."},"content_type":{"type":"string","readOnly":true,"description":"The `Content-Type` header."}}},"EmailAuthResult":{"type":"object","additionalProperties":false,"description":"One authentication verdict (SPF, DKIM, or DMARC) parsed from an `Authentication-Results` or `Received-SPF` header.","required":["method","result"],"properties":{"method":{"type":"string","minLength":1,"readOnly":true,"description":"The authentication method, lower-cased: `spf`, `dkim`, `dmarc`, `arc`, `iprev`, `dkim-adsp`, or `bimi`. Entries naming any other method are skipped."},"result":{"type":"string","minLength":1,"readOnly":true,"description":"The verdict token as the reporting server wrote it, for example `pass`, `fail`, `softfail`, or `none`. Any token the header holds is passed through, so treat this as an open set."},"detail":{"type":"string","readOnly":true,"description":"The remainder of the entry (properties or comments) kept for context, when present."}}},"EmailAuthSummary":{"type":"object","additionalProperties":false,"description":"The SPF, DKIM, and DMARC verdicts found in the message's authentication headers.","required":["all"],"properties":{"spf":{"allOf":[{"$ref":"#/components/schemas/EmailAuthResult"}],"readOnly":true,"description":"The first SPF verdict found. Absent when the message has none."},"dkim":{"allOf":[{"$ref":"#/components/schemas/EmailAuthResult"}],"readOnly":true,"description":"The first DKIM verdict found. Absent when the message has none."},"dmarc":{"allOf":[{"$ref":"#/components/schemas/EmailAuthResult"}],"readOnly":true,"description":"The first DMARC verdict found. Absent when the message has none."},"all":{"type":"array","readOnly":true,"description":"Every authentication verdict parsed, in the order it appeared.","items":{"$ref":"#/components/schemas/EmailAuthResult"}}}},"EmailReceivedHop":{"type":"object","additionalProperties":false,"description":"One `Received` relay hop, parsed and placed in delivery order (oldest first).","required":["index","raw"],"properties":{"index":{"type":"integer","readOnly":true,"description":"1-based position in delivery order (1 = the first server to handle the message)."},"from":{"type":"string","readOnly":true,"description":"The host the message was received from."},"by":{"type":"string","readOnly":true,"description":"The host that received the message."},"with":{"type":"string","readOnly":true,"description":"The protocol the message arrived over, for example `ESMTPS`."},"recipient":{"type":"string","readOnly":true,"description":"The envelope recipient, when the relay disclosed it."},"date":{"type":"string","readOnly":true,"description":"The handoff time in ISO 8601, or absent when the date could not be read."},"delay_seconds":{"type":"integer","readOnly":true,"description":"Seconds spent between the previous hop and this one. Absent for the first hop or when either timestamp is missing."},"raw":{"type":"string","minLength":1,"readOnly":true,"description":"The full unfolded header value, kept so the original line can be shown."}}},"EmailHeaderAnalysis":{"type":"object","additionalProperties":false,"description":"What we found in a raw email's headers. The headline fields, the authentication results, and the path the message took to get here.\n","required":["ok","summary","auth","hops"],"properties":{"ok":{"type":"boolean","readOnly":true,"description":"Whether at least one recognizable header was found. False when the input did not look like email headers."},"summary":{"allOf":[{"$ref":"#/components/schemas/EmailHeaderSummary"}],"readOnly":true,"description":"The headline header fields."},"auth":{"allOf":[{"$ref":"#/components/schemas/EmailAuthSummary"}],"readOnly":true,"description":"The SPF, DKIM, and DMARC verdicts."},"hops":{"type":"array","readOnly":true,"description":"The relay hops in delivery order (oldest first). Empty when the message has no recorded hops.","items":{"$ref":"#/components/schemas/EmailReceivedHop"}},"total_delay_seconds":{"type":"integer","readOnly":true,"description":"Total seconds from the first to the last hop. Absent when both ends do not have a timestamp."}}},"AnalyzeDmarcReportRequest":{"type":"object","additionalProperties":false,"description":"A DMARC aggregate report to analyze.","required":["report_xml"],"properties":{"report_xml":{"type":"string","minLength":1,"description":"The DMARC aggregate (RUA) report as XML. This is the `.xml` inside the report archive mailbox providers email to the address in your DMARC `rua=` tag.","example":"<feedback>\n  <report_metadata>\n    <org_name>example.net</org_name>\n    <report_id>14297523</report_id>\n  </report_metadata>\n  <policy_published>\n    <domain>example.com</domain>\n    <p>none</p>\n  </policy_published>\n  <record>\n    <row>\n      <source_ip>203.0.113.10</source_ip>\n      <count>42</count>\n      <policy_evaluated><disposition>none</disposition><dkim>pass</dkim><spf>pass</spf></policy_evaluated>\n    </row>\n  </record>\n</feedback>\n"}}},"DmarcDispositionCount":{"type":"object","additionalProperties":false,"description":"How many messages a receiver applied a given DMARC disposition to.","required":["disposition","count"],"properties":{"disposition":{"type":"string","minLength":1,"readOnly":true,"description":"The disposition the receiver applied. Possible values are `none`, `quarantine`, and `reject`."},"count":{"type":"integer","readOnly":true,"description":"The number of messages that received this disposition."}}},"DmarcSourceSummary":{"type":"object","additionalProperties":false,"description":"A per-source-IP breakdown of the mail in a DMARC aggregate report.","required":["source_ip","count","pass_count","fail_count","pass_rate","spf","dkim","spf_domains","dispositions"],"properties":{"source_ip":{"type":"string","minLength":1,"readOnly":true,"description":"The IP address that sent the mail."},"count":{"type":"integer","readOnly":true,"description":"Total messages from this IP across every row of the report."},"pass_count":{"type":"integer","readOnly":true,"description":"Messages from this IP that passed DMARC."},"fail_count":{"type":"integer","readOnly":true,"description":"Messages from this IP that failed DMARC."},"pass_rate":{"type":"number","readOnly":true,"description":"The fraction of this IP's messages that passed DMARC, from 0 to 1."},"spf":{"type":"string","minLength":1,"readOnly":true,"enum":["pass","fail","mixed"],"description":"The aligned SPF result across this IP's rows. `mixed` when some rows passed and others failed."},"dkim":{"type":"string","minLength":1,"readOnly":true,"enum":["pass","fail","mixed"],"description":"The aligned DKIM result across this IP's rows. `mixed` when some rows passed and others failed."},"spf_domains":{"type":"array","readOnly":true,"description":"The envelope domains SPF checked for this IP, distinct and in first-seen order. For a legitimate sender this is your domain; for a forwarder it is theirs, which is why alignment fails.","items":{"type":"string"}},"dispositions":{"type":"array","readOnly":true,"description":"The dispositions the receiver applied to this IP's mail, in first-seen order.","items":{"type":"string"}}}},"DmarcFinding":{"type":"object","additionalProperties":false,"description":"One plain-language observation drawn from a DMARC aggregate report.","required":["level","message"],"properties":{"level":{"type":"string","minLength":1,"readOnly":true,"enum":["good","warning","problem"],"description":"How serious the finding is.\n\n- `good`: nothing to act on.\n- `warning`: worth addressing.\n- `problem`: needs action.\n"},"message":{"type":"string","minLength":1,"readOnly":true,"description":"The finding in plain language."}}},"DmarcReportAnalysis":{"type":"object","additionalProperties":false,"description":"Analysis of a DMARC aggregate (RUA) report, including totals, a per-source breakdown, and plain-language findings.","required":["total_messages","pass_messages","fail_messages","pass_rate","spf_pass_messages","dkim_pass_messages","dispositions","sources","findings"],"properties":{"total_messages":{"type":"integer","readOnly":true,"description":"Total messages covered by the report."},"pass_messages":{"type":"integer","readOnly":true,"description":"Messages that passed DMARC (aligned SPF or aligned DKIM)."},"fail_messages":{"type":"integer","readOnly":true,"description":"Messages that failed DMARC."},"pass_rate":{"type":"number","readOnly":true,"description":"The fraction of messages that passed DMARC, from 0 to 1."},"spf_pass_messages":{"type":"integer","readOnly":true,"description":"Messages whose aligned SPF passed, regardless of DKIM."},"dkim_pass_messages":{"type":"integer","readOnly":true,"description":"Messages whose aligned DKIM passed, regardless of SPF."},"dispositions":{"type":"array","readOnly":true,"description":"The dispositions receivers applied, with a count for each.","items":{"$ref":"#/components/schemas/DmarcDispositionCount"}},"sources":{"type":"array","readOnly":true,"description":"One entry per source IP, sorted by message volume, highest first.","items":{"$ref":"#/components/schemas/DmarcSourceSummary"}},"findings":{"type":"array","readOnly":true,"description":"Plain-language observations about the report, each tagged with a severity.","items":{"$ref":"#/components/schemas/DmarcFinding"}}}},"EmailAuthArea":{"type":"string","minLength":1,"enum":["dmarc","spf","dkim","bimi","mx"],"readOnly":true,"description":"Which part of a domain's email setup this record covers.\n\n- `dmarc`: The domain's DMARC policy record.\n- `spf`: The domain's SPF record.\n- `dkim`: A DKIM selector's public-key record.\n- `bimi`: The domain's BIMI record, pointing to the logo and any certificate location.\n- `mx`: The domain's mail-exchanger records.\n"},"EmailAuditRecord":{"type":"object","additionalProperties":false,"description":"The resolved state of one email-authentication record.","required":["area","found"],"properties":{"area":{"$ref":"#/components/schemas/EmailAuthArea"},"found":{"type":"boolean","readOnly":true,"description":"Whether the record was present in DNS."},"value":{"type":["string","null"],"readOnly":true,"description":"The raw record as published, or null when absent (or, for MX, a comma-separated host list)."}}},"AuditFinding":{"type":"object","additionalProperties":false,"description":"A single finding about one area of a domain's email authentication.","required":["severity","area","message"],"properties":{"severity":{"type":"string","minLength":1,"enum":["ok","warning","problem"],"readOnly":true,"description":"How serious the finding is.\n\n- `ok`: a passing check.\n- `warning`: worth addressing.\n- `problem`: delivery or spoofing protection is affected.\n"},"area":{"$ref":"#/components/schemas/EmailAuthArea"},"message":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable description of the finding."},"fix":{"type":"string","readOnly":true,"description":"Suggested remediation, if available."}}},"EmailAuditResult":{"type":"object","additionalProperties":false,"description":"The result of auditing a domain's email authentication.","required":["domain","valid","records","findings","recommendations"],"properties":{"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The domain that was audited."},"valid":{"type":"boolean","readOnly":true,"description":"Whether the audit found no problem-severity issues. Warnings do not affect this."},"records":{"type":"array","readOnly":true,"description":"The resolved state of each email-authentication record (DMARC, SPF, DKIM, BIMI, MX).","items":{"$ref":"#/components/schemas/EmailAuditRecord"}},"findings":{"type":"array","readOnly":true,"description":"All findings across every record, ordered most-severe first.","items":{"$ref":"#/components/schemas/AuditFinding"}},"recommendations":{"type":"array","readOnly":true,"description":"Prioritized, human-readable fixes, most important first.","items":{"type":"string","minLength":1}}}},"AdminAssistantKnowledgeStatus":{"type":"string","minLength":1,"description":"Publication state of a curated Ask Bird entry.\n\n- `draft` is the default, editable state and is excluded from customer retrieval.\n- `waiting_approval` is awaiting staff review and is excluded from customer retrieval.\n- `published` is available to customer retrieval after replication.\n","enum":["draft","waiting_approval","published"]},"AssistantKnowledgeID":{"type":"string","minLength":1,"pattern":"^ask_[0-9a-hjkmnp-tv-z]{26}$","example":"ask_01krdgeqcxet5s7t44vh8rt9mg"},"AdminStaffAssistantKnowledge":{"x-sensitive":true,"type":"object","description":"Curated staff knowledge and its latest editor.","additionalProperties":false,"required":["id","key","title","content","tags","status","revision","created_at","updated_at","updated_by"],"properties":{"id":{"$ref":"#/components/schemas/AssistantKnowledgeID"},"key":{"type":"string","description":"Stable entry key used across imports. Cannot be changed on an existing entry.","minLength":1,"maxLength":100,"pattern":"^[a-z0-9][a-z0-9_-]{0,99}$"},"title":{"type":"string","description":"Short title describing the knowledge.","minLength":1,"maxLength":200},"content":{"type":"string","description":"Markdown knowledge content. The service rejects content over 12000 UTF-8 bytes with 422; multibyte characters can reach this byte limit sooner than maxLength.","minLength":1,"maxLength":12000},"tags":{"type":"array","description":"Tags used to find related entries.","maxItems":20,"items":{"type":"string","minLength":1,"maxLength":50}},"status":{"$ref":"#/components/schemas/AdminAssistantKnowledgeStatus"},"revision":{"type":"integer","format":"int64","minimum":1,"description":"Revision required to replace or remove this entry."},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"Time the entry was created."},"updated_at":{"type":"string","format":"date-time","minLength":1,"description":"Time the entry was last changed."},"updated_by":{"$ref":"#/components/schemas/UserID"}}},"AdminStaffAssistantKnowledgeList":{"x-sensitive":true,"description":"A page of global curated knowledge.","allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Knowledge entries matching the requested filters.","items":{"$ref":"#/components/schemas/AdminStaffAssistantKnowledge"}}}}],"unevaluatedProperties":false},"AdminStaffAssistantKnowledgeInput":{"x-sensitive":true,"type":"object","description":"Curated knowledge to save. An omitted status starts as draft.","additionalProperties":false,"required":["key","title","content"],"properties":{"key":{"type":"string","description":"Stable entry key used across imports. Cannot be changed on an existing entry.","minLength":1,"maxLength":100,"pattern":"^[a-z0-9][a-z0-9_-]{0,99}$"},"title":{"type":"string","description":"Short title describing the knowledge.","minLength":1,"maxLength":200},"content":{"type":"string","description":"Markdown knowledge content. The service rejects content over 12000 UTF-8 bytes with 422; multibyte characters can reach this byte limit sooner than maxLength.","minLength":1,"maxLength":12000},"tags":{"type":"array","description":"Tags used to find related entries.","maxItems":20,"items":{"type":"string","minLength":1,"maxLength":50}},"status":{"$ref":"#/components/schemas/AdminAssistantKnowledgeStatus"}}},"AdminStaffAssistantKnowledgeUpdate":{"x-sensitive":true,"type":"object","description":"Full replacement guarded by the last observed revision.","additionalProperties":false,"required":["entry","expected_revision"],"properties":{"entry":{"$ref":"#/components/schemas/AdminStaffAssistantKnowledgeInput"},"expected_revision":{"type":"integer","format":"int64","minimum":1,"description":"Revision displayed when editing began."}}},"AdminStaffAssistantKnowledgeBase":{"type":"string","minLength":5,"maxLength":5,"enum":["admin"],"description":"Explicit destination for this import. Customer knowledge is rejected."},"AdminStaffAssistantKnowledgeImport":{"x-sensitive":true,"type":"object","description":"Bounded JSON import. Invalid or duplicate keys reject the complete batch.","additionalProperties":false,"required":["knowledge_base","schema_version","entries"],"properties":{"knowledge_base":{"$ref":"#/components/schemas/AdminStaffAssistantKnowledgeBase"},"schema_version":{"type":"integer","minimum":1,"maximum":1,"description":"Import schema version, currently 1."},"entries":{"type":"array","minItems":1,"maxItems":50,"description":"Entries to compare against saved knowledge.","items":{"$ref":"#/components/schemas/AdminStaffAssistantKnowledgeInput"}}}},"AdminAssistantKnowledgeImportChange":{"type":"string","minLength":1,"description":"Difference between an imported entry and the saved entry.","enum":["new","changed","unchanged"]},"AdminStaffAssistantKnowledgeImportPreviewEntry":{"x-sensitive":true,"type":"object","description":"Normalized import entry and its observed revision.","additionalProperties":false,"required":["entry","expected_revision","change"],"properties":{"entry":{"$ref":"#/components/schemas/AdminStaffAssistantKnowledgeInput"},"expected_revision":{"type":"integer","format":"int64","minimum":0,"description":"Observed revision, or zero for a new key. Apply rejects intervening changes."},"change":{"$ref":"#/components/schemas/AdminAssistantKnowledgeImportChange"}}},"AdminStaffAssistantKnowledgeImportPreview":{"x-sensitive":true,"type":"object","description":"Validated import preview. Submit these entries to apply the complete batch atomically.","additionalProperties":false,"required":["knowledge_base","schema_version","entries"],"properties":{"knowledge_base":{"$ref":"#/components/schemas/AdminStaffAssistantKnowledgeBase"},"schema_version":{"type":"integer","minimum":1,"maximum":1,"description":"Import schema version, currently 1."},"entries":{"type":"array","minItems":1,"maxItems":50,"description":"Proposed entries and concurrency guards.","items":{"$ref":"#/components/schemas/AdminStaffAssistantKnowledgeImportPreviewEntry"}}}},"AdminStaffAssistantKnowledgeImportResult":{"x-sensitive":true,"type":"object","description":"Entries returned by a successful atomic import.","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Saved entries in import order.","items":{"$ref":"#/components/schemas/AdminStaffAssistantKnowledge"}}}},"AdminAssistantKnowledge":{"x-sensitive":true,"type":"object","description":"Curated global Ask Bird knowledge and its latest editor.","additionalProperties":false,"required":["id","key","title","content","tags","status","revision","created_at","updated_at","updated_by"],"properties":{"id":{"$ref":"#/components/schemas/AssistantKnowledgeID"},"key":{"type":"string","description":"Stable entry key used across imports. Cannot be changed on an existing entry.","minLength":1,"maxLength":100,"pattern":"^[a-z0-9][a-z0-9_-]{0,99}$"},"title":{"type":"string","description":"Short title describing the knowledge.","minLength":1,"maxLength":200},"content":{"type":"string","description":"Markdown knowledge content, limited to 12000 UTF-8 bytes.","minLength":1,"maxLength":12000},"tags":{"type":"array","description":"Tags used to find related entries.","maxItems":20,"items":{"type":"string","minLength":1,"maxLength":50}},"status":{"$ref":"#/components/schemas/AdminAssistantKnowledgeStatus"},"revision":{"type":"integer","format":"int64","minimum":1,"description":"Revision required to replace or remove this entry."},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"Time the entry was created."},"updated_at":{"type":"string","format":"date-time","minLength":1,"description":"Time the entry was last changed."},"updated_by":{"$ref":"#/components/schemas/UserID"}}},"AdminAssistantKnowledgeList":{"x-sensitive":true,"description":"A page of global curated knowledge.","allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Knowledge entries matching the requested filters.","items":{"$ref":"#/components/schemas/AdminAssistantKnowledge"}}}}],"unevaluatedProperties":false},"AdminAssistantKnowledgeInput":{"x-sensitive":true,"type":"object","description":"Curated knowledge to save. An omitted status starts as draft.","additionalProperties":false,"required":["key","title","content"],"properties":{"key":{"type":"string","description":"Stable entry key used across imports. Cannot be changed on an existing entry.","minLength":1,"maxLength":100,"pattern":"^[a-z0-9][a-z0-9_-]{0,99}$"},"title":{"type":"string","description":"Short title describing the knowledge.","minLength":1,"maxLength":200},"content":{"type":"string","description":"Markdown knowledge content, limited to 12000 UTF-8 bytes.","minLength":1,"maxLength":12000},"tags":{"type":"array","description":"Tags used to find related entries.","maxItems":20,"items":{"type":"string","minLength":1,"maxLength":50}},"status":{"$ref":"#/components/schemas/AdminAssistantKnowledgeStatus"}}},"AdminAssistantKnowledgeUpdate":{"x-sensitive":true,"type":"object","description":"Full replacement guarded by the last observed revision.","additionalProperties":false,"required":["entry","expected_revision"],"properties":{"entry":{"$ref":"#/components/schemas/AdminAssistantKnowledgeInput"},"expected_revision":{"type":"integer","format":"int64","minimum":1,"description":"Revision displayed when editing began."}}},"AdminAssistantKnowledgeImport":{"x-sensitive":true,"type":"object","description":"Bounded JSON import. Invalid or duplicate keys reject the complete batch.","additionalProperties":false,"required":["schema_version","entries"],"properties":{"schema_version":{"type":"integer","minimum":1,"maximum":1,"description":"Import schema version, currently 1."},"entries":{"type":"array","minItems":1,"maxItems":50,"description":"Entries to compare against saved knowledge.","items":{"$ref":"#/components/schemas/AdminAssistantKnowledgeInput"}}}},"AdminAssistantKnowledgeImportPreviewEntry":{"x-sensitive":true,"type":"object","description":"Normalized import entry and its observed revision.","additionalProperties":false,"required":["entry","expected_revision","change"],"properties":{"entry":{"$ref":"#/components/schemas/AdminAssistantKnowledgeInput"},"expected_revision":{"type":"integer","format":"int64","minimum":0,"description":"Observed revision, or zero for a new key. Apply rejects intervening changes."},"change":{"$ref":"#/components/schemas/AdminAssistantKnowledgeImportChange"}}},"AdminAssistantKnowledgeImportPreview":{"x-sensitive":true,"type":"object","description":"Validated import preview. Submit these entries to apply the complete batch atomically.","additionalProperties":false,"required":["schema_version","entries"],"properties":{"schema_version":{"type":"integer","minimum":1,"maximum":1,"description":"Import schema version, currently 1."},"entries":{"type":"array","minItems":1,"maxItems":50,"description":"Proposed entries and concurrency guards.","items":{"$ref":"#/components/schemas/AdminAssistantKnowledgeImportPreviewEntry"}}}},"AdminAssistantKnowledgeImportResult":{"x-sensitive":true,"type":"object","description":"Entries returned by a successful atomic import.","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Saved entries in import order.","items":{"$ref":"#/components/schemas/AdminAssistantKnowledge"}}}},"AssistantConversationID":{"type":"string","minLength":1,"pattern":"^asc_[0-9a-hjkmnp-tv-z]{26}$","example":"asc_01krdgeqcxet5s7t44vh8rt9mg"},"AssistantConversation":{"type":"object","description":"A saved conversation private to its owner and workspace.","x-sensitive":true,"additionalProperties":false,"required":["id","title","revision","archived","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/AssistantConversationID"},"title":{"type":"string","description":"Title derived from the first customer question.","minLength":1,"maxLength":300},"revision":{"type":"integer","format":"int64","description":"Current revision for archive, restore and delete operations.","minimum":0},"archived":{"type":"boolean","description":"Whether the conversation is archived."},"created_at":{"type":"string","format":"date-time","description":"Creation time.","minLength":1},"updated_at":{"type":"string","format":"date-time","description":"Last content or explicit history update.","minLength":1}}},"AdminStaffAssistantReview":{"type":"object","description":"Staff conversation summary for admins and Security.","x-sensitive":true,"additionalProperties":false,"required":["conversation","cursor","user_id"],"properties":{"cursor":{"type":"string","minLength":1,"description":"Opaque pagination boundary for updated_at ordering, reusable across regions."},"conversation":{"$ref":"#/components/schemas/AssistantConversation"},"user_id":{"$ref":"#/components/schemas/UserID"}}},"AdminStaffAssistantReviewList":{"description":"Audited staff content for admins and Security.","x-sensitive":true,"allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Conversation summaries, newest first.","items":{"$ref":"#/components/schemas/AdminStaffAssistantReview"}}}}],"unevaluatedProperties":false},"AssistantRunID":{"type":"string","minLength":1,"pattern":"^asr_[0-9a-hjkmnp-tv-z]{26}$","example":"asr_01krdgeqcxet5s7t44vh8rt9mg"},"AssistantRunTerminalReason":{"type":"string","description":"Sanitized reason a request ended. Omitted when no terminal reason was recorded.\n* `stopped`: The request was stopped.\n* `expired`: The request exceeded its execution deadline.\n* `request_limit`: The organization had no requests available.\n* `request_budget`: The request reached its operation limit or execution deadline before another step could start.\n* `context_budget`: The request exceeded its context budget.\n* `invalid_model_output`: The model response could not be validated.\n* `invalid_tool`: The prepared operation used an unsupported delivery mode.\n* `tool_input_limit`: The proposed operation exceeded its input limit.\n* `invalid_review`: The proposed action could not be prepared for review.\n* `deleted`: The conversation was deleted.\n","enum":["stopped","expired","request_limit","request_budget","context_budget","invalid_model_output","invalid_tool","tool_input_limit","invalid_review","deleted"]},"AssistantRunState":{"type":"string","description":"Current state of an Ask Bird request. admitting means allowance reservation is pending; retry the original start with the same request key and input. ready accepts the next advance. advancing has a bounded step in progress; read the run before retrying. waiting_for_tool requires the pending browser result or reviewed action claim. interrupted pauses after a provider or step interruption; explicitly retry advance with the current revision. completed has a committed answer. stopped prevents further work but already claimed actions may still need reconciliation. failed is terminal; inspect terminal_reason before starting a new request. rejected was not admitted, for example request_limit; a retry of the original key returns the same rejection.","enum":["admitting","ready","advancing","waiting_for_tool","interrupted","completed","stopped","failed","rejected"],"minLength":1},"AdminAssistantRetrievalTrigger":{"type":"string","minLength":1,"description":"Retrieval trigger.","enum":["first_message","tool"]},"AdminAssistantRetrievalSource":{"type":"string","minLength":1,"description":"Retrieval source.","enum":["public","curated"]},"AdminAssistantRetrievalStatus":{"type":"string","minLength":1,"description":"Retrieval status.","enum":["ok","unavailable","timeout"]},"AdminAssistantRetrievalMode":{"type":"string","minLength":1,"description":"Search strategy. `hybrid` combines vector similarity and keyword matching; `keyword` uses text matching without a query embedding.","enum":["hybrid","keyword"]},"AdminAssistantRetrievalReference":{"type":"object","description":"Identity and version of evidence supplied by retrieval.","additionalProperties":false,"required":["id","revision"],"properties":{"id":{"type":"string","minLength":1},"revision":{"type":"string","minLength":1}}},"AdminAssistantRetrieval":{"type":"object","description":"A bounded search attempt and the evidence supplied to the agent.","additionalProperties":false,"required":["step","trigger","source","status","duration_ms","completed_at","results"],"properties":{"step":{"type":"integer"},"trigger":{"$ref":"#/components/schemas/AdminAssistantRetrievalTrigger"},"source":{"$ref":"#/components/schemas/AdminAssistantRetrievalSource"},"status":{"$ref":"#/components/schemas/AdminAssistantRetrievalStatus"},"mode":{"$ref":"#/components/schemas/AdminAssistantRetrievalMode"},"duration_ms":{"type":"integer","format":"int64"},"completed_at":{"type":"string","minLength":1,"format":"date-time"},"results":{"type":"array","maxItems":8,"items":{"$ref":"#/components/schemas/AdminAssistantRetrievalReference"}}}},"AdminAssistantModelOutcome":{"type":"string","minLength":1,"enum":["answer","tool_call","request_error","invalid_choices","invalid_completion","invalid_tool_call","invalid_answer","unavailable_source","canceled","timeout"],"description":"Bounded result of this provider attempt. Accepted output does not establish that a tool ran or an answer reached the customer.\n* `answer`: The structured answer and its source references passed validation.\n* `tool_call`: The structured response selected an available tool; its arguments and execution are validated separately.\n* `request_error`: The provider request failed, including an HTTP rejection or transport error.\n* `invalid_choices`: The provider returned a choice count other than one.\n* `invalid_completion`: The response was incomplete, refused, exceeded the size limit, or included native tool calls instead of the required structured response.\n* `invalid_tool_call`: The structured response could not be decoded, selected an unavailable operation, or lacked an arguments object.\n* `invalid_answer`: The answer fields or links failed validation.\n* `unavailable_source`: The answer cited a source absent from this request's retrieved knowledge.\n* `canceled`: The provider request's execution context was canceled.\n* `timeout`: The provider request exceeded its execution deadline.\n"},"AdminAssistantProviderErrorCode":{"type":"string","minLength":1,"enum":["tool_use_failed","json_validate_failed","rate_limit_exceeded","invalid_api_key","model_not_found","other"],"description":"Allowlisted provider error code; other replaces an unrecognized code."},"AdminAssistantServiceTier":{"type":"string","minLength":1,"description":"Inference service tier.","enum":["standard","priority"]},"AdminAssistantModelTokens":{"type":"object","description":"Provider-reported token usage. Billable output includes thinking tokens in Gemini total usage.","additionalProperties":false,"required":["input","output","total","billable_output"],"properties":{"input":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991},"output":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991,"description":"Completion tokens reported separately by the provider; may omit thinking tokens."},"total":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991},"billable_output":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991,"description":"Total tokens minus input tokens, including thinking."},"cached_input":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991,"description":"Cached portion of input. Absent when not reported."}}},"amount":{"type":"string","minLength":1,"description":"Decimal amount as a string, in major currency units.","example":"0.00995"},"CurrencyCode":{"type":"string","minLength":3,"maxLength":3,"pattern":"^[A-Z]{3}$","description":"ISO 4217 three-letter currency code.","example":"EUR"},"AdminAssistantModelCostTierBasis":{"type":"string","minLength":1,"enum":["reported","requested"],"description":"Whether the calculation uses a provider-reported tier or the requested tier when unreported."},"AdminAssistantModelRates":{"type":"object","description":"Published rates per million tokens, in the estimate's currency. Decimal strings preserve the supplied precision.","additionalProperties":false,"required":["input_per_million","cached_input_per_million","output_per_million"],"properties":{"input_per_million":{"type":"string","minLength":1},"cached_input_per_million":{"type":"string","minLength":1},"output_per_million":{"type":"string","minLength":1}}},"AdminAssistantModelCost":{"type":"object","description":"Estimated inference cost for this attempt, not an invoiced charge. Excludes embeddings, cache storage, taxes, and negotiated adjustments.","additionalProperties":false,"required":["amount","currency_code","tier","tier_basis","cached_input_assumed","rates","source_url","observed_at","valid_from"],"properties":{"amount":{"$ref":"#/components/schemas/amount"},"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"tier":{"$ref":"#/components/schemas/AdminAssistantServiceTier"},"tier_basis":{"$ref":"#/components/schemas/AdminAssistantModelCostTierBasis"},"cached_input_assumed":{"type":"boolean","description":"True when cache usage was unreported and all input was priced at the uncached rate."},"rates":{"$ref":"#/components/schemas/AdminAssistantModelRates"},"source_url":{"type":"string","minLength":1,"format":"uri","description":"Published supplier pricing source used for these rates."},"observed_at":{"type":"string","minLength":1,"format":"date-time","description":"When the configured rates were checked against the source."},"valid_from":{"type":"string","minLength":1,"format":"date-time"},"valid_until":{"type":"string","minLength":1,"format":"date-time","description":"Exclusive end of the published rate period. Absent when no end date is published."}}},"AdminAssistantModelAccounting":{"type":"object","description":"Model usage and inference experiment metadata. Missing usage or cost is unknown, never zero.","additionalProperties":false,"required":["requested_tier"],"properties":{"experiment":{"type":"string","minLength":1,"description":"Cohort assignment version, stable for all calls and retries of a request."},"requested_tier":{"$ref":"#/components/schemas/AdminAssistantServiceTier"},"served_tier":{"$ref":"#/components/schemas/AdminAssistantServiceTier","description":"Provider-reported tier. Absent when the provider did not report it."},"tokens":{"$ref":"#/components/schemas/AdminAssistantModelTokens"},"estimated_cost":{"$ref":"#/components/schemas/AdminAssistantModelCost"}}},"AdminAssistantModelCall":{"type":"object","description":"One observed provider attempt using the model configured at execution time.","additionalProperties":false,"required":["step","attempt","completed_at","provider","model","outcome","duration_ms"],"properties":{"step":{"type":"integer","minimum":1,"description":"Counted execution step that made this model call."},"attempt":{"type":"integer","minimum":1,"maximum":2,"description":"Attempt within this step; 2 identifies internal structured-response recovery."},"completed_at":{"type":"string","format":"date-time","minLength":1,"description":"When this attempt finished."},"provider":{"type":"string","minLength":1,"maxLength":253,"description":"Configured provider hostname, excluding credentials, port, path, and query."},"model":{"type":"string","minLength":1,"maxLength":200,"description":"Configured model identifier for this attempt."},"outcome":{"$ref":"#/components/schemas/AdminAssistantModelOutcome"},"duration_ms":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991,"description":"Measured duration of this attempt in milliseconds."},"http_status":{"type":"integer","minimum":100,"maximum":599,"description":"Provider HTTP status for a rejected request, when available."},"provider_code":{"$ref":"#/components/schemas/AdminAssistantProviderErrorCode"},"accounting":{"$ref":"#/components/schemas/AdminAssistantModelAccounting"}}},"AdminAssistantDiagnosticSource":{"type":"string","minLength":1,"enum":["runtime","tool"],"description":"Execution boundary that recorded the failure."},"AdminAssistantDiagnosticCode":{"type":"string","minLength":1,"enum":["expired","request_limit","request_budget","context_budget","invalid_model_output","invalid_tool","tool_input_limit","invalid_review","interrupted","stale_target","invalid_arguments","tool_error","tool_timeout","tool_canceled","forbidden","error","browser_arguments_schema","browser_observation_invalid","browser_arguments_invalid","browser_observation_id_mismatch","browser_value_too_long","browser_target_too_long","browser_fingerprint_too_long","browser_credentials_forbidden","browser_page_manual","browser_target_must_be_empty","browser_value_must_be_empty","browser_fingerprint_must_be_empty","browser_scroll_direction_invalid","browser_navigation_unavailable","browser_billing_manual","browser_action_unsupported","browser_control_not_found","browser_fingerprint_mismatch","browser_option_unavailable","browser_write_review_required","browser_control_manual"],"description":"Bounded failure classification. An entry can precede a successful recovery; the request's current state and terminal reason remain separate.\n* `expired`: The request exceeded its execution deadline.\n* `request_limit`: The organization had no requests available at admission.\n* `request_budget`: The request reached its operation limit or execution deadline before another step could start.\n* `context_budget`: The retained conversation, page, and operation context exceeded its size limit.\n* `invalid_model_output`: The runtime could not accept the model response.\n* `invalid_tool`: The prepared operation used an unsupported delivery mode.\n* `tool_input_limit`: The proposed operation's input exceeded its size limit.\n* `invalid_review`: The proposed action lacked the title or fingerprint required for confirmation.\n* `interrupted`: In-flight work ended before the runtime could record a validated result.\n* `stale_target`: The proposed browser target no longer matched the current page observation.\n* `invalid_arguments`: The proposed browser operation's arguments failed validation.\n* `tool_error`: A tool failed for a reason outside the recorded timeout and cancellation classifications.\n* `tool_timeout`: A tool exceeded its execution deadline.\n* `tool_canceled`: A tool's execution context was canceled.\n* `forbidden`: The proposed operation is not permitted.\n* `error`: The profile could not resolve the proposed operation.\n* `browser_arguments_schema`: The browser arguments did not match the action schema.\n* `browser_observation_invalid`: The page observation could not be decoded.\n* `browser_arguments_invalid`: The browser arguments could not be decoded.\n* `browser_observation_id_mismatch`: The operation referenced a different page observation.\n* `browser_value_too_long`: The input value exceeded the browser limit.\n* `browser_target_too_long`: The target exceeded the browser limit.\n* `browser_fingerprint_too_long`: The fingerprint exceeded the browser limit.\n* `browser_credentials_forbidden`: The operation attempted to enter credentials.\n* `browser_page_manual`: The page requires manual interaction.\n* `browser_target_must_be_empty`: The operation requires an empty target.\n* `browser_value_must_be_empty`: The operation requires an empty value.\n* `browser_fingerprint_must_be_empty`: The operation requires an empty fingerprint.\n* `browser_scroll_direction_invalid`: The scroll direction was not supported.\n* `browser_navigation_unavailable`: The destination was not available for browser navigation.\n* `browser_billing_manual`: Billing controls require manual interaction.\n* `browser_action_unsupported`: The browser action was not supported.\n* `browser_control_not_found`: The target control was absent from the current observation.\n* `browser_fingerprint_mismatch`: The fingerprint did not match the observed control.\n* `browser_option_unavailable`: The selected value was absent from the observed options.\n* `browser_write_review_required`: The operation lacked a supported native review.\n* `browser_control_manual`: The control requires manual interaction.\n"},"AdminAssistantDiagnosticTool":{"type":"string","minLength":1,"enum":["search_bird_knowledge","control_browser","open_help_view","other"],"description":"Allowlisted tool name when the failure belongs to a tool operation."},"AdminAssistantDiagnosticError":{"type":"object","description":"Sanitized runtime or tool failure without raw error text or customer content.","additionalProperties":false,"required":["step","occurred_at","source","code"],"properties":{"step":{"type":"integer","minimum":0,"description":"Counted execution step when the failure was recorded; 0 precedes the first advance."},"occurred_at":{"type":"string","format":"date-time","minLength":1,"description":"When the failure was recorded."},"source":{"$ref":"#/components/schemas/AdminAssistantDiagnosticSource"},"code":{"$ref":"#/components/schemas/AdminAssistantDiagnosticCode"},"tool":{"$ref":"#/components/schemas/AdminAssistantDiagnosticTool"}}},"AdminAssistantCostSummary":{"type":"object","description":"Sum of retained per-attempt estimates in one currency. Absent if no attempts are priced or currencies differ. Active requests can accumulate more cost.","additionalProperties":false,"required":["amount","currency_code","priced_calls","total_calls","complete"],"properties":{"amount":{"$ref":"#/components/schemas/amount"},"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"priced_calls":{"type":"integer","minimum":1,"maximum":64},"total_calls":{"type":"integer","minimum":1,"maximum":64},"complete":{"type":"boolean","description":"Every retained call has an estimate and diagnostics are not truncated. This does not establish invoice accuracy or request completion."}}},"AdminAssistantExecutionDiagnostics":{"type":"object","description":"Bounded execution history retained through retries, completion, and temporary-context expiry. Does not establish which errors the customer saw.","additionalProperties":false,"required":["model_calls","errors","truncated"],"properties":{"retrievals":{"type":"array","maxItems":64,"description":"Public and curated searches, including automatic first-message retrieval.","items":{"$ref":"#/components/schemas/AdminAssistantRetrieval"}},"model_calls":{"type":"array","maxItems":64,"description":"Earliest recorded provider attempts, including internal recovery attempts.","items":{"$ref":"#/components/schemas/AdminAssistantModelCall"}},"errors":{"type":"array","maxItems":64,"description":"Earliest recorded runtime and tool errors. Model failures are recorded in model_calls.","items":{"$ref":"#/components/schemas/AdminAssistantDiagnosticError"}},"truncated":{"type":"boolean","description":"Whether later model attempts, errors, or retrievals were omitted because a history limit was reached."},"estimated_cost":{"$ref":"#/components/schemas/AdminAssistantCostSummary"}}},"AdminAssistantExecutionSummary":{"type":"object","description":"Recorded state of a request in this conversation. Completion does not establish that the customer's issue was resolved.","additionalProperties":false,"required":["run_id","state","awaiting_review","updated_at"],"properties":{"run_id":{"$ref":"#/components/schemas/AssistantRunID"},"terminal_reason":{"$ref":"#/components/schemas/AssistantRunTerminalReason","description":"Recorded terminal reason, when available. Does not contain provider errors or prove that the customer saw an error."},"state":{"$ref":"#/components/schemas/AssistantRunState"},"awaiting_review":{"type":"boolean","description":"Whether this request is waiting for review of its current proposed action."},"updated_at":{"type":"string","format":"date-time","minLength":1,"description":"When the execution state was last updated."},"working_duration_ms":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991,"description":"Recorded model and tool execution time in milliseconds, including execution during recovery and retries; a subtotal while active. Excludes time waiting for approval, manual interaction, retry, or scheduling. Browser timing is client-reported diagnostic data. Omitted when timing evidence is incomplete."},"diagnostics":{"$ref":"#/components/schemas/AdminAssistantExecutionDiagnostics","description":"Recorded model attempts and sanitized error history. Omitted for requests whose history was not recorded."}}},"AssistantMessageID":{"type":"string","minLength":1,"pattern":"^asm_[0-9a-hjkmnp-tv-z]{26}$","example":"asm_01krdgeqcxet5s7t44vh8rt9mg"},"AssistantCitation":{"type":"object","description":"A source verified against the passages retrieved for this answer.","additionalProperties":false,"required":["id","url","title","version"],"properties":{"id":{"type":"string","description":"Source identity.","maxLength":300,"minLength":1},"url":{"type":"string","format":"uri","description":"Published source URL.","maxLength":2000,"minLength":1},"title":{"type":"string","description":"Published source title.","maxLength":500,"minLength":1},"version":{"type":"string","description":"Available source revision or content identity.","maxLength":200,"minLength":0}}},"AssistantMessage":{"type":"object","description":"An immutable committed message with independently editable feedback.","x-sensitive":true,"additionalProperties":false,"required":["id","conversation_id","position","role","body","citations","feedback_revision","feedback_reasons","created_at"],"properties":{"id":{"$ref":"#/components/schemas/AssistantMessageID"},"conversation_id":{"$ref":"#/components/schemas/AssistantConversationID"},"run_id":{"$ref":"#/components/schemas/AssistantRunID"},"position":{"type":"integer","format":"int64","description":"Conversation ordering position."},"role":{"type":"string","description":"The committed author role is customer or assistant. Support handoffs create a separate Support message.","maxLength":30,"minLength":1},"body":{"type":"string","description":"Committed text.","minLength":1,"maxLength":32768},"citations":{"type":"array","items":{"$ref":"#/components/schemas/AssistantCitation"},"maxItems":8},"feedback_revision":{"type":"integer","format":"int64","description":"Revision required for feedback edits."},"vote":{"type":"string","description":"Current vote, if any.","maxLength":20,"minLength":1},"feedback_reasons":{"type":"array","items":{"type":"string","description":"Selected reason.","maxLength":200,"minLength":1},"maxItems":8},"feedback_comment":{"type":"string","description":"Optional feedback.","maxLength":4000,"minLength":1},"report_reason":{"type":"string","description":"Report category.","maxLength":200,"minLength":1},"report_comment":{"type":"string","description":"Report details.","maxLength":4000,"minLength":1},"created_at":{"type":"string","format":"date-time","description":"When the message was committed.","minLength":1},"reported_at":{"type":"string","format":"date-time","description":"When this answer was reported.","minLength":1},"support_reference":{"type":"string","description":"Verified Support ticket associated with this message.","minLength":1,"maxLength":200}}},"AdminStaffAssistantReviewDetail":{"description":"Audited staff content for admins and Security.","x-sensitive":true,"allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","required":["data","conversation"],"properties":{"conversation":{"$ref":"#/components/schemas/AdminStaffAssistantReview"},"executions":{"type":"array","description":"Recorded requests referenced by messages on this page. Retired request records are omitted. These are current outcomes, not a history of every interruption or proof of which errors the staff member saw.","maxItems":100,"items":{"$ref":"#/components/schemas/AdminAssistantExecutionSummary"}},"latest_execution":{"$ref":"#/components/schemas/AdminAssistantExecutionSummary"},"data":{"type":"array","description":"Committed messages, newest page first.","items":{"$ref":"#/components/schemas/AssistantMessage"}}}}],"unevaluatedProperties":false},"AdminAssistantAudience":{"type":"string","minLength":1,"description":"Whether the conversation belongs to a customer or staff member.","enum":["customer","staff"]},"AdminAssistantExportIdentity":{"type":"object","description":"Exported conversation identity. Organization and workspace are present only for customer conversations.","x-sensitive":true,"additionalProperties":false,"required":["conversation","cursor","user_id","reported","positive_feedback_count","negative_feedback_count","support_references"],"properties":{"cursor":{"type":"string","minLength":1,"description":"Opaque pagination boundary for updated_at ordering, reusable across regions."},"conversation":{"$ref":"#/components/schemas/AssistantConversation"},"user_id":{"$ref":"#/components/schemas/UserID"},"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"reported":{"type":"boolean","description":"Whether a committed answer has been reported."},"positive_feedback_count":{"type":"integer","format":"int64","minimum":0,"description":"Number of saved answers currently rated positively by the customer."},"negative_feedback_count":{"type":"integer","format":"int64","minimum":0,"description":"Number of saved answers currently rated negatively by the customer."},"first_reported_message_id":{"description":"Earliest reported answer in conversation order. Absent when no answer is reported.","$ref":"#/components/schemas/AssistantMessageID"},"support_references":{"type":"array","description":"Verified linked Support ticket identities.","items":{"type":"string","description":"Support ticket identity.","maxLength":200}}}},"AdminAssistantConversationExport":{"type":"object","description":"A complete saved conversation and retained diagnostic records at export time.","x-sensitive":true,"additionalProperties":false,"required":["format","version","audience","started_at","exported_at","region","conversation","through_message_id","messages","executions","missing_execution_run_ids","limitations"],"properties":{"format":{"type":"string","minLength":1,"description":"Export format identifier.","const":"bird.ask-bird.conversation"},"version":{"type":"integer","description":"Export format version.","const":1},"audience":{"$ref":"#/components/schemas/AdminAssistantAudience"},"started_at":{"type":"string","minLength":1,"format":"date-time","description":"Export start time."},"exported_at":{"type":"string","minLength":1,"format":"date-time","description":"Export completion time."},"region":{"$ref":"#/components/schemas/Region"},"conversation":{"$ref":"#/components/schemas/AdminAssistantExportIdentity"},"through_message_id":{"oneOf":[{"$ref":"#/components/schemas/AssistantMessageID"},{"type":"null"}],"description":"Last saved message included, or null for an empty conversation."},"messages":{"type":"array","description":"Saved messages in chronological order.","items":{"$ref":"#/components/schemas/AssistantMessage"}},"executions":{"type":"array","description":"Retained request outcomes and diagnostics associated with exported messages, including the latest request.","items":{"$ref":"#/components/schemas/AdminAssistantExecutionSummary"}},"missing_execution_run_ids":{"type":"array","description":"Message request identities whose diagnostic records are no longer available.","items":{"$ref":"#/components/schemas/AssistantRunID"}},"limitations":{"type":"array","description":"Interpretation and completeness limits of the retained data.","items":{"type":"string","minLength":1}}}},"AdminAssistantConversation":{"type":"object","description":"Customer conversation summary for an authorized regional staff reader.","x-sensitive":true,"additionalProperties":false,"required":["conversation","cursor","user_id","organization_id","workspace_id","reported","positive_feedback_count","negative_feedback_count","support_references"],"properties":{"cursor":{"type":"string","minLength":1,"description":"Opaque pagination boundary for updated_at ordering, reusable across regions."},"conversation":{"$ref":"#/components/schemas/AssistantConversation"},"user_id":{"$ref":"#/components/schemas/UserID"},"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"reported":{"type":"boolean","description":"Whether a committed answer has been reported."},"positive_feedback_count":{"type":"integer","format":"int64","minimum":0,"description":"Number of saved answers currently rated positively by the customer."},"negative_feedback_count":{"type":"integer","format":"int64","minimum":0,"description":"Number of saved answers currently rated negatively by the customer."},"first_reported_message_id":{"description":"Earliest reported answer in conversation order. Absent when no answer is reported.","$ref":"#/components/schemas/AssistantMessageID"},"support_references":{"type":"array","description":"Verified linked Support ticket identities.","items":{"type":"string","description":"Support ticket identity.","maxLength":200}}}},"AdminAssistantConversationList":{"description":"Audited customer content from the selected region.","x-sensitive":true,"allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Conversation summaries, newest first.","items":{"$ref":"#/components/schemas/AdminAssistantConversation"}}}}],"unevaluatedProperties":false},"AdminAssistantReportNavigation":{"type":"object","description":"Position and neighboring reports for the selected answer in conversation order.","additionalProperties":false,"required":["message_id","index","total"],"properties":{"message_id":{"$ref":"#/components/schemas/AssistantMessageID"},"index":{"type":"integer","format":"int64","minimum":1,"description":"One-based position of this report in conversation order."},"total":{"type":"integer","format":"int64","minimum":1,"description":"Total reported answers in the conversation."},"previous_message_id":{"description":"Previous reported answer. Absent at the first report.","$ref":"#/components/schemas/AssistantMessageID"},"next_message_id":{"description":"Next reported answer. Absent at the last report.","$ref":"#/components/schemas/AssistantMessageID"}}},"AdminAssistantConversationDetail":{"description":"Audited customer content from the selected region.","x-sensitive":true,"allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","required":["data","conversation"],"properties":{"conversation":{"$ref":"#/components/schemas/AdminAssistantConversation"},"report_navigation":{"$ref":"#/components/schemas/AdminAssistantReportNavigation"},"executions":{"type":"array","description":"Recorded requests referenced by messages on this page. Retired request records are omitted. These are current outcomes, not a history of every interruption or proof of which errors the customer saw.","maxItems":100,"items":{"$ref":"#/components/schemas/AdminAssistantExecutionSummary"}},"latest_execution":{"$ref":"#/components/schemas/AdminAssistantExecutionSummary"},"data":{"type":"array","description":"Committed messages, newest page first.","items":{"$ref":"#/components/schemas/AssistantMessage"}}}}],"unevaluatedProperties":false},"AdminStaffAssistantCapabilities":{"type":"object","additionalProperties":false,"description":"Availability and current permission version for personal staff Ask Bird.","required":["enabled","authorization_version"],"properties":{"enabled":{"type":"boolean","description":"Whether new staff requests can be started."},"authorization_version":{"type":"string","description":"Opaque current permission version required for subsequent requests.","minLength":64,"maxLength":64,"pattern":"^[a-f0-9]{64}$"}}},"AssistantNavigationEntry":{"type":"object","description":"A permitted application menu destination, including pages that require manual operation.","additionalProperties":false,"required":["group","label","keywords"],"properties":{"group":{"type":"string","minLength":1,"maxLength":200,"description":"Menu section containing this destination."},"label":{"type":"string","minLength":1,"maxLength":200,"description":"Current destination label."},"keywords":{"type":"array","maxItems":8,"items":{"type":"string","minLength":1,"maxLength":200},"description":"Search terms associated with this destination."},"href":{"type":"string","minLength":1,"maxLength":2000,"description":"Exact destination for the customer to open, subject to their access."}}},"AssistantNavigation":{"type":"object","description":"Current permitted menu locations for guidance and customer-click links. These entries do not grant browser control.","additionalProperties":false,"required":["entries","truncated"],"properties":{"entries":{"type":"array","maxItems":200,"items":{"$ref":"#/components/schemas/AssistantNavigationEntry"}},"truncated":{"type":"boolean","description":"Whether the menu guide omits additional destinations."}}},"AssistantControlOption":{"type":"object","description":"An available option in an observed native control.","additionalProperties":false,"required":["value","label"],"properties":{"value":{"type":"string","description":"Exact option value.","maxLength":2000,"minLength":0},"label":{"type":"string","description":"Visible option label.","maxLength":500,"minLength":0}}},"AssistantControlBehavior":{"type":"string","description":"Effect semantics supplied by the native Bird control.","enum":["read","prepare","write","manual"],"minLength":1},"AssistantReviewValue":{"type":"object","description":"A meaningful value in the native operation review.","additionalProperties":false,"required":["label","value"],"properties":{"label":{"type":"string","description":"Name of the value.","maxLength":160,"minLength":1},"value":{"type":"string","description":"Current reviewed value.","maxLength":16000,"minLength":0}}},"AssistantReview":{"type":"object","description":"The exact operation and values the customer reviews.","additionalProperties":false,"required":["title","description","values","fingerprint"],"properties":{"title":{"type":"string","description":"Named operation.","maxLength":200,"minLength":1},"description":{"type":"string","description":"Effect of submitting this operation.","maxLength":2000,"minLength":1},"values":{"type":"array","items":{"$ref":"#/components/schemas/AssistantReviewValue"},"maxItems":80},"fingerprint":{"type":"string","description":"Identity of the reviewed target and values.","maxLength":128,"minLength":1}}},"AssistantBrowserControl":{"type":"object","description":"A bounded, visible native control with a short-lived reference.","additionalProperties":false,"required":["ref","label","kind","value","options","behavior","fingerprint"],"properties":{"ref":{"type":"string","description":"Reference valid for this observation only.","maxLength":80,"minLength":1},"label":{"type":"string","description":"Visible or accessible name.","maxLength":500,"minLength":1},"kind":{"type":"string","description":"Native control kind.","maxLength":80,"minLength":1},"value":{"type":"string","description":"Sanitized current value.","maxLength":16000,"minLength":0},"options":{"type":"array","items":{"$ref":"#/components/schemas/AssistantControlOption"},"maxItems":100},"href":{"type":"string","description":"Observed same-site destination.","maxLength":2000,"minLength":1},"behavior":{"$ref":"#/components/schemas/AssistantControlBehavior"},"fingerprint":{"type":"string","description":"Meaningful native control and form state.","maxLength":128,"minLength":1},"effect":{"$ref":"#/components/schemas/AssistantReview"}}},"AssistantDestination":{"type":"object","description":"A permitted Bird destination.","additionalProperties":false,"required":["href","label"],"properties":{"href":{"type":"string","description":"Exact same-site path.","maxLength":2000,"minLength":1},"label":{"type":"string","description":"Destination name.","maxLength":200,"minLength":1}}},"AssistantPageContext":{"type":"object","description":"Sanitized current-tab evidence. Page content does not grant permission to act.","additionalProperties":false,"required":["observation_id","path","title","text","surface","controls","destinations"],"properties":{"navigation":{"$ref":"#/components/schemas/AssistantNavigation"},"observation_id":{"type":"string","format":"uuid","description":"Identity of the current observation.","minLength":1},"path":{"type":"string","description":"Current Bird route.","maxLength":2000,"minLength":1},"title":{"type":"string","description":"Current page title.","maxLength":300,"minLength":0},"text":{"type":"string","description":"Relevant visible text, excluding private and secret fields.","maxLength":24000,"minLength":0},"surface":{"type":"string","description":"Current dialog, popover or page identity.","maxLength":200,"minLength":1},"controls":{"type":"array","items":{"$ref":"#/components/schemas/AssistantBrowserControl"},"maxItems":100},"destinations":{"type":"array","items":{"$ref":"#/components/schemas/AssistantDestination"},"maxItems":80}}},"CreateAssistantRun":{"type":"object","description":"One immutable customer intent. Retain the key and creation time for retries.","additionalProperties":false,"required":["request_key","request_created_at","text","page_context"],"properties":{"request_key":{"type":"string","format":"uuid","description":"Stable client request identity.","minLength":1},"request_created_at":{"type":"string","format":"date-time","description":"Immutable time the customer submitted this intent.","minLength":1},"conversation_id":{"$ref":"#/components/schemas/AssistantConversationID"},"text":{"type":"string","description":"The customer request.","maxLength":16000,"minLength":1},"page_context":{"$ref":"#/components/schemas/AssistantPageContext"}},"x-sensitive":true},"AssistantBrowserVerb":{"type":"string","description":"One operation in the current Bird tab.","enum":["observe","navigate","click","type","select","scroll"],"minLength":1},"AssistantBrowserOperation":{"type":"object","description":"An operation bound to the observed Bird page and control.","additionalProperties":false,"required":["action","target","value","observation_id","fingerprint"],"properties":{"action":{"$ref":"#/components/schemas/AssistantBrowserVerb"},"target":{"type":"string","description":"Exact observed control reference or permitted destination.","maxLength":2000,"minLength":0},"value":{"type":"string","description":"Text or option value to prepare; up or down for scrolling.","maxLength":16000,"minLength":0},"observation_id":{"type":"string","format":"uuid","description":"Observation from which this operation was proposed.","minLength":1},"fingerprint":{"type":"string","description":"Observed target fingerprint; empty for operations without a control.","maxLength":128,"minLength":0}}},"AssistantHelpView":{"type":"string","description":"A companion view to open without submitting a form.","enum":["contact","feedback","slack","history","resources"],"minLength":1},"AssistantActionID":{"type":"string","minLength":1,"pattern":"^asa_[0-9a-hjkmnp-tv-z]{26}$","example":"asa_01krdgeqcxet5s7t44vh8rt9mg"},"AssistantPendingOperation":{"type":"object","description":"The single external operation awaiting browser execution or review.","additionalProperties":false,"required":["id"],"oneOf":[{"required":["browser"]},{"required":["help_view"]}],"properties":{"id":{"type":"string","format":"uuid","description":"Identity that the result must acknowledge.","minLength":1},"browser":{"$ref":"#/components/schemas/AssistantBrowserOperation"},"help_view":{"$ref":"#/components/schemas/AssistantHelpView"},"action_id":{"$ref":"#/components/schemas/AssistantActionID"}}},"AssistantActionState":{"type":"string","description":"Dispatch and outcome state. `proposed` awaits review and may be declined or claimed. `claimed` has granted one caller permission to submit; a replay never grants permission again. `succeeded` and `failed` record a confirmed result. `declined` records a refused proposal. `not_dispatched` confirms that submission did not occur. `uncertain` means submission may have occurred; reconcile the existing operation before attempting another change. Never redispatch a claimed or uncertain operation.","enum":["proposed","claimed","succeeded","failed","declined","not_dispatched","uncertain"],"minLength":1},"AssistantAction":{"type":"object","description":"An immutable proposal and the observed outcome of its one dispatch claim.","additionalProperties":false,"required":["id","operation_id","state"],"properties":{"id":{"$ref":"#/components/schemas/AssistantActionID"},"operation_id":{"type":"string","format":"uuid","description":"Matching operation identity.","minLength":1},"state":{"$ref":"#/components/schemas/AssistantActionState"},"review":{"$ref":"#/components/schemas/AssistantReview"},"result":{"type":"string","description":"Observed outcome or uncertainty.","maxLength":16000,"minLength":1}}},"AssistantRun":{"type":"object","description":"Current request state and its committed messages. Reading it performs no inference.","additionalProperties":false,"required":["id","conversation_id","state","revision","admitted","deadline","replay_until","messages"],"properties":{"id":{"$ref":"#/components/schemas/AssistantRunID"},"conversation_id":{"$ref":"#/components/schemas/AssistantConversationID"},"state":{"$ref":"#/components/schemas/AssistantRunState"},"revision":{"type":"integer","format":"int64","description":"Expected revision for the next transition."},"admitted":{"type":"boolean","description":"Whether this request consumed its one daily unit."},"deadline":{"type":"string","format":"date-time","description":"Overall execution deadline.","minLength":1},"replay_until":{"type":"string","format":"date-time","description":"Deadline for reconciling this request and its action outcomes.","minLength":1},"pending_operation":{"$ref":"#/components/schemas/AssistantPendingOperation"},"action":{"$ref":"#/components/schemas/AssistantAction"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/AssistantMessage"},"maxItems":2},"terminal_reason":{"$ref":"#/components/schemas/AssistantRunTerminalReason"}},"x-sensitive":true},"AssistantToolResult":{"type":"object","description":"Observed result of the exact pending browser operation.","additionalProperties":false,"required":["operation_id","text","success"],"properties":{"operation_id":{"type":"string","format":"uuid","description":"Operation being acknowledged.","minLength":1},"text":{"type":"string","description":"Bounded observed result, including partial outcomes.","maxLength":16000,"minLength":1},"success":{"type":"boolean","description":"Whether product evidence confirms the operation succeeded."},"page_context":{"$ref":"#/components/schemas/AssistantPageContext"},"execution_duration_ms":{"type":"integer","format":"int64","minimum":0,"maximum":9007199254740991,"description":"Measured browser execution time in milliseconds, excluding approval and manual interaction. Omitted when unknown."}}},"AdvanceAssistantRun":{"type":"object","description":"Advance one request revision; include a result only for its matching unclaimed external operation.","additionalProperties":false,"required":["expected_revision"],"properties":{"expected_revision":{"type":"integer","format":"int64","minimum":1,"description":"Current request revision."},"result":{"$ref":"#/components/schemas/AssistantToolResult"}},"x-sensitive":true},"AdminStaffAssistantConversation":{"type":"object","description":"A saved conversation in the staff member’s current-permission history. Admins and Security may separately review retained staff conversations.","x-sensitive":true,"additionalProperties":false,"required":["id","title","revision","archived","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/AssistantConversationID"},"title":{"type":"string","description":"Title derived from the first staff member question.","minLength":1,"maxLength":300},"revision":{"type":"integer","format":"int64","description":"Current revision for archive, restore and delete operations.","minimum":0},"archived":{"type":"boolean","description":"Whether the conversation is archived."},"created_at":{"type":"string","format":"date-time","description":"Creation time.","minLength":1},"updated_at":{"type":"string","format":"date-time","description":"Last content or explicit history update.","minLength":1}}},"AdminStaffAssistantConversationPage":{"x-sensitive":true,"description":"An owner-scoped page of conversation summaries.","allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Conversation summaries ordered newest first.","items":{"$ref":"#/components/schemas/AdminStaffAssistantConversation"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminStaffAssistantConversationDetail":{"x-sensitive":true,"description":"Saved conversation with a page of committed messages and any active request.","allOf":[{"type":"object","required":["conversation","data"],"properties":{"conversation":{"$ref":"#/components/schemas/AdminStaffAssistantConversation"},"data":{"type":"array","description":"Committed messages, newest page first.","items":{"$ref":"#/components/schemas/AssistantMessage"}},"active_run":{"$ref":"#/components/schemas/AssistantRun"}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"UpdateAssistantConversation":{"type":"object","description":"Explicit revision-guarded archive or restore.","x-sensitive":true,"additionalProperties":false,"required":["revision","archived"],"properties":{"revision":{"type":"integer","format":"int64","description":"Last observed conversation revision.","minimum":0},"archived":{"type":"boolean","description":"Set true to archive and false to restore."}}},"AssistantConversationList":{"x-sensitive":true,"description":"An owner-scoped page of conversation summaries.","allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Conversation summaries ordered newest first.","items":{"$ref":"#/components/schemas/AssistantConversation"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AssistantConversationDetail":{"x-sensitive":true,"description":"Saved conversation with a page of committed messages and any active request.","allOf":[{"type":"object","required":["conversation","data"],"properties":{"conversation":{"$ref":"#/components/schemas/AssistantConversation"},"data":{"type":"array","description":"Committed messages, newest page first.","items":{"$ref":"#/components/schemas/AssistantMessage"}},"active_run":{"$ref":"#/components/schemas/AssistantRun"}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AssistantVote":{"type":"string","description":"Selected vote, or none to clear it.","minLength":1,"maxLength":10,"enum":["up","down","none"]},"UpdateAssistantFeedback":{"type":"object","description":"Feedback on one exact committed assistant answer. Published answer content is unchanged.","x-sensitive":true,"additionalProperties":false,"required":["revision","vote","reasons","comment"],"properties":{"revision":{"type":"integer","format":"int64","description":"Last observed feedback revision.","minimum":0},"vote":{"$ref":"#/components/schemas/AssistantVote"},"reasons":{"type":"array","description":"Optional reasons for the selected vote.","maxItems":8,"items":{"type":"string","description":"Feedback reason.","minLength":1,"maxLength":200}},"comment":{"type":"string","description":"Optional feedback comment; empty clears it.","minLength":0,"maxLength":4000}}},"CreateAssistantReport":{"type":"object","description":"A report about one exact immutable assistant answer.","x-sensitive":true,"additionalProperties":false,"required":["revision","reason","comment"],"properties":{"revision":{"type":"integer","format":"int64","description":"Last observed feedback revision.","minimum":0},"reason":{"type":"string","description":"Report category.","minLength":1,"maxLength":200},"comment":{"type":"string","description":"Optional details.","minLength":0,"maxLength":4000}}},"CreateAssistantHandoff":{"type":"object","description":"Prepare an immutable Support submission for customer review.","x-sensitive":true,"additionalProperties":false,"required":["request_key","request_created_at","body","attachment_ids"],"properties":{"request_key":{"type":"string","description":"Stable preparation identity.","minLength":1,"maxLength":300,"format":"uuid"},"request_created_at":{"type":"string","description":"Original preparation time.","minLength":1,"maxLength":300,"format":"date-time"},"body":{"type":"string","description":"Customer-authored message to send to Support.","minLength":1,"maxLength":12000},"attachment_ids":{"type":"array","description":"Existing Support attachment identities selected by the customer.","maxItems":100,"uniqueItems":true,"items":{"type":"string","description":"Support attachment identity.","minLength":1,"maxLength":200}},"answer_id":{"$ref":"#/components/schemas/AssistantMessageID"}}},"AssistantHandoff":{"type":"object","description":"Canonical Support preparation and verified submission state.","x-sensitive":true,"additionalProperties":false,"required":["id","conversation_id","state","fingerprint","body","attachment_ids","can_reconcile","created_at"],"properties":{"id":{"$ref":"#/components/schemas/AssistantActionID"},"conversation_id":{"$ref":"#/components/schemas/AssistantConversationID"},"state":{"$ref":"#/components/schemas/AssistantActionState"},"fingerprint":{"type":"string","description":"Identity of the exact review.","minLength":1,"maxLength":128},"body":{"type":"string","description":"Exact message, including the saved conversation link.","minLength":1,"maxLength":16000},"attachment_ids":{"type":"array","description":"Existing Support attachment identities selected by the customer.","maxItems":100,"uniqueItems":true,"items":{"type":"string","description":"Support attachment identity.","minLength":1,"maxLength":200}},"answer_id":{"$ref":"#/components/schemas/AssistantMessageID"},"support_reference":{"type":"string","description":"Verified Support ticket identity. Absent until verified.","minLength":1,"maxLength":300},"can_reconcile":{"type":"boolean","description":"Whether an unresolved submission has a known thread to inspect."},"created_at":{"type":"string","description":"Time this handoff was prepared.","minLength":1,"maxLength":300,"format":"date-time"}}},"SubmitAssistantHandoff":{"type":"object","description":"Submit the exact reviewed Support handoff once.","x-sensitive":true,"additionalProperties":false,"required":["reviewed_fingerprint"],"properties":{"reviewed_fingerprint":{"type":"string","description":"Fingerprint returned by the prepared handoff.","minLength":1,"maxLength":128}}},"ClaimAssistantAction":{"type":"object","description":"Claim dispatch once for the exact review the customer approved.","additionalProperties":false,"required":["expected_revision","reviewed_fingerprint"],"properties":{"expected_revision":{"type":"integer","format":"int64","minimum":1,"description":"Current request revision."},"reviewed_fingerprint":{"type":"string","description":"Fingerprint of the visible reviewed target and values.","maxLength":128,"minLength":1}},"x-sensitive":true},"AssistantDispatch":{"type":"object","description":"Only a winning external claim grants dispatch. A repeated claim never grants it again.","additionalProperties":false,"required":["run","granted"],"properties":{"run":{"$ref":"#/components/schemas/AssistantRun"},"granted":{"type":"boolean","description":"True only for the response that won this external dispatch claim."}},"x-sensitive":true},"AssistantActionDisposition":{"type":"string","description":"Observed final or uncertain outcome of the proposed action.","minLength":1,"enum":["succeeded","failed","declined","not_dispatched","uncertain"]},"RecordAssistantActionResult":{"type":"object","description":"Record the outcome without repeating the product operation.","additionalProperties":false,"required":["disposition","result"],"properties":{"disposition":{"$ref":"#/components/schemas/AssistantActionDisposition"},"result":{"$ref":"#/components/schemas/AssistantToolResult"}},"x-sensitive":true},"SupportChatMessageAttachment":{"type":"object","additionalProperties":false,"required":["id","file_name"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"Support attachment ID.","example":"att_01abc"},"file_name":{"type":"string","minLength":1,"readOnly":true,"description":"Original filename as uploaded.","example":"screenshot.png"},"mime_type":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"MIME type of the attachment. Null when unavailable.","example":"image/png"},"size":{"type":["integer","null"],"readOnly":true,"description":"File size in bytes. Null when unavailable.","example":102400},"download_url":{"type":["string","null"],"format":"uri","minLength":1,"readOnly":true,"description":"Short-lived signed URL for downloading or previewing the attachment. Present for image attachments; null for other file types or when unavailable. Expires within minutes. Do not cache it.\n"}}},"SupportChatThreadMessage":{"type":"object","additionalProperties":false,"required":["id","sender_type","created_at"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"Timeline entry identifier.","example":"te_01abc"},"text":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Text content of the message. Null for attachment-only messages.","example":"Hi, I need help with my domain setup."},"attachments":{"type":"array","readOnly":true,"description":"Files attached to this message.","items":{"$ref":"#/components/schemas/SupportChatMessageAttachment"}},"sender_type":{"type":"string","minLength":1,"enum":["customer","agent"],"readOnly":true,"description":"Whether the message was sent by the customer or a support agent.","example":"customer"},"sender_name":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Display name of the sender. Null when unavailable.","example":"Alice Anderson"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the message was sent.","example":"2026-06-19T12:00:00Z"}}},"SupportTicket":{"type":"object","additionalProperties":false,"required":["id","status","created_at","messages"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier of this ticket.","example":"th_01abc"},"status":{"type":"string","minLength":1,"enum":["OPEN","SNOOZED","DONE"],"readOnly":true,"description":"Current lifecycle state of the ticket.\n\n- `OPEN`: active.\n- `SNOOZED`: paused.\n- `DONE`: resolved.\n","example":"OPEN"},"title":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Ticket subject or title.","example":"Domain verification stuck"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the ticket was created.","example":"2026-06-19T12:00:00Z"},"messages":{"type":"array","readOnly":true,"description":"Ordered list of messages in the ticket, oldest first.","items":{"$ref":"#/components/schemas/SupportChatThreadMessage"}},"has_unread":{"type":"boolean","readOnly":true,"description":"Whether the authenticated user has an agent reply on this ticket they have not yet viewed.","example":true}}},"SupportTicketList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","readOnly":true,"description":"Tickets ordered newest first.","items":{"$ref":"#/components/schemas/SupportTicket"}}}},"CreateSupportTicket":{"type":"object","additionalProperties":false,"description":"Either message or `attachment_ids` (or both) must be provided.\n","properties":{"message":{"type":"string","minLength":1,"maxLength":10000,"description":"First message to send in the new ticket. Optional when `attachment_ids` are provided.\n","example":"Hi, I need help setting up my sending domain."},"title":{"type":"string","minLength":1,"maxLength":200,"description":"Optional subject for the ticket. When omitted, derived from the first message.\n","example":"Domain verification stuck"},"priority":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"],"default":"MEDIUM","description":"Urgency level for the ticket. Defaults to MEDIUM when omitted.\n","example":"MEDIUM"},"type":{"type":"string","enum":["ai","support","feedback"],"default":"ai","description":"Ticket routing.\n\n- `ai`: routes to the AI assistant.\n- `support`: opens a human support request.\n- `feedback`: files product feedback.\n\nDefaults to `ai`.\n","example":"support"},"area":{"type":"string","enum":["email","sms","whatsapp","voice","api","dashboard","billing","other"],"description":"Support area. Only used when type is `support`: determines which team receives the ticket.\n","example":"email"},"feedback_kind":{"type":"string","enum":["feature-request","improvement","bug","general"],"default":"general","description":"Feedback category. Only used when type is `feedback`. Defaults to `general`.\n","example":"feature-request"},"attachment_ids":{"type":"array","maxItems":100,"description":"IDs of files to attach to the first message, each obtained from the attachment upload-url endpoint. Up to 100 attachments.\n","items":{"type":"string","minLength":1},"example":["att_01abc"]}},"example":{"message":"Hi, I need help setting up my sending domain.","title":"Domain verification stuck","type":"support","priority":"MEDIUM","area":"email"}},"CreateSupportTicketMessage":{"type":"object","additionalProperties":false,"description":"Either text or `attachment_ids` (or both) must be provided.\n","properties":{"text":{"type":"string","minLength":1,"maxLength":10000,"description":"Message content. Optional when `attachment_ids` are provided.","example":"Can you share more details about the error?"},"attachment_ids":{"type":"array","maxItems":100,"description":"IDs of files to attach, each obtained from the attachment upload-url endpoint. Up to 100 attachments.\n","items":{"type":"string","minLength":1},"example":["att_01abc"]}},"example":{"text":"Can you share more details about the error?"}},"SupportTicketMessageResult":{"type":"object","additionalProperties":false,"required":["ticket_id"],"properties":{"ticket_id":{"type":"string","minLength":1,"readOnly":true,"description":"ID of the ticket the message was added to.","example":"th_01abc"}},"example":{"ticket_id":"th_01abc"}},"SupportTicketUnread":{"type":"object","additionalProperties":false,"required":["has_unread"],"properties":{"has_unread":{"type":"boolean","readOnly":true,"description":"Whether the user has any support ticket with an agent reply they have not yet viewed.","example":true}}},"SupportChatAttachmentUploadFormData":{"type":"object","additionalProperties":false,"required":["key","value"],"properties":{"key":{"type":"string","minLength":1,"readOnly":true,"description":"Form field name for the multipart upload.","example":"Content-Type"},"value":{"type":"string","minLength":1,"readOnly":true,"description":"Form field value for the multipart upload.","example":"image/png"}}},"SupportChatAttachmentUploadUrl":{"type":"object","additionalProperties":false,"required":["attachment_id","upload_form_url","upload_form_data"],"properties":{"attachment_id":{"type":"string","minLength":1,"readOnly":true,"description":"Support attachment ID. Pass this in `attachment_ids` when sending a message or creating a ticket.\n","example":"att_01abc"},"upload_form_url":{"type":"string","format":"uri","minLength":1,"readOnly":true,"description":"URL to post the file to. Build a `multipart/form-data` body with the `upload_form_data` fields followed by the file in a field named `file`. Valid for two hours.\n","example":"https://s3.amazonaws.com/plain-attachments"},"upload_form_data":{"type":"array","readOnly":true,"description":"Key/value pairs to include as form fields before the file in the `multipart/form-data` request to `upload_form_url`.\n","items":{"$ref":"#/components/schemas/SupportChatAttachmentUploadFormData"}}}},"SupportSlackChannel":{"type":"object","description":"The private Slack channel provisioned for your organization's support conversations with the Bird team.\n","properties":{"channel_id":{"type":"string","minLength":1,"description":"Slack channel identifier.","example":"C08XXXXXXXXX"},"channel_url":{"type":"string","format":"uri","minLength":1,"description":"URL that opens the channel in the Slack client or web app.\n","example":"https://slack.com/app_redirect?channel=C08XXXXXXXXX"},"invite_pending_approval":{"type":"boolean","description":"`true` when the Slack Connect invite is pending admin approval in your workspace. Ask your Slack workspace admin to approve it.\n"},"invite_sent_at":{"type":["string","null"],"format":"date-time","description":"When the most recent Slack Connect invite was sent. Null when no invite has been sent yet. Use this to surface an expiry warning after 14 days.\n"}},"required":["channel_id","channel_url"],"additionalProperties":false},"DestinationRegion":{"type":["string","null"],"minLength":1,"x-extensible-enum":["western_europe","nordics_baltics","southern_europe","central_eastern_europe","north_america","latin_america_caribbean","middle_east_north_africa","sub_saharan_africa","central_asia_caucasus","south_asia","south_east_asia","east_asia","oceania"],"example":"western_europe","description":"The commercial region this country belongs to, for grouping a destination list the way it is bought rather than alphabetically. `null` for a country we have not assigned yet. Bird defines these regions independently of ISO and UN M49. A country can move between them, so treat the set as open.\n"},"CountryPolicy":{"type":"object","additionalProperties":false,"required":["country_code","country_name","high_risk_destination","data_residency_required","default_locale","supported_platforms","mobile_country_codes","portability_supported","updated_at"],"properties":{"country_code":{"type":"string","minLength":2,"description":"ISO 3166-1 alpha-2 country code.","example":"US","readOnly":true},"country_name":{"type":"string","minLength":1,"description":"Full English country name.","example":"United States","readOnly":true},"high_risk_destination":{"type":"boolean","description":"Whether we treat this country as a high-risk messaging destination.","readOnly":true},"data_residency_required":{"type":"boolean","description":"Whether local data-residency requirements apply.","readOnly":true},"default_locale":{"type":"string","minLength":1,"description":"Default BCP 47 locale for this country.","example":"en-US","readOnly":true},"supported_platforms":{"type":"array","items":{"type":"string"},"description":"Messaging platforms available in this country.","example":["sms","email"],"readOnly":true},"dial_code":{"type":["string","null"],"description":"International dialling prefix (without the leading +).","example":"1","readOnly":true},"region":{"allOf":[{"$ref":"#/components/schemas/DestinationRegion"}],"readOnly":true},"mobile_country_codes":{"type":"array","items":{"type":"string"},"description":"ITU mobile country codes (MCC) assigned to this country.","example":["310","311"],"readOnly":true},"portability_supported":{"type":"boolean","description":"Whether number portability is supported.","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","description":"Timestamp of the last update to this policy.","readOnly":true}}},"CountryPolicyList":{"type":"object","additionalProperties":false,"required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CountryPolicy"},"description":"Matching country policies."},"total":{"type":"integer","format":"int64","description":"Total number of country policies, ignoring pagination."}}},"CountryCode":{"type":"string","minLength":2,"maxLength":2,"pattern":"^[A-Za-z]{2}$","description":"ISO 3166-1 alpha-2 country code.","example":"US"},"MessagingContextPlatform":{"type":"object","additionalProperties":false,"required":["platform","status"],"properties":{"platform":{"type":"string","minLength":1,"description":"Messaging platform identifier.","example":"sms"},"status":{"type":"string","minLength":1,"enum":["available","restricted","coming_soon","unavailable"],"description":"Availability of this platform in the country."},"regulatory_body":{"type":["string","null"],"description":"Name of the primary regulatory body governing SMS in this country."},"consent_model":{"type":["string","null"],"enum":["opt_in","opt_out","double_opt_in"],"description":"Opt-in model required by regulation."},"opt_in_required":{"type":["boolean","null"],"description":"Whether explicit opt-in is required before sending."},"double_opt_in_required":{"type":["boolean","null"],"description":"Whether double opt-in (confirmed subscription) is required."},"opt_out_required":{"type":["boolean","null"],"description":"Whether the sender must honour opt-out requests."},"requires_registration":{"type":["boolean","null"],"description":"Whether sender registration is required before sending."},"requires_template_approval":{"type":["boolean","null"],"description":"Whether message templates must be pre-approved before sending."},"is_one_way_supported":{"type":["boolean","null"],"description":"Whether one-way (send-only) messaging is supported."},"is_two_way_supported":{"type":["boolean","null"],"description":"Whether two-way (reply-capable) messaging is supported."},"max_throughput_per_second":{"type":["integer","null"],"description":"Maximum messages per second allowed by regulation."},"max_message_length":{"type":["integer","null"],"description":"Maximum message length in characters allowed by regulation."},"url_registration_required":{"type":["boolean","null"],"description":"Whether URLs must be registered before use in messages."},"url_shorteners_allowed":{"type":["boolean","null"],"description":"Whether URL shortener services are permitted in messages."},"generic_url_shorteners_allowed":{"type":["boolean","null"],"description":"Whether public (generic) URL shorteners are permitted, as opposed to dedicated branded shorteners."},"has_quiet_hours":{"type":["boolean","null"],"description":"Whether quiet hours restrictions apply in this country."},"quiet_hours_is_regulatory":{"type":["boolean","null"],"description":"Whether quiet hour restrictions are mandated by regulation rather than platform policy."},"restricted_categories":{"type":"array","items":{"type":"string"},"description":"Message categories that are restricted or prohibited."},"required_elements":{"type":"array","items":{"type":"string"},"description":"Message elements that must be present in every message (for example, opt-out instructions)."},"exempt_message_categories":{"type":"array","items":{"type":"string"},"description":"Categories exempt from quiet-hour restrictions."},"regulations_description":{"type":["string","null"],"description":"Summary of applicable regulations."},"restrictions_description":{"type":["string","null"],"description":"Summary of content or operational restrictions."}}},"SMSRegistrationMode":{"type":"string","minLength":1,"enum":["none","optional","required"],"description":"Whether the sender type has a registration path for this destination.\n\n- `none`: no country registration is needed. Where sender IDs are overwritten, registration is unavailable.\n- `optional`: registration is available, but unregistered sending remains possible.\n- `required`: registration must be approved before sending.\n"},"SMSSenderIDBehavior":{"type":"string","minLength":1,"enum":["unknown","preserved","overwritten"],"description":"What carriers do with the sender ID for this destination.\n\n- `unknown`: the outcome has not been confirmed.\n- `preserved`: the submitted sender ID reaches the recipient unchanged.\n- `overwritten`: the submitted sender ID is replaced before delivery.\n"},"SMSSenderIDReplacementType":{"type":"string","minLength":1,"enum":["random_short_code","random_number","generic_alphanumeric","alphanumeric","other"],"description":"The kind of sender ID that replaces the submitted value. generic_alphanumeric means an alphanumeric sender ID selected by the carrier or provider, without a fixed value. alphanumeric means a fixed alphanumeric sender ID.\n"},"SMSSenderIDReplacement":{"type":"object","additionalProperties":false,"required":["type","value","description"],"properties":{"type":{"$ref":"#/components/schemas/SMSSenderIDReplacementType","description":"The kind of sender ID that replaces the submitted value."},"value":{"type":["string","null"],"maxLength":64,"description":"Required for alphanumeric and must be a valid alphanumeric sender ID. Must be null for random_short_code, random_number, and generic_alphanumeric. Optional for other. Whitespace is trimmed; an empty value is treated as null.\n"},"description":{"type":["string","null"],"maxLength":500,"description":"Required and non-blank for other; optional for named kinds. Whitespace is trimmed; an empty description is treated as null. Invalid type, value, or description combinations return 422 when saving a country policy.\n"}}},"SMSRegistrationProgram":{"type":"string","minLength":1,"x-extensible-enum":["10dlc","tfn","alpha","numeric","ctia_short_code"],"description":"The registration program a country runs for a sender type.\n\n- `10dlc`: US A2P brand and campaign registration.\n- `tfn`: US toll-free verification.\n- `alpha`: registers an alphanumeric sender.\n- `numeric`: registers a long code.\n- `ctia_short_code`: a US short-code application.\n"},"MessagingContextSenderType":{"type":"object","additionalProperties":false,"required":["country_code","platform","sender_type","display_name","status","direction","registration_mode","sender_id_behavior","sender_id_replacement","default_routing_priority"],"properties":{"country_code":{"type":"string","minLength":2,"description":"Country this policy applies to."},"platform":{"type":"string","minLength":1,"description":"Messaging platform this sender type belongs to."},"sender_type":{"type":"string","minLength":1,"enum":["long_code","short_code","alphanumeric","toll_free"],"description":"Type of sender identity this policy applies to. The `toll_free` value is distinct from `long_code`. A country can permit one and refuse the other, and the two usually demand different registration. A country lists one entry per type it carries, so a type absent from the list is one it does not carry.\n"},"display_name":{"type":"string","minLength":1,"description":"Human-readable label for this sender type."},"status":{"type":"string","minLength":1,"enum":["available","restricted","coming_soon","unavailable"],"description":"Availability status."},"direction":{"type":"string","minLength":1,"enum":["one_way","two_way"],"description":"Whether this sender type supports replies."},"throughput_mps":{"type":["integer","null"],"description":"Maximum send rate in messages per second."},"registration_mode":{"$ref":"#/components/schemas/SMSRegistrationMode","description":"Whether registration is not required, optional, or required before sending."},"sender_id_behavior":{"$ref":"#/components/schemas/SMSSenderIDBehavior","description":"Expected sender ID behavior. For optional registration, this applies before approval; approved registrations preserve the sender ID. For required registration, this behavior still applies after approval.\n"},"sender_id_replacement":{"description":"The replacement used when sender IDs are overwritten, or null otherwise.","oneOf":[{"$ref":"#/components/schemas/SMSSenderIDReplacement"},{"type":"null"}]},"program":{"readOnly":true,"description":"External registration program, if applicable.","oneOf":[{"$ref":"#/components/schemas/SMSRegistrationProgram"},{"type":"null"}]},"registration_types":{"type":"array","items":{"type":"string"},"description":"Registration categories accepted under the program."},"registration_timeline_min":{"type":["integer","null"],"description":"Minimum expected registration time in business days."},"registration_timeline_max":{"type":["integer","null"],"description":"Maximum expected registration time in business days."},"supported_message_categories":{"type":"array","items":{"type":"string"},"description":"Message categories this sender type can be used for."},"allowed_source_countries":{"type":"array","items":{"$ref":"#/components/schemas/CountryCode"},"description":"The countries a sender of this type must belong to for this destination to accept it. Empty means any. Where it is set, a sender from outside the list has no route to this destination and nothing it can register for, which is how a registration program that admits only local senders is expressed.\n"},"restrictions":{"type":"array","items":{"type":"string"},"description":"Applicable content or operational restrictions."},"default_routing_priority":{"type":"integer","description":"Routing priority relative to other sender types (lower = higher priority)."}}},"MessagingContext":{"type":"object","additionalProperties":false,"required":["country_code","country","platforms","sender_types"],"properties":{"country_code":{"type":"string","minLength":2,"description":"ISO 3166-1 alpha-2 country code.","example":"US"},"country":{"$ref":"#/components/schemas/CountryPolicy","description":"Country-level policy, such as risk classification and data residency requirements."},"platforms":{"type":"array","description":"Per-platform regulatory requirements for this country.","items":{"$ref":"#/components/schemas/MessagingContextPlatform"}},"sender_types":{"type":"array","description":"Sender identity options available in this country.","items":{"$ref":"#/components/schemas/MessagingContextSenderType"}}}},"EgressIPPrefix":{"type":"object","additionalProperties":false,"required":["prefix"],"properties":{"prefix":{"type":"string","minLength":1,"description":"An IP address range in CIDR notation."}}},"EgressIPList":{"type":"object","additionalProperties":false,"required":["updated_at","message","ipv4_prefixes","ipv6_prefixes"],"properties":{"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the list was last refreshed; null until the first list is published."},"message":{"type":"string","minLength":1,"description":"Advisory note for consumers of the list."},"ipv4_prefixes":{"type":"array","items":{"$ref":"#/components/schemas/EgressIPPrefix"},"description":"IPv4 address ranges used for outbound traffic."},"ipv6_prefixes":{"type":"array","items":{"$ref":"#/components/schemas/EgressIPPrefix"},"description":"IPv6 address ranges used for outbound traffic."}}},"WorkspaceNotificationEmails":{"type":"object","additionalProperties":false,"properties":{"operational":{"type":"array","items":{"type":"string","format":"email"},"example":["alerts@example.com"],"description":"Addresses for operational notifications about this workspace (sending domain authentication failures, webhook endpoint degradation). When empty, these notifications go to the organization owners. Maximum 10 addresses."}}},"Timestamps":{"type":"object","required":["created_at","updated_at"],"properties":{"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"example":"2026-05-20T09:14:52Z"},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"example":"2026-05-25T16:42:01Z"}}},"Workspace":{"allOf":[{"type":"object","required":["id","organization_id","name","notification_emails","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"name":{"type":"string","minLength":1,"example":"Production"},"notification_emails":{"$ref":"#/components/schemas/WorkspaceNotificationEmails"},"logo_url":{"type":["string","null"],"format":"uri","readOnly":true,"description":"HTTPS URL to the current workspace logo. `null` when unset."}}},{"$ref":"#/components/schemas/Timestamps"}]},"WorkspaceUpdate":{"type":"object","additionalProperties":false,"properties":{"name":{"$ref":"#/components/schemas/TenantName","example":"Staging"},"notification_emails":{"$ref":"#/components/schemas/WorkspaceNotificationEmails"}}},"WorkspaceEmailSettings":{"type":"object","additionalProperties":false,"required":["storage_enabled"],"properties":{"storage_enabled":{"type":"boolean","description":"Whether outgoing email content, including the HTML body, text body, and attachments, is retained for later retrieval through the message content endpoint. When disabled, only message metadata is kept.","example":true}}},"WorkspaceSettings":{"type":"object","additionalProperties":false,"required":["email"],"properties":{"email":{"$ref":"#/components/schemas/WorkspaceEmailSettings"}}},"WorkspaceEmailSettingsUpdate":{"type":"object","additionalProperties":false,"properties":{"storage_enabled":{"type":"boolean","description":"Whether outgoing email content, including the HTML body, text body, and attachments, is retained for later retrieval through the message content endpoint. When disabled, only message metadata is kept.","example":true}}},"WorkspaceSettingsUpdate":{"type":"object","additionalProperties":false,"properties":{"email":{"$ref":"#/components/schemas/WorkspaceEmailSettingsUpdate"}}},"NotificationEmails":{"type":"object","additionalProperties":false,"properties":{"billing":{"type":"array","items":{"type":"string","format":"email"},"description":"Addresses that receive invoice emails. Low-balance and payment-failure notices go to the organization owners. Maximum 10 addresses."},"security":{"type":"array","items":{"type":"string","format":"email"},"description":"Addresses for security notifications (API key events, new device logins, password resets). Maximum 10 addresses."}}},"Organization":{"allOf":[{"type":"object","required":["id","name","region","notification_emails","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization ID."},"region":{"$ref":"#/components/schemas/Region","readOnly":true,"description":"The data region this organization is hosted in."},"name":{"type":"string","minLength":1,"description":"The organization's display name.","example":"Acme Corp"},"notification_emails":{"$ref":"#/components/schemas/NotificationEmails"},"deleted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp when the organization was deleted, or `null` if active."},"logo_url":{"type":["string","null"],"format":"uri","readOnly":true,"description":"HTTPS URL to the current organization logo. `null` when unset."}}},{"$ref":"#/components/schemas/Timestamps"}]},"OrganizationUpdate":{"type":"object","additionalProperties":false,"properties":{"name":{"$ref":"#/components/schemas/TenantName","example":"Acme Inc"},"notification_emails":{"$ref":"#/components/schemas/NotificationEmails"}}},"OrganizationClosureCancelRequest":{"type":"object","additionalProperties":false,"required":["token"],"properties":{"token":{"$ref":"#/components/schemas/ClosureCancelToken","description":"The cancel token from the closure-scheduled email."}}},"Address":{"type":"object","additionalProperties":false,"description":"A postal address.","required":["street","city","postal_code","country_code"],"properties":{"street":{"type":"string","minLength":1,"description":"Street address including house or unit number."},"city":{"type":"string","minLength":1,"description":"City or town."},"postal_code":{"type":"string","minLength":1,"description":"Postal or ZIP code."},"state":{"type":"string","description":"State, province, or region. Optional."},"country_code":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code (uppercase)."}}},"ContactInformation":{"type":"object","additionalProperties":false,"description":"Primary contact details for an organization.","properties":{"primary_phone":{"type":"string","description":"The organization's primary phone number, in E.164 format. Changing it once the `verified_phone` trust requirement is met reverts that requirement to unmet and cancels any verification code still in progress for the old number. See [List your organization's trust requirements](/docs/api/reference/list-trust-gates)."},"primary_email":{"type":"string","format":"email","description":"The organization's primary contact email address. Changing it once the `verified_email` trust requirement is met reverts that requirement to unmet and cancels any verification code still in progress for the old address. See [List your organization's trust requirements](/docs/api/reference/list-trust-gates)."},"website":{"type":"string","format":"uri","description":"The organization's public website."}}},"BusinessProfile":{"type":"object","additionalProperties":false,"description":"The organization's business profile. Returns default values\n(`name` and `legal_name` empty, `business_type` set to `unknown`) if none has been configured.\n","required":["business_type","contact_information"],"properties":{"name":{"type":"string","readOnly":true,"description":"Display name of the business. Empty if not configured."},"legal_name":{"type":"string","readOnly":true,"description":"Registered legal name of the business. Empty if not configured."},"business_type":{"type":"string","minLength":1,"enum":["unknown","public","private","personal","education","government","nonprofit"],"readOnly":true,"description":"The legal or organizational nature of the business."},"registration_address":{"$ref":"#/components/schemas/Address"},"billing_address":{"$ref":"#/components/schemas/Address"},"contact_information":{"$ref":"#/components/schemas/ContactInformation"},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the profile was first created. `null` if none is configured."},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the profile was last updated. `null` if none is configured."}}},"BusinessProfileUpdate":{"type":"object","additionalProperties":false,"description":"Request body for updating the business profile. Every field is optional;\nomitted fields are left unchanged. The registration address, billing address,\nand contact information are replaced as a whole object when supplied.\n","properties":{"name":{"type":"string","description":"Display name of the business."},"legal_name":{"type":"string","description":"Registered legal name of the business."},"business_type":{"type":"string","enum":["unknown","public","private","personal","education","government","nonprofit"],"description":"The legal or organizational nature of the business."},"registration_address":{"$ref":"#/components/schemas/Address"},"billing_address":{"description":"Billing address used on invoices. Send an address object to set or replace\nit, or `null` to remove it so invoices use the registration address instead.\n","oneOf":[{"$ref":"#/components/schemas/Address"},{"type":"null"}]},"contact_information":{"$ref":"#/components/schemas/ContactInformation"}}},"ComplianceIdentityID":{"type":"string","minLength":1,"pattern":"^cid_[0-9a-hjkmnp-tv-z]{26}$","example":"cid_01krdgeqcxet5s7t44vh8rt9mg"},"BusinessProfileCopyFromIdentityRequest":{"type":"object","description":"Names the compliance party whose details should be copied onto the business profile.\n","additionalProperties":false,"required":["identity_id"],"properties":{"identity_id":{"$ref":"#/components/schemas/ComplianceIdentityID","description":"The party to copy from. It may belong to any workspace in this organization.\n"}}},"Quota":{"type":"object","additionalProperties":false,"description":"A quota's effective limit and current usage.","required":["key","name","description","product_slug","limit","unit","usage","remaining","percent_used"],"properties":{"key":{"type":"string","readOnly":true,"minLength":1,"example":"email_domains_max"},"name":{"type":"string","readOnly":true,"minLength":1},"description":{"type":"string","minLength":1,"readOnly":true},"product_slug":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"The billing product this quota belongs to. `null` when the quota is not tied to a specific product."},"limit":{"type":"integer","format":"int64","readOnly":true,"description":"The effective limit currently in force."},"unit":{"type":"string","minLength":1,"readOnly":true,"description":"What the limit counts (for example, count, domains, emails).","example":"count"},"usage":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Current consumption counted against `limit` at request time. `null` when this quota has no usage counter and only enforces a ceiling.\n"},"remaining":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Headroom left under `limit`, floored at zero once usage reaches or passes it. `null` under the same condition as `usage`.\n"},"percent_used":{"type":["number","null"],"format":"double","readOnly":true,"description":"`usage` as a percentage of `limit`. A limit of zero reports 100 once any usage exists, 0 otherwise. `null` under the same condition as `usage`.\n"}}},"QuotaList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Quota"}}}},"RateLimit":{"type":"object","additionalProperties":false,"description":"An organization's effective rate and window for a rate-limit policy.","required":["group","name","description","limit","window_seconds"],"properties":{"group":{"type":"string","readOnly":true,"minLength":1,"example":"api_create","description":"Stable key of the rate-limit policy."},"name":{"type":"string","readOnly":true,"minLength":1},"description":{"type":"string","minLength":1,"readOnly":true},"limit":{"type":"integer","readOnly":true,"description":"Maximum policy units per rolling window. `email_send`, `sms_send`, and `whatsapp_send` count messages; `verify_send` counts verifications; `verify_check` counts attempts; `voice_call` counts calls; and `lookup` counts lookups. The remaining policies count requests.\n"},"window_seconds":{"type":"integer","readOnly":true,"description":"The rolling window length in seconds."}}},"RateLimitList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RateLimit"}}}},"WorkspaceList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"WorkspaceCreate":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"$ref":"#/components/schemas/TenantName","example":"Production"}}},"OrganizationWorkspaceUpdate":{"type":"object","additionalProperties":false,"properties":{"name":{"$ref":"#/components/schemas/TenantName","description":"New display name for the workspace, as it appears in the dashboard and in workspace lists. Must include at least one letter or number.\n","example":"Staging"}}},"APIKeyID":{"type":"string","minLength":1,"pattern":"^key_[0-9a-hjkmnp-tv-z]{26}$","example":"key_01krdgeqcxet5s7t44vh8rt9mg"},"ApiKeyScope":{"type":"object","additionalProperties":false,"required":["scope","level"],"properties":{"scope":{"type":"string","minLength":1,"enum":["emails","email_management","email_marketing","competitive","inbox_insights","domains","realtime","sms","sms_management","verify","whatsapp","whatsapp_management","amb","amb_management","voice","voice_management","mailbox","mailbox_management","assets","esim","esim_credentials","lookup","numbers","webhooks","preferences","workspace"],"description":"Workspace product area that the API key can access."},"level":{"type":"string","minLength":1,"enum":["read","write"],"description":"The permission level for this scope."}}},"ApiKey":{"type":"object","additionalProperties":false,"required":["id","workspace_id","name","key_prefix","fingerprint","scopes","created_at","last_used_on","revoked_at","expires_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/APIKeyID"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"name":{"type":"string","minLength":1,"example":"Email operations production key"},"key_prefix":{"type":"string","minLength":1,"readOnly":true,"description":"The first few characters of the key, shown so you can recognize it without revealing its full value.","example":"bk_us1_Ab3xKq9m"},"fingerprint":{"type":"string","minLength":1,"readOnly":true,"description":"A short, stable identifier derived from the key (12 hex characters). Use it to match a key without exposing its full value.","example":"a1b2c3d4e5f6"},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyScope"}},"token":{"type":"string","readOnly":true,"x-sensitive":true,"description":"The full API key. Returned only when the key is created and never shown again. Store it securely.","example":"bk_us1_Ab3xKq9mP2wR5tY8uI1oL4nJ"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"last_used_on":{"type":["string","null"],"format":"date","readOnly":true,"description":"Date (day precision) when the key was last used.","example":"2026-05-08"},"revoked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp when the key was revoked, or null if still active."},"expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the key stops working, or null if it never expires."},"rotated_from_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/APIKeyID"},{"type":"null"}],"description":"The key this key replaced, if it was created by rotation."},"replaced_by_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/APIKeyID"},{"type":"null"}],"description":"The key that replaced this one, set when this key was rotated."}}},"ApiKeyList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"ApiKeyCreate":{"type":"object","additionalProperties":false,"required":["name","scopes"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"A label for the key, shown wherever it is listed. Names do not have to be unique, and the name has no effect on what the key can do.","example":"Email operations production key"},"scopes":{"type":"array","minItems":1,"description":"What the key may do, as scope and level pairs. A key can never carry the scopes that issue keys or change members, settings, or IP pools.","items":{"$ref":"#/components/schemas/ApiKeyScope"}},"expires_at":{"type":"string","format":"date-time","description":"When the key stops working, as an RFC 3339 timestamp. Omit for a key that never expires. Must be in the future. To change the expiry after creation, create a new key.","example":"2027-01-01T00:00:00Z"},"cidrs":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"Restrict this key to these source IP ranges (CIDR). Requests from other IPs are rejected. The key and its IP restriction are created atomically, so an invalid CIDR fails the whole request. Omit for a key with no IP restriction; use the IP restrictions endpoints to add one later.","example":["203.0.113.0/24","198.51.100.14/32"]}}},"ApiKeyUpdate":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"A new name for this key. Leave it out to keep the current name."},"scopes":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/ApiKeyScope"},"description":"The complete set of permissions this key should have. Any permission you leave out is removed, so send the full set rather than only what changed. Leave the field out entirely to keep the current permissions. You can only grant permissions you hold yourself.\n"}}},"RealtimeRegion":{"type":"object","additionalProperties":false,"required":["id"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/Region"}],"description":"Region identifier to pass as a Realtime app's region."}}},"RealtimeRegionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The regions a Realtime app can be created in.","items":{"$ref":"#/components/schemas/RealtimeRegion"}}}},"RealtimeAppSortField":{"type":"string","default":"created_at","description":"Field to sort Realtime apps by.\n","enum":["created_at","name"]},"RealtimeAppID":{"type":"string","minLength":1,"pattern":"^rap_[0-9a-hjkmnp-tv-z]{26}$","example":"rap_01krdgeqcxet5s7t44vh8rt9mg"},"RealtimeApp":{"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","app_id","name","region","client_events","connection_counting","connection_count_events","watchlist_events","authorized_connections","status"],"properties":{"client_events":{"type":"boolean","description":"Allow clients to trigger events directly (client events)."},"connection_counting":{"type":"boolean","description":"Count the connections subscribed to each channel and expose the count on channel queries."},"connection_count_events":{"type":"boolean","description":"Broadcast a connection-count event to a channel's subscribers whenever its connection count changes. Requires `connection_counting`."},"watchlist_events":{"type":"boolean","description":"Tell a signed-in member when the members on their watchlist come online or go offline. The watchlist is part of the identity your backend signs at signin."},"authorized_connections":{"type":"boolean","description":"Require every connection to be authorized."},"id":{"readOnly":true,"$ref":"#/components/schemas/RealtimeAppID"},"app_id":{"type":"integer","format":"int64","readOnly":true,"description":"The numeric Realtime app id. Use it together with a key and secret to initialize a Realtime client/server SDK. Immutable.","example":432557},"name":{"type":"string","minLength":1,"description":"A label for the app, shown wherever it is listed.","example":"chat-production"},"region":{"allOf":[{"$ref":"#/components/schemas/Region"}],"description":"The region this app runs in. Unlike other products, a Realtime app can be placed in a region other than the workspace's home region. Immutable after creation."},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["active","suspended"],"description":"Lifecycle state of the app. `active` apps serve connections; `suspended` apps are provisioned but reject connections."}}}]},"RealtimeAppList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RealtimeApp"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"RealtimeAppConfig":{"type":"object","description":"Realtime app configuration flags. TLS is always enforced (non-TLS client connections are rejected) and is not configurable.","properties":{"client_events":{"type":"boolean","description":"Allow clients to trigger events directly (client events)."},"connection_counting":{"type":"boolean","description":"Count the connections subscribed to each channel and expose the count on channel queries."},"connection_count_events":{"type":"boolean","description":"Broadcast a connection-count event to a channel's subscribers whenever its connection count changes. Requires `connection_counting`."},"watchlist_events":{"type":"boolean","description":"Tell a signed-in member when the members on their watchlist come online or go offline. The watchlist is part of the identity your backend signs at signin."},"authorized_connections":{"type":"boolean","description":"Require every connection to be authorized."}}},"RealtimeAppCreate":{"example":{"name":"chat-production","region":"eu1","connection_counting":true},"allOf":[{"$ref":"#/components/schemas/RealtimeAppConfig"},{"type":"object","additionalProperties":false,"required":["name","region"],"properties":{"name":{"type":"string","description":"A label for the app, shown wherever it is listed.","minLength":1,"maxLength":100,"example":"chat-production"},"region":{"allOf":[{"$ref":"#/components/schemas/Region"}],"description":"The region this app runs in. Unlike other products, a Realtime app can be placed in a region other than the workspace's home region. Immutable after creation."},"client_events":{"type":"boolean","default":false,"description":"Allow clients to trigger events directly (client events)."},"connection_counting":{"type":"boolean","default":false,"description":"Count the connections subscribed to each channel and expose the count on channel queries."},"connection_count_events":{"type":"boolean","default":false,"description":"Broadcast a connection-count event to a channel's subscribers whenever its connection count changes. Requires `connection_counting`."},"watchlist_events":{"type":"boolean","default":false,"description":"Tell a signed-in member when the members on their watchlist come online or go offline. The watchlist is part of the identity your backend signs at signin."},"authorized_connections":{"type":"boolean","default":false,"description":"Require every connection to be authorized."}}}]},"RealtimeAppKeyID":{"type":"string","minLength":1,"pattern":"^rak_[0-9a-hjkmnp-tv-z]{26}$","example":"rak_01krdgeqcxet5s7t44vh8rt9mg"},"RealtimeAppKey":{"type":"object","additionalProperties":false,"required":["id","key","revoked_at","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/RealtimeAppKeyID"},"key":{"type":"string","minLength":1,"readOnly":true,"description":"The public app key clients use to connect.","example":"d0e95a856ddc1b09d4c8"},"secret":{"type":"string","readOnly":true,"x-sensitive":true,"description":"The key secret, used for server-side request signing. Returned only when the key is created, and never shown again: store it securely. If lost, create a new key and revoke this one.","example":"862925e1991a8b6902f9"},"revoked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the key was revoked, or `null` if still active."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"RealtimeAppCreated":{"allOf":[{"$ref":"#/components/schemas/RealtimeApp"},{"type":"object","required":["key"],"properties":{"key":{"readOnly":true,"description":"The app's initial key, including its one-time secret. Present in this create response only; the secret is never returned again.","allOf":[{"$ref":"#/components/schemas/RealtimeAppKey"}]}}}]},"RealtimeUsageMetric":{"type":"object","additionalProperties":false,"description":"A usage metric over the selected window and the immediately preceding window of equal length. Values use the `double` format because `average_connections` can be fractional. Count metrics such as `messages` and `peak_connections` can therefore appear here as `1234.0` and in a series as `1234`.","required":["current"],"properties":{"current":{"type":"number","format":"double","readOnly":true,"description":"Metric value over the selected window."},"previous":{"type":"number","format":"double","readOnly":true,"description":"Same metric over the preceding window of equal length. Omitted when prior data is unavailable."}}},"RealtimeUsageSummary":{"type":"object","additionalProperties":false,"required":["peak_connections","average_connections","messages"],"properties":{"peak_connections":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/RealtimeUsageMetric"}]},"average_connections":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/RealtimeUsageMetric"}]},"messages":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/RealtimeUsageMetric"}]}}},"RealtimeUsageDailyPoint":{"type":"object","additionalProperties":false,"description":"Usage for one UTC day. peak/average connections are over that day's buckets; messages is that day's total.","required":["date","peak_connections","average_connections","messages"],"properties":{"date":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"UTC day, `YYYY-MM-DD`."},"peak_connections":{"type":"integer","format":"int64","readOnly":true},"average_connections":{"type":"number","format":"double","readOnly":true},"messages":{"type":"integer","format":"int64","readOnly":true}}},"RealtimeUsageAppRow":{"type":"object","additionalProperties":false,"description":"Per-app usage over the window.","required":["realtime_app_id","name","region","messages","peak_connections","share"],"properties":{"realtime_app_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/RealtimeAppID"}]},"name":{"type":"string","minLength":1,"readOnly":true},"region":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Region"}]},"messages":{"type":"integer","format":"int64","readOnly":true},"peak_connections":{"type":"integer","format":"int64","readOnly":true},"share":{"type":"number","format":"double","readOnly":true,"description":"This app's share of total messages over the window, 0..1."}}},"RealtimeUsage":{"type":"object","additionalProperties":false,"description":"Realtime usage for a workspace over a time window, including summary metrics, a daily series, and a per-app breakdown. For display only; not a billing source.","required":["summary","series","by_app"],"properties":{"summary":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/RealtimeUsageSummary"}]},"series":{"type":"array","readOnly":true,"description":"One point per UTC day in the window, ascending.","items":{"$ref":"#/components/schemas/RealtimeUsageDailyPoint"}},"by_app":{"type":"array","readOnly":true,"description":"Per-app breakdown over the window, ordered by messages descending.","items":{"$ref":"#/components/schemas/RealtimeUsageAppRow"}},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The approximate time through which this response includes usage. More recent usage may not be reflected yet. `null` when the window has no data.","example":"2026-07-22T14:03:10Z"}}},"RealtimeAppUpdate":{"example":{"name":"chat-production","connection_counting":true},"description":"Mutable Realtime app fields. Omitted fields are left unchanged. Region is immutable and TLS is always enforced, so neither appears here.","type":"object","additionalProperties":false,"properties":{"name":{"type":"string","description":"A label for the app, shown wherever it is listed.","minLength":1,"maxLength":100,"example":"chat-production"},"client_events":{"type":"boolean","description":"Allow clients to trigger events directly (client events)."},"connection_counting":{"type":"boolean","description":"Count the connections subscribed to each channel and expose the count on channel queries."},"connection_count_events":{"type":"boolean","description":"Broadcast a connection-count event to a channel's subscribers whenever its connection count changes. Requires `connection_counting`."},"watchlist_events":{"type":"boolean","description":"Tell a signed-in member when the members on their watchlist come online or go offline. The watchlist is part of the identity your backend signs at signin."},"authorized_connections":{"type":"boolean","description":"Require every connection to be authorized."}}},"RealtimeEventName":{"type":"string","minLength":1,"maxLength":200,"description":"The event name clients bind to. Application event names are free-form; the `bird:` and `bird_internal:` prefixes are reserved for the protocol and rejected.","example":"order-updated"},"RealtimeChannelName":{"type":"string","minLength":1,"maxLength":164,"pattern":"^[A-Za-z0-9_=@,.;-]+$","description":"A Realtime channel name. Only letters, digits, and _ - = @ , . ; Prefix with `private-` or `presence-` for authenticated channels, or `private-encrypted-` for channels whose payloads are end-to-end encrypted with a key only you hold.","example":"orders-42"},"RealtimeEventData":{"description":"Arbitrary JSON payload delivered as the event data: an object, array, or scalar. Cap: 10 KB serialized."},"RealtimeExcludeConnectionId":{"type":"string","minLength":1,"example":"123.4567","description":"Exclude this connection from delivery, to avoid echoing a change back to the client that triggered it. The value is the client's connection id, assigned when its connection is established."},"RealtimeChannelInclude":{"type":"string","enum":["member_count","connection_count"],"description":"A per-channel attribute to include in the response. `member_count` is presence-channels only; `connection_count` requires the app's connection-counting flag."},"RealtimePublish":{"type":"object","additionalProperties":false,"description":"A Realtime publish: delivers one event to one or more channels of the app. Listing several channels fans the event out to all of them (broadcast) in a single call.\n","required":["event","channels"],"properties":{"event":{"$ref":"#/components/schemas/RealtimeEventName"},"channels":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/RealtimeChannelName"},"description":"The channels to deliver the event to (up to 100 per call). Prefix with `private-` or `presence-` for authenticated channels. A `private-encrypted-` channel must be the only channel in its publish: each encrypted channel has its own key, so a fan-out would hand the other channels unreadable ciphertext.\n","example":["orders","orders-42"]},"data":{"$ref":"#/components/schemas/RealtimeEventData"},"exclude_connection_id":{"$ref":"#/components/schemas/RealtimeExcludeConnectionId"},"include":{"type":"array","items":{"$ref":"#/components/schemas/RealtimeChannelInclude"},"description":"Per-channel attributes to return alongside the publish, reflecting each channel's state at publish time. `member_count` is available only for presence channels. `connection_count` requires the app's connection-counting flag. Requesting attributes counts as one additional message toward usage."}}},"RealtimeChannelCounts":{"type":"object","description":"Per-channel counts, present only when requested via `include` and applicable.","properties":{"member_count":{"type":"integer","format":"int64","description":"Distinct members (presence channels only; requires `include=member_count`)."},"connection_count":{"type":"integer","format":"int64","description":"Connections currently subscribed to this channel (requires `include=connection_count` and the app's connection-counting flag). Channel-scoped: distinct from the app-wide peak connections metric."}}},"RealtimeChannelListItem":{"allOf":[{"$ref":"#/components/schemas/RealtimeChannelCounts"},{"type":"object","additionalProperties":false,"description":"One occupied channel. A listed channel is occupied by definition; counts appear only when requested via `include` and applicable.","required":["name"],"properties":{"name":{"$ref":"#/components/schemas/RealtimeChannelName"}}}]},"RealtimePublishResult":{"type":"object","additionalProperties":false,"description":"The result of a Realtime publish. The event was accepted and fanned out to the requested channels; delivery to connected clients is asynchronous.\n","properties":{"data":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/RealtimeChannelListItem"},"description":"Per-channel attributes at publish time, present only when the request asked for them via `include`; one item per distinct target channel, sorted by name."}}},"RealtimeBatchEvent":{"type":"object","additionalProperties":false,"description":"A single event published to one channel as part of a batch.","required":["event","channel"],"properties":{"event":{"$ref":"#/components/schemas/RealtimeEventName"},"channel":{"$ref":"#/components/schemas/RealtimeChannelName"},"data":{"$ref":"#/components/schemas/RealtimeEventData"},"exclude_connection_id":{"$ref":"#/components/schemas/RealtimeExcludeConnectionId"},"include":{"type":"array","items":{"$ref":"#/components/schemas/RealtimeChannelInclude"},"description":"Attributes of this event's channel to return alongside the publish (same semantics and validation errors as on the channel endpoints). Requesting attributes counts as one additional message toward usage."}}},"RealtimeBatchPublish":{"type":"object","additionalProperties":false,"description":"A batch of events, each delivered to a single channel, in one request.","required":["events"],"properties":{"events":{"type":"array","minItems":1,"maxItems":10,"description":"Up to 10 events per batch.","items":{"$ref":"#/components/schemas/RealtimeBatchEvent"}}}},"RealtimeBatchPublishResultItem":{"allOf":[{"$ref":"#/components/schemas/RealtimeChannelCounts"},{"type":"object","additionalProperties":false,"description":"Attributes of one batch item's channel at publish time. Items are positional: the n-th item corresponds to the n-th event in the request.","required":["channel"],"properties":{"channel":{"$ref":"#/components/schemas/RealtimeChannelName"}}}]},"RealtimeBatchPublishResult":{"type":"object","additionalProperties":false,"description":"The result of a Realtime batch publish. The events were accepted for delivery; delivery to connected clients is asynchronous.\n","properties":{"data":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/RealtimeBatchPublishResultItem"},"description":"Per-event channel attributes at publish time, present only when at least one event asked for them via `include`. Positional: one item per event, in request order."}}},"RealtimeChannelsList":{"type":"object","additionalProperties":false,"description":"The app's occupied channels. The Realtime service does not paginate this listing, so all occupied channels are returned in one response.","required":["data"],"properties":{"data":{"type":"array","description":"The occupied channels, sorted by name.","items":{"$ref":"#/components/schemas/RealtimeChannelListItem"}}}},"RealtimeChannelInfo":{"allOf":[{"$ref":"#/components/schemas/RealtimeChannelCounts"},{"type":"object","additionalProperties":false,"description":"State of a single channel. Counts appear only when requested and applicable.","required":["occupied"],"properties":{"occupied":{"type":"boolean","description":"Whether at least one client is subscribed."}}}]},"RealtimeMemberID":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9._~!$&'()*+,;=:@-]+$","description":"An app-defined member ID for your application's end user, assigned when your auth server authorizes them. Use up to 128 URL-safe characters because member IDs appear directly in API request paths. The value can include `+ : @ . _ -`, but not `/ ? # %` or whitespace."},"RealtimeChannelMember":{"type":"object","additionalProperties":false,"description":"A member present on a presence channel.","required":["member_id"],"properties":{"member_id":{"$ref":"#/components/schemas/RealtimeMemberID"}}},"RealtimeChannelMembers":{"type":"object","additionalProperties":false,"description":"The members present on a presence channel.","required":["members"],"properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/RealtimeChannelMember"}}}},"RealtimeMemberPublish":{"type":"object","additionalProperties":false,"description":"An event addressed to one member rather than to a channel. Every connection that member currently holds receives it; if they hold none, the event is dropped.","required":["event"],"properties":{"event":{"$ref":"#/components/schemas/RealtimeEventName"},"data":{"$ref":"#/components/schemas/RealtimeEventData"}}},"RealtimeAppKeyList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The app's keys, oldest first. Revoked keys are excluded unless `include_revoked`=`true`.","items":{"$ref":"#/components/schemas/RealtimeAppKey"}}}},"ApiKeyRotate":{"type":"object","additionalProperties":false,"description":"Options for rotating an API key.","properties":{"grace_period":{"type":"string","default":"24h","description":"How long the previous key keeps working after rotation, as a duration\nstring, such as `24h` or `30m`. Defaults to `24h` when omitted. Use `0` to\nrevoke the previous key immediately instead of granting a grace period.\nIf the previous key was already set to expire sooner than the requested\ngrace period, its original expiry is kept. Rotating never extends a\nkey's lifetime.\n","example":"24h"},"scopes":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/ApiKeyScope"},"description":"Permissions for the replacement key. Leave it out to carry the current key's permissions over unchanged. Use this to add a permission that can only be granted when a key is issued, such as voice.\n"}}},"ApiKeyIpRestrictions":{"type":"object","additionalProperties":false,"description":"The API key's current IP allowlist configuration.","required":["cidrs","updated_at"],"properties":{"cidrs":{"type":"array","readOnly":true,"items":{"type":"string","minLength":1},"description":"Allowed source IP ranges in CIDR notation. Empty when the key has no IP allowlist configured.","example":["203.0.113.0/24","198.51.100.14/32"]},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the IP allowlist was last set. Null when the key has no IP allowlist configured.","example":"2026-07-22T09:15:00Z"}}},"ApiKeyIpRestrictionsUpdate":{"type":"object","additionalProperties":false,"description":"Replaces the API key's IP allowlist. Requests from any address outside the listed ranges are denied.","required":["cidrs"],"properties":{"cidrs":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"Allowed source IP ranges in CIDR notation, such as `203.0.113.0/24`. Replaces the full list on every call.","example":["203.0.113.0/24","198.51.100.14/32"]}}},"ServiceAccountID":{"type":"string","minLength":1,"pattern":"^bsa_[0-9a-hjkmnp-tv-z]{26}$","example":"bsa_01krdgeqcxet5s7t44vh8rt9mg"},"ServiceAccountStatus":{"type":"string","minLength":1,"enum":["active","disconnected","revoked"],"description":"Lifecycle of the integration.\n\n- `active` (the initial status) means the integration holds at least one live connection. A connection whose access lapsed can take up to a day to leave the live set.\n- `disconnected` means the integration has no live connections and holds no access until the platform is reconnected. Derived from the connections rather than stored.\n- `revoked` means the integration was revoked from this workspace. Final: reconnecting from the platform creates a new integration.\n","example":"active"},"ServiceAccountConnectionStatus":{"type":"string","minLength":1,"enum":["active","revoked"],"description":"Lifecycle of the connection. `active` (the initial status) means the connection holds a live approval. `revoked` means the connection was revoked or its access lapsed; a lapsed connection can take up to a day to show as `revoked`.\n","example":"active"},"ServiceAccountConnection":{"type":"object","additionalProperties":false,"description":"One connection of an integration: a grant of access approved from the platform's consent flow, with the scopes it currently carries.\n","required":["grant_id","granted_at","status","scopes"],"properties":{"grant_id":{"$ref":"#/components/schemas/OAuthGrantID","readOnly":true,"description":"Unique identifier of the underlying grant."},"granted_by":{"readOnly":true,"description":"The user who approved the connection. `null` once that user is deleted; the connection itself stays valid.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"granted_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the connection was approved.","example":"2026-08-01T09:14:52Z"},"last_used_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the connection last obtained an access token. `null` if never used.","example":"2026-08-20T08:30:00Z"},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/ServiceAccountConnectionStatus"}]},"scopes":{"type":"array","readOnly":true,"description":"The effective scopes of the connection: what was approved, narrowed to what the platform may currently request. Tokens carry exactly these scopes; a connection whose effective set is empty cannot obtain tokens at all.\n","items":{"$ref":"#/components/schemas/OAuthScopeGrant"}}}},"ServiceAccount":{"type":"object","additionalProperties":false,"description":"A platform integration connected to the workspace: a machine identity that acts through the connections listed on it, independent of the user who set it up.\n","required":["id","name","status","connections","created_at"],"properties":{"id":{"$ref":"#/components/schemas/ServiceAccountID","readOnly":true,"description":"Unique identifier of the integration."},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the platform, recorded when the integration was first connected.","example":"Zapier"},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/ServiceAccountStatus"}]},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the integration was first connected.","example":"2026-08-01T09:14:52Z"},"last_used_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the integration last obtained an access token, across all of its connections. Accurate to about an hour for an active connection, matching the access token lifetime. `null` if never used.\n","example":"2026-08-20T08:30:00Z"},"connections":{"type":"array","readOnly":true,"description":"The integration's connections, one per approval, including revoked ones.","items":{"$ref":"#/components/schemas/ServiceAccountConnection"}}}},"ServiceAccountList":{"type":"object","additionalProperties":false,"description":"The workspace's platform integrations.","required":["data"],"properties":{"data":{"type":"array","description":"The workspace's integrations, including revoked ones, newest first.","items":{"$ref":"#/components/schemas/ServiceAccount"}}}},"MemberUser":{"type":"object","additionalProperties":false,"description":"A user's basic profile, as shown alongside their membership in a workspace.","required":["id","email"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/UserID","description":"User ID."},"email":{"type":"string","format":"email","minLength":5,"readOnly":true,"description":"The user's email address.","example":"alice@acme.com"},"full_name":{"type":["string","null"],"readOnly":true,"minLength":1,"maxLength":100,"description":"The user's full name. `null` when unset.","example":"Alice Liddell"},"avatar_url":{"type":["string","null"],"format":"uri","readOnly":true,"description":"HTTPS URL to the user's avatar. `null` when unset."}}},"WorkspaceRole":{"type":"object","additionalProperties":false,"required":["workspace_id","workspace_name","role"],"properties":{"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"workspace_name":{"type":"string","minLength":1,"example":"Production"},"role":{"$ref":"#/components/schemas/WorkspaceRoleEnum.gen"}}},"OrganizationMember":{"type":"object","additionalProperties":false,"required":["user","org_role","workspace_roles","joined_at"],"properties":{"user":{"$ref":"#/components/schemas/MemberUser","description":"The user this organization membership belongs to."},"org_role":{"oneOf":[{"$ref":"#/components/schemas/OrgRoleEnum.gen"},{"type":"null"}],"description":"The user's org-level role. `null` if the user has only workspace roles."},"workspace_roles":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceRole"},"description":"Workspace role grants this member currently holds."},"joined_at":{"type":"string","minLength":1,"readOnly":true,"format":"date-time","description":"When the user joined this organization, based on the earliest date a role was assigned to them here."},"last_login_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the user last signed in. The timestamp covers every organization the user belongs to. `null` if the user has never signed in.","example":"2026-05-07T14:30:00Z"},"mfa_enabled":{"type":"boolean","readOnly":true,"description":"Whether the user has multi-factor authentication enabled for their account. The setting applies wherever the user signs in.","example":false}}},"OrganizationMemberList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationMember"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"OrganizationMemberUpdate":{"type":"object","additionalProperties":false,"required":["org_role"],"properties":{"org_role":{"oneOf":[{"$ref":"#/components/schemas/OrgRoleEnum.gen"},{"type":"null"}],"description":"Org-level role to set. Pass `null` to remove the org role while retaining workspace roles."}}},"Member":{"type":"object","additionalProperties":false,"description":"A user's access to this workspace, granted directly or inherited from organization ownership.","required":["user","role","granted_at"],"properties":{"user":{"$ref":"#/components/schemas/MemberUser","description":"The user this membership belongs to."},"role":{"type":"string","minLength":1,"enum":["owner","admin","developer","analyst"],"description":"The user's role in this workspace:\n\n- `admin`: full read and write access, including managing members.\n- `developer`: read and write access to messaging and sending\n  features; read-only on members and workspace settings.\n- `analyst`: read-only access to the workspace.\n- `owner`: the user has no explicit grant on this workspace and\n  reaches it as an organization owner instead, which gives every\n  workspace's full access. An explicit workspace grant, if the user\n  also holds one, is reported in its place.\n"},"granted_at":{"type":"string","minLength":1,"readOnly":true,"format":"date-time","description":"When the role was granted. For the `owner` bypass, when organization ownership was granted."}}},"MemberList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Member"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"MemberAdd":{"type":"object","additionalProperties":false,"description":"Parameters for granting an existing organization member a role on this workspace.","required":["user_id","role"],"properties":{"user_id":{"$ref":"#/components/schemas/UserID","description":"ID of an existing organization member to add to this workspace."},"role":{"type":"string","minLength":1,"enum":["admin","developer","analyst"],"description":"Workspace role to assign:\n\n- `admin`: full read and write access, including managing members.\n- `developer`: read and write access to messaging and sending\n  features; read-only on members and workspace settings.\n- `analyst`: read-only access to the workspace.\n"}}},"MemberUpdate":{"type":"object","additionalProperties":false,"description":"Fields to update on a workspace membership.","required":["role"],"properties":{"role":{"type":"string","minLength":1,"enum":["admin","developer","analyst"],"description":"Workspace role to assign:\n\n- `admin`: full read and write access, including managing members.\n- `developer`: read and write access to messaging and sending\n  features; read-only on members and workspace settings.\n- `analyst`: read-only access to the workspace.\n"}}},"Invitation":{"type":"object","additionalProperties":false,"description":"An invitation for someone to join a workspace, sent by email.","required":["id","workspace_id","email","role","invited_by","status","expires_at","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/InvitationID","description":"Invitation ID."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace the invitation grants access to."},"email":{"type":"string","format":"email","minLength":5,"description":"Address the invitation was sent to.","example":"bob@acme.com"},"role":{"type":"string","minLength":1,"readOnly":true,"enum":["admin","developer","analyst"],"description":"Workspace role to be granted on acceptance."},"invited_by":{"readOnly":true,"$ref":"#/components/schemas/UserID","description":"User ID of the person who created this invitation."},"status":{"type":"string","readOnly":true,"minLength":1,"enum":["pending","accepted","revoked","expired"],"description":"Computed status of the invitation:\n\n- `pending`: not yet accepted, revoked, or expired.\n- `accepted`: the recipient accepted it.\n- `revoked`: canceled before it was accepted or expired.\n- `expired`: `expires_at` passed unaccepted.\n"},"expires_at":{"type":"string","readOnly":true,"minLength":1,"format":"date-time","description":"When the invitation expires."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the invitation was created."}}},"InvitationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Invitation"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"MemberCreate":{"type":"object","additionalProperties":false,"description":"Parameters for adding a member to this workspace by email.","required":["email","role"],"properties":{"email":{"type":"string","format":"email","minLength":5,"maxLength":254,"description":"Email address of the user to add. If the user is already an org member, they are added directly. Otherwise an invitation is sent.","example":"alice@acme.com"},"role":{"type":"string","minLength":1,"enum":["admin","developer","analyst"],"description":"Workspace role to assign:\n\n- `admin`: full read and write access, including managing members.\n- `developer`: read and write access to messaging and sending\n  features; read-only on members and workspace settings.\n- `analyst`: read-only access to the workspace.\n"}}},"SmartInviteTeamMember":{"type":"object","additionalProperties":false,"required":["type","user_id","email","role","granted_at"],"properties":{"type":{"type":"string","minLength":1,"enum":["team_member"]},"user_id":{"$ref":"#/components/schemas/UserID"},"email":{"type":"string","format":"email","minLength":5,"example":"alice@acme.com"},"role":{"type":"string","minLength":1,"enum":["admin","developer","analyst"],"description":"The workspace role granted to the member."},"granted_at":{"type":"string","minLength":1,"format":"date-time"}}},"SmartInviteInvitation":{"type":"object","additionalProperties":false,"required":["type","id","email","status","role","expires_at"],"properties":{"type":{"type":"string","minLength":1,"enum":["invitation"]},"id":{"$ref":"#/components/schemas/InvitationID"},"email":{"type":"string","format":"email","minLength":5,"example":"bob@acme.com"},"status":{"type":"string","minLength":1,"enum":["pending","accepted","revoked","expired"],"description":"Computed status of the invitation:\n\n- `pending`: not yet accepted, revoked, or expired.\n- `accepted`: the recipient accepted it.\n- `revoked`: canceled before it was accepted or expired.\n- `expired`: `expires_at` passed unaccepted.\n"},"role":{"type":"string","minLength":1,"enum":["admin","developer","analyst"],"description":"Workspace role to be granted on acceptance."},"expires_at":{"type":"string","minLength":1,"format":"date-time"}}},"SmartInviteResponse":{"description":"The member added directly, or the invitation created when no matching member exists. `type` tells them apart.","oneOf":[{"$ref":"#/components/schemas/SmartInviteTeamMember"},{"$ref":"#/components/schemas/SmartInviteInvitation"}],"discriminator":{"propertyName":"type","mapping":{"team_member":"#/components/schemas/SmartInviteTeamMember","invitation":"#/components/schemas/SmartInviteInvitation"}}},"OrganizationInvitationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationInvitation"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"OrganizationInvitationCreate":{"type":"object","additionalProperties":false,"required":["email"],"properties":{"email":{"type":"string","format":"email","minLength":5,"maxLength":254,"description":"Email address to invite. Normalized to lowercase.","example":"bob@acme.com"},"org_role":{"$ref":"#/components/schemas/OrgRoleEnum.gen","description":"Org-level role to assign on acceptance. Omit to invite with workspace roles only; at least one of `org_role` or `workspace_roles` must be provided."},"workspace_roles":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationInvitationWorkspaceRole"},"description":"Workspace role assignments to grant on acceptance."}}},"EmailMessageStatus":{"type":"string","minLength":1,"enum":["scheduled","accepted","processed","deferred","delivered","partial_failure","bounced","complained","rejected","canceled"],"description":"Aggregate delivery status of an email, derived from its recipients' states.\n\nIn flight:\n\n- `scheduled`: The message is queued to send at a future time and has not been dispatched yet.\n- `accepted`: The initial status of an immediate send. The message is queued for its recipients.\n- `processed`: Delivery is underway, so at least one recipient's message is on its way out and none has failed.\n- `deferred`: At least one recipient's mailbox provider asked for a retry, and delivery attempts continue.\n\nFinal:\n\n- `delivered`: Every recipient's mail server accepted the message.\n- `bounced`: Every recipient permanently failed (bounced or was rejected).\n- `rejected`: Every recipient was rejected before a delivery attempt (for example, all recipients were suppressed).\n- `partial_failure`: Some recipients permanently failed while others were delivered or are still in flight.\n- `canceled`: A scheduled message was canceled before it was sent.\n\n`complained` takes precedence over every other status: at least one recipient reported\nthe message as spam, regardless of what happened to the rest.\n"},"EmailMessageCategory":{"type":"string","minLength":1,"enum":["marketing","transactional"],"description":"Content classification, which controls suppression policy:\n\n- `marketing`: Blocks on all suppression reasons.\n- `transactional`: Allows delivery through complaint and unsubscribe suppressions, for receipts, password resets, and similar operational mail.\n"},"EmailID":{"type":"string","minLength":1,"pattern":"^em_[0-9a-hjkmnp-tv-z]{26}$","example":"em_01krdgeqcxet5s7t44vh8rt9mg"},"EmailAddress":{"type":"object","additionalProperties":false,"description":"An email address with an optional display name.","required":["email"],"properties":{"email":{"type":"string","format":"email","minLength":5,"description":"Email address.","example":"jane@acme.com"},"name":{"type":"string","minLength":1,"maxLength":256,"pattern":"^[^\\r\\n]+$","description":"Display name shown alongside the address in mail clients.","example":"Jane Doe"}}},"LanguageTag":{"type":"string","minLength":2,"maxLength":35,"description":"A language tag in BCP-47 form, for example `en` or `pt-BR`.","example":"pt-BR"},"EmailTemplateID":{"type":"string","minLength":1,"pattern":"^emt_[0-9a-hjkmnp-tv-z]{26}$","example":"emt_01krdgeqcxet5s7t44vh8rt9mg"},"EmailTemplateVersionID":{"type":"string","minLength":1,"pattern":"^emv_[0-9a-hjkmnp-tv-z]{26}$","example":"emv_01krdgeqcxet5s7t44vh8rt9mg"},"EmailBroadcastID":{"type":"string","minLength":1,"pattern":"^eb_[0-9a-hjkmnp-tv-z]{26}$","example":"eb_01krdgeqcxet5s7t44vh8rt9mg"},"Tag":{"type":"object","additionalProperties":false,"required":["name","value"],"description":"Structured key/value label attached to a message or a call. Use tags for low-cardinality filtering dimensions (category, experiment ID, template ID); they surface in the list filter of whatever carries them.\n\nOn a message they also surface in the event log and in webhook payloads, and a message can carry `metadata` beside them for arbitrary per-send context that does not need to be filterable. A call has none of those three: its tags are set on the wire when the call is placed, and the call record is the one place you read them back.\n\nWhatever carries the tags defines how many it may have. Tag names are unique: a send that repeats one is rejected, and on a call the first instance of a name wins.\n","properties":{"name":{"type":"string","minLength":1,"maxLength":32,"pattern":"^[A-Za-z0-9_-]+$","description":"Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.\n","example":"category"},"value":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$","description":"Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.\n","example":"welcome"}}},"EmailAttachmentID":{"type":"string","minLength":1,"pattern":"^ea_[0-9a-hjkmnp-tv-z]{26}$","example":"ea_01krdgeqcxet5s7t44vh8rt9mg"},"EmailAttachmentRef":{"type":"object","additionalProperties":false,"required":["filename","size"],"description":"Attachment metadata returned on API reads. Download the file during its retention window with `GET /v1/email/messages/{message_id}/attachments/{attachment_id}`.\n","properties":{"id":{"readOnly":true,"description":"Attachment ID, stable per email send.","$ref":"#/components/schemas/EmailAttachmentID"},"filename":{"type":"string","minLength":1,"description":"Filename as shown to the recipient.","example":"invoice.pdf"},"content_type":{"type":"string","description":"Resolved MIME type at send time.","example":"application/pdf"},"size":{"type":"integer","minimum":0,"description":"Decoded size in bytes.","example":215432},"inline":{"type":"boolean","description":"True when the attachment was sent inline via a `content_id` reference in the HTML body, false for regular file attachments.\n","example":false},"content_id":{"type":["string","null"],"description":"The Content-ID set at send time, when the attachment was inline."}}},"EmailMessage":{"type":"object","description":"An email message, including a recipient's copy of a broadcast. `broadcast_id` identifies the broadcast that sent the message and is absent for other sends. A broadcast records one message per recipient; these copies share the same `broadcast_id`.\n","additionalProperties":false,"required":["id","from","to","subject","category","status","accepted_count","processed_count","delivered_count","bounced_count","complained_count","deferred_count","rejected_count","open_count","click_count","track_opens","track_clicks","created_at"],"example":{"id":"em_01krdgeqcxet5s7t44vh8rt9mg","from":{"email":"onboarding@messagebird.dev","name":"Bird"},"to":[{"email":"delivered@messagebird.dev"}],"subject":"Hello from Bird","category":"marketing","status":"accepted","broadcast_id":"eb_01krdgeqcxet5s7t44vh8rt9mg","accepted_count":1,"processed_count":0,"delivered_count":0,"bounced_count":0,"complained_count":0,"deferred_count":0,"rejected_count":0,"open_count":0,"click_count":0,"track_opens":false,"track_clicks":false,"created_at":"2026-07-01T12:00:00Z"},"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/EmailID","description":"Message ID."},"from":{"$ref":"#/components/schemas/EmailAddress","description":"Sender address. `name` is present when a display name was provided on the send."},"to":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddress"},"minItems":1,"maxItems":50,"description":"Primary recipients. Length is the recipient count. Use the broadcasts endpoint for audience-targeted sends. Each entry's `name` is present when a display name was provided on the send."},"cc":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddress"},"description":"CC recipients."},"bcc":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddress"},"description":"BCC recipients."},"subject":{"type":"string","minLength":1,"description":"The subject line as delivered. For a send that used a template, the stored subject is the template's, so this reports it with the send's `parameters` substituted in, which is what the recipient saw.\n"},"category":{"$ref":"#/components/schemas/EmailMessageCategory"},"reply_to":{"type":["array","null"],"items":{"$ref":"#/components/schemas/EmailAddress"},"maxItems":25,"description":"Reply-To addresses, if set on the send. Empty/null when no Reply-To was provided."},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailMessageStatus"}]},"accepted_count":{"type":"integer","readOnly":true,"default":0,"description":"How many recipients are in the `accepted` state, meaning we have the message and are getting ready to deliver it."},"processed_count":{"type":"integer","readOnly":true,"default":0,"description":"How many recipients the message has been prepared for and queued for delivery."},"delivered_count":{"type":"integer","readOnly":true,"default":0,"description":"How many recipients' messages were accepted by their mail server."},"bounced_count":{"type":"integer","readOnly":true,"default":0,"description":"Number of recipients that resulted in a permanent delivery failure."},"complained_count":{"type":"integer","readOnly":true,"default":0,"description":"Number of recipients that reported spam."},"deferred_count":{"type":"integer","readOnly":true,"default":0,"description":"Number of recipients in transient delivery deferral. Their mail server asked for a retry, and delivery attempts continue."},"rejected_count":{"type":"integer","readOnly":true,"default":0,"description":"Number of recipients rejected before delivery. Read the per-recipient `rejection_reason` field on `GET /v1/email/messages/{message_id}/recipients` for the specific cause.\n"},"processing_latency_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Time between the send being accepted and the message being prepared for delivery, in milliseconds, for the fastest recipient. Null until the first recipient reaches `processed`.\n"},"delivery_latency_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Time between the message being processed and the receiving mail server accepting it, in milliseconds, for the fastest delivered recipient. Null until the first recipient is delivered.\n"},"total_latency_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"End-to-end accept → delivered time for the fastest delivered recipient, in milliseconds. Null until the first recipient is delivered.\n"},"open_count":{"type":"integer","readOnly":true,"default":0,"description":"Total open events across all recipients."},"click_count":{"type":"integer","readOnly":true,"default":0,"description":"Total click events across all recipients."},"requested_language":{"oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}],"readOnly":true,"description":"The template language this send asked for, in canonical form (`pt-BR` for a request of `pt-br`). Null when the send named no language (it took the template's default) or used no template at all. Compare it with `resolved_language`: when they differ, the language you asked for was not available and the template's `on_missing_language` policy chose the one shown there instead.\n"},"resolved_language":{"oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}],"readOnly":true,"description":"The template language this send was actually delivered in, in canonical form. Null when the send used no template. A non-null value with a null `requested_language` means the send named no language and took the template's default.\n"},"template_id":{"oneOf":[{"$ref":"#/components/schemas/EmailTemplateID"},{"type":"null"}],"readOnly":true,"description":"The template this send rendered from, or null for a send that supplied its content inline.\n"},"template_version_id":{"oneOf":[{"$ref":"#/components/schemas/EmailTemplateVersionID"},{"type":"null"}],"readOnly":true,"description":"The exact template version this send rendered from, or null for an inline send. A template's live version changes every time you submit it, so this is what identifies the wording that was actually delivered, together with `resolved_language`.\n"},"broadcast_id":{"$ref":"#/components/schemas/EmailBroadcastID","readOnly":true,"description":"The broadcast that sent this message. Absent for a send that was not part of a broadcast. A broadcast records one message per recipient, and every one of them carries the same value here.\n"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"description":"Labels on this message, each one a `name` and a `value`, that you can filter and search messages by. Use tags for anything you want to find messages by later, and `metadata` for data you only want handed back to you."},"metadata":{"type":"object","description":"Any JSON you kept on the message. We store it and hand it back in webhook payloads, and that is all it does. If you want to search or filter by it, use `tags` instead.","additionalProperties":true},"parameters":{"type":["object","null"],"additionalProperties":true,"readOnly":true,"description":"The substitution values this send supplied, whether inline or from a template, or null if none were supplied. They are the values applied to `subject` and to the bodies the content endpoint returns, kept so you can see what produced the delivered copy and not only the result.\n"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/EmailAttachmentRef"},"description":"Attachment metadata for the send. Empty when no attachments were included. Raw content is not echoed. When content storage is enabled, download an attachment by its `id` via the message's attachment endpoint."},"track_opens":{"type":"boolean","description":"Whether open tracking is enabled for this send."},"track_clicks":{"type":"boolean","description":"Whether click tracking is enabled for this send."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the send request was accepted."},"thread_id":{"type":["string","null"],"readOnly":true,"pattern":"^thr_[0-9a-hjkmnp-tv-z]{26}$","description":"Thread this message belongs to, or null when the message is not part of one."},"in_reply_to_message_id":{"readOnly":true,"description":"The message this one is a reply to, if any.","oneOf":[{"$ref":"#/components/schemas/EmailID"},{"type":"null"}]},"delivered_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When all recipients reached a terminal delivered state, or null if not yet fully delivered."},"scheduled_at":{"type":"string","format":"date-time","readOnly":true,"description":"When this message is scheduled to send, for a send created with a future send time. Absent for an immediate send. Stays set after the scheduled send fires."}}},"EmailMessageList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of message objects.","items":{"$ref":"#/components/schemas/EmailMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EmailAddressInput":{"description":"A sender or recipient address. Accepts a plain email string (`jane@acme.com`), an RFC 5322 mailbox string with an embedded display name (`Jane Doe <jane@acme.com>`), or an object carrying the address and an optional display name. All forms can be mixed freely within one request. Responses always return the object form.\n","oneOf":[{"type":"string","minLength":5,"maxLength":998,"pattern":"^[^\\r\\n]+$","title":"Email string","description":"Email address, optionally in RFC 5322 mailbox form with an embedded display name.","example":"Jane Doe <jane@acme.com>"},{"$ref":"#/components/schemas/EmailAddress"}]},"TemplateSlug":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$","description":"A template's slug: what you send it by, for example `welcome-email`. Email and SMS slugs stay fixed after creation. WhatsApp slugs can change only before the first submission. A slug can contain lowercase letters, numbers, hyphens, and underscores, has to start and end with a letter or a number, and can be up to 63 characters long.\n","example":"welcome-email"},"EmailTemplateSend":{"type":"object","additionalProperties":false,"description":"A reference to the template to send. Identify the template by its `id` or its `slug`, supplying exactly one of the two, and give the values for its variables in `parameters`.\n","oneOf":[{"required":["id"]},{"required":["slug"]}],"properties":{"id":{"description":"The template to send, by its id.","$ref":"#/components/schemas/EmailTemplateID"},"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"description":"The template to send, by its slug handle. A workspace template (for example `welcome-email`) or a built-in `system` template (for example `bird_welcome`).","example":"welcome-email"},"language":{"$ref":"#/components/schemas/LanguageTag","description":"Which of the template's languages to send. Omit it to send the template's default language, unless the template sets `language_source_required`, in which case a send naming no language is rejected. When the template does not have the language you ask for, its own `on_missing_language` setting decides whether the closest available language is sent instead or the send is rejected.\n"},"parameters":{"type":"object","additionalProperties":true,"description":"Values for the template's variables, keyed by the variable name. A variable name is a single word.\n\nEvery variable in the template's `variables` list needs a value. A send\nthat omits one is rejected. Languages can use different variables, and a\nvalue unused by the selected language is ignored.\n\nThe API supplies values under the reserved `bird` key, so a send that sets\nit is rejected. `parameters` is capped at 16 KB once serialized.\n","example":{"animal":"otter"}}}},"EmailAttachment":{"type":"object","additionalProperties":false,"required":["filename","content"],"description":"A file attached to an email. Put the base64-encoded bytes in `content` and the\nrecipient-facing name in `filename`. To show an image inline, set `content_id`\nand reference it from the HTML body with `<img src=\"cid:...\"/>`.\n\nThe generated message is limited to 20 MB across the HTML body, text body,\nattachments, and inline images after base64 and MIME encoding. Keep raw\nattachment content at or below 15 MB to leave room for encoding and the body.\n\nBatch sends apply the same 20 MB limit to each message and to the full request\nbody. Executable and script content types are rejected.\n","properties":{"filename":{"type":"string","minLength":1,"maxLength":255,"description":"The name the recipient sees on the attachment.","example":"invoice.pdf"},"content":{"type":"string","format":"byte","minLength":1,"description":"Base64-encoded file bytes. The encoded value and MIME wrapping count toward the 20 MB message limit."},"content_type":{"type":"string","description":"The file's MIME type. If omitted, the API infers it from the extension in `filename`. The API rejects executable and script types based on this value.","example":"application/pdf"},"content_id":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9._-]+$","description":"An RFC 2392 Content-ID for an inline file. Reference it from the HTML body with `<img src=\"cid:{content_id}\"/>`. Omit it to send a downloadable attachment.","example":"invoice-logo"}}},"EmailMessageSendRequest":{"type":"object","additionalProperties":false,"required":["from","to"],"properties":{"from":{"$ref":"#/components/schemas/EmailAddressInput","description":"Sender address, as a plain email string, an RFC 5322 mailbox string (`Jane <jane@acme.com>`), or an object with an optional display name. Must be from a verified domain in this workspace."},"to":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddressInput"},"minItems":1,"maxItems":50,"description":"Primary recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane <jane@acme.com>`), or an object with an optional display name."},"cc":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddressInput"},"maxItems":50,"description":"CC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane <jane@acme.com>`), or an object with an optional display name."},"bcc":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddressInput"},"maxItems":50,"description":"BCC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane <jane@acme.com>`), or an object with an optional display name."},"subject":{"type":"string","minLength":1,"maxLength":998,"description":"Message subject line. Required for inline sends. Omit it when sending a `template` (the template supplies the subject)."},"html":{"type":"string","maxLength":524288,"description":"HTML body. At least one of html or text must be provided."},"text":{"type":"string","maxLength":524288,"description":"Plain-text body. At least one of html or text must be provided."},"reply_to":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddressInput"},"minItems":1,"maxItems":25,"description":"Reply-To addresses, each a plain email string, an RFC 5322 mailbox string, or an object with an optional display name. RFC 5322 allows multiple. Every recipient reply hits all listed addresses, so 1-2 is typical. The 25 cap exists to prevent header sizes that some receiving mail servers reject.\n"},"headers":{"type":"object","maxProperties":25,"additionalProperties":{"type":"string","maxLength":998},"description":"Custom email headers as key-value pairs (for example `References`, `In-Reply-To`, or your own `X-*` headers). Reserved headers are rejected with a `422`. Set the message's addressing and subject through the dedicated fields: `from`, `to`, `cc`, `bcc`, `reply_to`, and `subject`. The API automatically generates `Content-Type`, `Content-Transfer-Encoding`, `DKIM-Signature`, `Received`, and `Return-Path`. You cannot override these generated headers. `List-Unsubscribe` and `List-Unsubscribe-Post` are honored as-is on `transactional` sends. Marketing sends receive a compliant unsubscribe header, so supplying either one is rejected with a `422`. Header values may not contain carriage-return or line-feed characters. Up to 25 headers per send, each value up to 998 characters.\n"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"maxItems":20,"description":"Structured `{name, value}` labels for **filtering and analytics**. Tags become first-class query dimensions:\n\n- Filter the list endpoint by tag name.\n- Slice analytics rollups by tag.\n- Surface in webhook payloads.\n\nCap: 20 tags per send. Use tags for low-cardinality dimensions (`category`, `experiment_variant`, `template_id`). For arbitrary structured context that you do not need as a filter dimension, use `metadata` instead.\n"},"metadata":{"type":"object","description":"Arbitrary JSON object returned on API reads and included in webhook payloads. You can query its paths in analytics, such as `metadata.order_id`, but it is not a dashboard filter. The serialized object is limited to 2 KB. Use metadata for per-send context such as order IDs, customer references, and structured event data. For low-cardinality filterable labels, use `tags` instead.\n","additionalProperties":true},"parameters":{"type":"object","description":"Parameter values used to personalize inline content, shared across all recipients of this send. Tokens such as `{{ animal }}` are replaced with matching values; missing values render empty. Include this object, even as `{}`, to use Liquid, or omit it to leave tokens unchanged. Use single-word names other than `bird`. Cap: 16 KB serialized. For a stored template, use `template.parameters` instead. See [inline personalization](https://bird.com/docs/guides/email/sending-email#content) for validation and URL encoding examples.\n","additionalProperties":true},"template":{"allOf":[{"$ref":"#/components/schemas/EmailTemplateSend"}],"description":"Send a stored template instead of inline content. When set, omit `subject`, `html` and `text`, because the template supplies them. Personalize with `template.parameters`. A template send goes out immediately: `template` and `scheduled_at` are mutually exclusive, and combining them is rejected with a `422`.\n"},"track_opens":{"type":"boolean","default":true,"description":"Whether to track open events for this message."},"track_clicks":{"type":"boolean","default":true,"description":"Whether to track click events for this message."},"ip_pool_id":{"type":"string","pattern":"^ipp_([0-9a-hjkmnp-tv-z]{26}|shared)$","description":"ID of the IP pool to send from (`ipp_` prefix), or `ipp_shared` to route through the shared pool explicitly. Omit to use your organization's default pool. An unknown pool, or a pool with no dedicated IPs available to send from, is rejected with a `422`.\n"},"category":{"$ref":"#/components/schemas/EmailMessageCategory","default":"marketing","description":"Content classification, which controls suppression policy:\n\n- `marketing`: Blocks on all suppression reasons.\n- `transactional`: Allows delivery through complaint and unsubscribe suppressions, for receipts, password resets, and similar operational mail.\n\nWhen you send with `template` and omit this field, the message takes the template's own classification, so a template created as `transactional` sends as transactional. Set this field to classify a single send differently from its template. It always takes precedence. A send with no template and no category defaults to `marketing`.\n"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/EmailAttachment"},"maxItems":20,"description":"Files to attach, up to 20 per message. A message can be at most 20 MB once it has been generated, and we refuse a send that would go over. That figure covers the HTML body, the text body and every attachment and inline image, all measured after base64 encoding, which adds roughly a third. So 15 MB of raw files already accounts for most of the budget, and the body competes for the same space. A batch send is held to the same 20 MB per message, and the whole request body is capped at 20 MB as well.\n"},"scheduled_at":{"type":"string","format":"date-time","description":"Schedule the message to send at a future time instead of immediately. Must be at least 30 seconds and at most 30 days ahead. Outside that range the request is rejected with `422`. The message returns with status `accepted` and shows as `scheduled` on reads until it sends. Cancel it before then with the message cancel endpoint. Scheduled sends count against your plan's monthly scheduled-email allowance. Exceeding it is rejected with a `422`. A scheduled message has inline content: `scheduled_at` and `template` are mutually exclusive, and combining them is rejected with a `422`. Batch items take this field too, so one batch can mix scheduled and immediate messages.\n"}},"example":{"from":{"email":"noreply@acme.com","name":"Acme Support"},"to":[{"email":"delivered@messagebird.dev","name":"Jane Doe"}],"cc":["manager@acme.com"],"reply_to":["support@acme.com"],"subject":"Welcome aboard","html":"<h1>Hi there 👋</h1>","text":"Hi there","headers":{"X-Campaign":"spring-2026"},"tags":[{"name":"category","value":"welcome"}],"metadata":{"user_id":"usr_12345"},"category":"transactional","track_clicks":false}},"EmailMessageBatchRequest":{"type":"object","additionalProperties":false,"description":"Batch of email message send requests.","required":["messages"],"properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/EmailMessageSendRequest"},"minItems":1,"maxItems":100,"description":"Email message send requests, up to 100. All items are validated before any are queued. Attachments are allowed on individual messages. Each message must stay within the 20 MB estimated generated message-size cap. The serialized JSON request body for the batch has a hard 20 MB cap.\n"}},"example":{"messages":[{"from":{"email":"noreply@acme.com","name":"Acme Support"},"to":[{"email":"delivered@messagebird.dev","name":"Jane Doe"}],"subject":"Your receipt for order #1234","text":"Thanks for your purchase! Your receipt is attached."},{"from":{"email":"noreply@acme.com","name":"Acme Support"},"to":[{"email":"delivered@messagebird.dev","name":"John Roe"}],"subject":"Your receipt for order #1235","text":"Thanks for your purchase! Your receipt is attached."}]}},"EmailMessageBatchItem":{"type":"object","additionalProperties":false,"required":["id","status","category"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/EmailID","description":"Message ID assigned to this batch item."},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["accepted"],"description":"Initial status of this message in the batch."},"category":{"type":"string","minLength":1,"enum":["marketing","transactional"],"description":"Resolved category for this batch item."},"requested_language":{"oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}],"readOnly":true,"description":"The template language this item asked for, in canonical form. Null when the item named no language or used no template. Every item in a batch resolves its own template reference, so this and `resolved_language` can differ from item to item.\n"},"resolved_language":{"oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}],"readOnly":true,"description":"The template language this item was actually delivered in, in canonical form. Null when the item used no template. A value here differing from `requested_language` means the template did not have the language asked for and its `on_missing_language` policy chose this one.\n"},"template_id":{"oneOf":[{"$ref":"#/components/schemas/EmailTemplateID"},{"type":"null"}],"readOnly":true,"description":"The template this item rendered from, or null for an item that supplied its content inline.\n"},"template_version_id":{"oneOf":[{"$ref":"#/components/schemas/EmailTemplateVersionID"},{"type":"null"}],"readOnly":true,"description":"The exact template version this item rendered from, or null for an inline item. Record it if you need to reproduce what was sent: a template's live version changes every time you submit it.\n"},"scheduled_at":{"type":"string","format":"date-time","readOnly":true,"description":"When this item is scheduled to send, for an item created with a future send time. Absent for an item that sends immediately.\n"}}},"EmailMessageBatchResponse":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailMessageBatchItem"},"description":"One entry per message in the batch, in submission order."}}},"RecipientID":{"type":"string","minLength":1,"pattern":"^er_[0-9a-hjkmnp-tv-z]{26}$","example":"er_01krdgeqcxet5s7t44vh8rt9mg"},"RecipientRole":{"type":"string","minLength":1,"enum":["to","cc","bcc"],"description":"Envelope position of a recipient on an outbound email event.","example":"to"},"EmailRecipient":{"type":"object","additionalProperties":false,"required":["id","parent_id","role","recipient","status","open_count","click_count"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/RecipientID","description":"Recipient ID."},"parent_id":{"$ref":"#/components/schemas/EmailID","description":"ID of the message this recipient belongs to. For a message send, this is the message's own `em_`-prefixed ID. For a broadcast, it is the `em_`-prefixed ID of the copy addressed to this recipient. Read either one with [Get an email message](/docs/api/reference/get-email-message), which answers 404 for a broadcast copy the send has not recorded. No recipient status distinguishes a copy the send recorded from one it did not."},"role":{"$ref":"#/components/schemas/RecipientRole","description":"How this recipient appeared in the send request."},"recipient":{"type":"string","format":"email","minLength":5,"description":"Recipient email address."},"name":{"type":["string","null"],"description":"Display name provided for this recipient on the send, or null if none was given."},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["accepted","processed","deferred","delivered","bounced","complained","rejected"],"x-enum-varnames":["EmailRecipientStatusAccepted","EmailRecipientStatusProcessed","EmailRecipientStatusDeferred","EmailRecipientStatusDelivered","EmailRecipientStatusBounced","EmailRecipientStatusComplained","EmailRecipientStatusRejected"],"description":"Delivery status for this recipient:\n\n- `accepted`: The send has been taken and is being prepared for delivery.\n- `processed`: This recipient's message is on its way out.\n- `deferred`: The recipient's mailbox provider asked for a retry, and delivery attempts continue.\n- `delivered`: The recipient's mail server accepted the message.\n- `bounced`: Delivery permanently failed (see `bounce_type` for hard vs soft).\n- `complained`: The recipient reported the message as spam.\n- `rejected`: Delivery was never attempted (see `rejection_reason` for why).\n"},"rejection_reason":{"type":["string","null"],"readOnly":true,"enum":["recipient_suppressed","transmission_failed","generation_failure","policy_rejection","domain_unverified","quota_exceeded","recipient_not_allowed",null],"description":"Present on `status: rejected` rows. Specifies why the recipient was rejected:\n\n- `recipient_suppressed`: The recipient is on the workspace suppression list, so\n  delivery was never attempted.\n- `transmission_failed`: The message could not be transmitted for delivery.\n- `generation_failure`: The message could not be built for delivery (template or\n  content issue).\n- `policy_rejection`: The message was refused by sending policy.\n- `domain_unverified`: The sending domain was not verified.\n- `quota_exceeded`: The organization's send quota was reached.\n- `recipient_not_allowed`: A recipient was not permitted for this send (for shared\n  onboarding-domain sends, recipients must be verified workspace members).\n"},"bounce_type":{"type":["string","null"],"readOnly":true,"enum":["hard","soft","undetermined","admin","block",null],"description":"Bounce classification for `bounced` and `deferred` rows, or null when the recipient\nhas not bounced or the receiving server's response has not been classified.\n\n- `hard`: a permanent failure (invalid address or non-existent domain).\n- `soft`: a transient failure (mailbox full or server temporarily unavailable).\n- `block`: the receiving mail server blocked the sending IP for reputation reasons.\n- `admin`: an administrative refusal (relaying denied or blocklisted domain).\n- `undetermined`: the receiving server's response is ambiguous.\n"},"bounce_code":{"type":["string","null"],"readOnly":true,"description":"SMTP reply code returned by the receiving mail server for `bounced` and `deferred` rows, or null when none was provided.","example":"550"},"bounce_description":{"type":["string","null"],"readOnly":true,"description":"Human-readable reason the receiving mail server gave for the bounce or deferral, or null when none was provided.","example":"5.1.1 Unknown user"},"processed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the message was prepared and queued for delivery to the recipient's mail server, or null if that has not happened yet."},"delivered_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the recipient's mail server accepted the message, or null if not yet delivered."},"processing_latency_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Time between the send being accepted and the message being prepared for delivery, in milliseconds. Null until processed."},"delivery_latency_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Time between the message being prepared and the receiving mail server accepting it, in milliseconds. Null until delivered."},"total_latency_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"End-to-end accept → delivered time for this recipient, in milliseconds. Null until delivered."},"open_count":{"type":"integer","readOnly":true,"default":0,"description":"Number of open events for this recipient."},"click_count":{"type":"integer","readOnly":true,"default":0,"description":"Number of click events for this recipient."}}},"EmailRecipientList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of recipient objects for this email send.","items":{"$ref":"#/components/schemas/EmailRecipient"}},"next":{"type":"array","readOnly":true,"description":"What to do next, given what this page reports. Present only where the read computes it: an\nempty list means the answer you were looking for is here and there is nothing further to\ndo. Absent entirely on reads that do not report next actions.\n","items":{"$ref":"#/components/schemas/NextAction"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EmailEventType":{"type":"string","minLength":1,"description":"Type of an event in a message's per-recipient delivery timeline.\n\n- `email.scheduled`: We accepted a send scheduled for a future time. Fires once for each message regardless of its recipient count.\n- `email.accepted`: We accepted the send and are getting ready to deliver it. Fires once per requested recipient.\n- `email.processed`: We queued the message for delivery to the recipient's mail server.\n- `email.deferred`: The recipient's mail server temporarily refused the message. Delivery remains pending and is retried. Can fire more than once per recipient.\n- `email.delivered`: The recipient's mail server accepted the message.\n- `email.bounced`: Delivery permanently failed at the recipient's mail server.\n- `email.out_of_band_bounce`: A bounce notification arrived after the message had already been accepted for delivery.\n- `email.rejected`: We rejected the message before attempting delivery, for example because the recipient is suppressed.\n- `email.canceled`: A scheduled send was canceled before it fired. Fires once for each message regardless of its recipient count.\n- `email.opened`: The recipient opened the message. Can fire more than once per recipient.\n- `email.clicked`: The recipient clicked a tracked link in the message. Can fire more than once per recipient.\n- `email.unsubscribed`: The recipient opted out through a tracked unsubscribe link in the message.\n- `email.list_unsubscribed`: The recipient opted out through the one-click unsubscribe control in their mail client.\n- `email.complained`: The recipient reported the message as spam through their mailbox provider.\n\nWe can add new event types to this list over time, so treat a value you do not recognize as a new type rather than as an error.\n","x-extensible-enum":["email.accepted","email.bounced","email.canceled","email.clicked","email.complained","email.deferred","email.delivered","email.list_unsubscribed","email.opened","email.out_of_band_bounce","email.processed","email.rejected","email.scheduled","email.unsubscribed"],"example":"email.delivered"},"EmailEvent":{"type":"object","additionalProperties":false,"required":["id","type","occurred_at","recipient_id"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"pattern":"^ev_[0-9a-hjkmnp-tv-z]{26}$","description":"Event ID.","example":"ev_01krdgeqcxet5s7t44vh8rt9mg"},"type":{"$ref":"#/components/schemas/EmailEventType","description":"The event's type. `email.processed`, for example, means the message has been processed and queued for delivery.\n"},"occurred_at":{"type":"string","format":"date-time","minLength":1,"description":"When this event occurred."},"recipient_id":{"$ref":"#/components/schemas/RecipientID","description":"Recipient this event applies to."},"bounce_type":{"type":["string","null"],"enum":["hard","soft","undetermined","admin","block",null],"description":"Bounce classification. Present on `email.bounced`, `email.out_of_band_bounce`, and\n`email.deferred` events.\n\n- `hard`: a permanent failure (invalid address or non-existent domain).\n- `soft`: a transient failure (mailbox full or server temporarily unavailable).\n- `block`: the receiving mail server blocked the sending IP for reputation reasons.\n- `admin`: an administrative refusal (relaying denied or blocklisted domain).\n- `undetermined`: the receiving server's response is ambiguous.\n"},"bounce_class":{"type":["integer","null"],"minimum":1,"maximum":255,"description":"A more detailed numeric bounce code, useful for telling apart failures that share the same `bounce_type`. For example, a DNS failure and a spam block can both come through as `bounce_type: soft` or `bounce_type: block`; this field tells you which one actually happened. Present on `email.bounced`, `email.out_of_band_bounce`, and `email.deferred` events.\n"},"bounce_code":{"type":["string","null"],"description":"SMTP status code returned by the receiving mail server. Present on `email.bounced` and `email.deferred` events.\n","example":"5.1.1"},"bounce_description":{"type":["string","null"],"description":"The bounce reason, in plain language, as reported by the mail server. Present on `email.bounced` and `email.deferred` events."},"rejection_reason":{"type":["string","null"],"enum":["recipient_suppressed","transmission_failed","generation_failure","policy_rejection","domain_unverified","quota_exceeded","recipient_not_allowed",null],"description":"Specific cause of rejection. Present on `email.rejected` events only.\n\n- `recipient_suppressed`: The recipient is on the workspace suppression list.\n- `transmission_failed`: The message could not be transmitted for delivery.\n- `generation_failure`: The message could not be built for delivery, because of a template or content issue.\n- `policy_rejection`: The message was refused by sending policy.\n- `domain_unverified`: The sending domain was not verified.\n- `quota_exceeded`: The organization's send quota was reached.\n- `recipient_not_allowed`: This recipient was not allowed for this send. For a send from the shared onboarding domain, every recipient has to be a verified member of the workspace.\n"},"sending_ip":{"type":["string","null"],"description":"The IP address used to send this message. Useful for spotting a deliverability problem that is tied to one specific sending IP rather than affecting all of them. Present on `email.delivered`, `email.bounced`, `email.out_of_band_bounce`, and `email.deferred` events.\n"},"mailbox_provider":{"type":["string","null"],"description":"The recipient mailbox provider, as a lowercased classifier bucket (e.g. `gmail`, `yahoo`, `microsoft`, `apple`). Present on `email.processed`, `email.delivered`, `email.complained`, `email.bounced`, `email.out_of_band_bounce`, `email.deferred`, `email.rejected`, `email.unsubscribed`, `email.list_unsubscribed`, `email.opened`, and `email.clicked` events when the receiving mail system could be classified; null when it could not.\n"},"mailbox_provider_region":{"type":["string","null"],"description":"The provider region, as reported by the receiving mail system (for example `NA`, `EU`, `APAC`). The set is open and provider-specific. Present on `email.processed`, `email.delivered`, `email.complained`, `email.bounced`, `email.out_of_band_bounce`, `email.deferred`, `email.rejected`, `email.unsubscribed`, `email.list_unsubscribed`, `email.opened`, and `email.clicked` events when reported; null otherwise.\n"},"is_prefetched":{"type":["boolean","null"],"description":"True when the open was auto-fetched by an inbox privacy feature (Apple Mail Privacy Protection, the Gmail image proxy) rather than a person actually opening the message. Use it to calculate open rate accurately. Present on `email.opened` events only.\n"},"url":{"type":["string","null"],"description":"The clicked URL. Present on `email.clicked` events, and on `email.unsubscribed` events when the recipient unsubscribed through a link in the message."},"link_name":{"type":["string","null"],"description":"The clicked link's own name, when the link in the message carried one, so a click can be reported by what the link said rather than where it pointed. Absent when the link had no name. Appears alongside `url` on `email.clicked` events, and on `email.unsubscribed` events when the recipient unsubscribed through a link.","example":"Faster exports, docs"},"country":{"type":["string","null"],"minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code derived from the client IP. Present on `email.opened` and `email.clicked` events when available.","example":"US"},"ip_address":{"type":["string","null"],"description":"Client IP address (IPv4 or IPv6). Present on `email.opened` and `email.clicked` events when available."},"user_agent":{"type":["string","null"],"description":"Client user-agent string. Present on `email.opened` and `email.clicked` events when available."}}},"EmailEventList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of timeline events for this email send, in chronological order.","items":{"$ref":"#/components/schemas/EmailEvent"}},"next":{"type":"array","readOnly":true,"description":"What to do next, given what this page reports. Present only where the read computes it: an\nempty list means the answer you were looking for is here and there is nothing further to\ndo. Absent entirely on reads that do not report next actions.\n","items":{"$ref":"#/components/schemas/NextAction"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EmailMessageContent":{"type":"object","additionalProperties":false,"description":"The body content of a sent email message, as delivered. A send that used a template stores the template's body, so these report it with the send's `parameters` substituted in.\n","properties":{"html":{"type":"string","minLength":1,"description":"The HTML body of the message as delivered, if it was stored."},"text":{"type":"string","minLength":1,"description":"The plain-text body of the message as delivered, if it was stored."}}},"EmailSendQuotaWindow":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One window of the send allowance, with the cap in force over it and how much of that cap the window has spent.","required":["limit","usage","remaining","percent_used","resets_at"],"properties":{"limit":{"type":"integer","format":"int64","readOnly":true,"description":"The most emails the organization may send in this window.","example":500000},"usage":{"type":"integer","format":"int64","readOnly":true,"description":"How many of them the window has counted so far. Every recipient of an accepted send counts toward it, not just the send itself.","example":19500},"remaining":{"type":"integer","format":"int64","readOnly":true,"description":"How many are left before the cap is reached. Zero once the window is fully spent.","example":480500},"percent_used":{"type":"number","format":"double","minimum":0,"readOnly":true,"description":"The share of the cap the window has spent, as a percentage. It can read above 100 when the cap was lowered part-way through the window.","example":3.9},"resets_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the window ends and its count starts again from zero.","example":"2026-09-01T00:00:00Z"}}},"EmailSendQuota":{"type":"object","additionalProperties":false,"description":"How much of the organization's email send allowance is spent, in each of the two windows a send is measured against. Use it to show a sender how close they are to the point where sending stops, without hard-coding either cap in the client.\n\nA window is null when no cap applies to it, so nothing is counted over it and nothing limits sending there.\n","required":["monthly","daily"],"properties":{"monthly":{"type":["object","null"],"readOnly":true,"description":"The billing period's allowance. It resets when the period does, so a mid-period plan change carries the count forward.","allOf":[{"$ref":"#/components/schemas/EmailSendQuotaWindow"}]},"daily":{"type":["object","null"],"readOnly":true,"description":"The day's allowance, counted over the UTC day.","allOf":[{"$ref":"#/components/schemas/EmailSendQuotaWindow"}]}}},"EmailBroadcastStatus":{"type":"string","minLength":1,"enum":["draft","scheduled","accepted","sending","sent","canceling","canceled","failed"],"description":"Where the broadcast itself has got to. This is separate from what happened to individual recipients, which the broadcast's own `sent_count`, `delivered_count`, `bounced_count` and `complained_count` tell you. Those four are fields on the broadcast, not on everything that carries this status, and reading the broadcast's recipients or its events gives the same outcomes one recipient at a time.\n\n- `draft`: Created, and not sent or scheduled yet.\n- `scheduled`: Due to send at `scheduled_at`. You can still edit it, and you can still change the time, right up until sending starts.\n- `accepted`: Taken for immediate sending. Nothing has gone out yet.\n- `sending`: On its way. Some recipients have been sent to and some have not.\n- `sent`: Every recipient has been sent to.\n- `canceling`: A cancellation is under way and the remaining sends are stopping.\n- `canceled`: The cancellation finished. Anything already on its way to a recipient when you canceled cannot be pulled back.\n- `failed`: The broadcast could not be sent. Reading the broadcast gives `failure_reason`, which says why. If it had already started sending, the recipients it reached keep their delivery status and carry on producing events.\n\nA draft is deleted rather than canceled, because it was never sent.\n"},"AudienceID":{"type":"string","minLength":1,"pattern":"^adn_[0-9a-hjkmnp-tv-z]{26}$","example":"adn_01krdgeqcxet5s7t44vh8rt9mg"},"EmailBroadcastTemplate":{"type":"object","additionalProperties":false,"required":["id"],"description":"The template a broadcast sends, and the exact version of it the broadcast is fixed to. The template cannot be one that requires every send to name a language, because a broadcast never names one, so a template that insists on it has nothing to work with.\n","properties":{"id":{"$ref":"#/components/schemas/EmailTemplateID","description":"Which template the broadcast sends. Which version of it the send is fixed to is `version_id`.\n"},"version_id":{"oneOf":[{"$ref":"#/components/schemas/EmailTemplateVersionID"},{"type":"null"}],"readOnly":true,"description":"The template version this broadcast is fixed to. It is chosen when the broadcast is prepared for sending, so publishing a new version while the broadcast is going out cannot change what the rest of the recipients get. Null until the broadcast is prepared.\n"}}},"EmailBroadcast":{"type":"object","additionalProperties":false,"required":["id","category","status","recipient_count","track_opens","track_clicks","created_at","sent_at"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"pattern":"^eb_[0-9a-hjkmnp-tv-z]{26}$","description":"Broadcast ID.","example":"eb_01krdgeqcxet5s7t44vh8rt9mg"},"from":{"$ref":"#/components/schemas/EmailAddress","description":"The address this broadcast sends from. `name` is filled in when the broadcast was given a display name to send under. Left out on a draft that has not picked a sender yet."},"audience_id":{"$ref":"#/components/schemas/AudienceID","description":"The audience this broadcast sends to. When the send starts we turn the audience into a list of recipients, and you can read that list a page at a time with [List recipients of a broadcast](/docs/api/reference/list-email-broadcast-recipients). Left out on a draft that has not picked an audience yet."},"template":{"oneOf":[{"$ref":"#/components/schemas/EmailBroadcastTemplate"},{"type":"null"}],"description":"The template this broadcast sends. A broadcast sends the template's published version, and the exact version is fixed when the broadcast is prepared for sending, so publishing a new version afterwards does not change what this broadcast sends. Null on a draft that has not chosen a template yet."},"html_bytes":{"type":"integer","minimum":0,"readOnly":true,"format":"int64","example":18432,"description":"Size of the HTML body this broadcast sends, in bytes, or 0 when its content has no HTML part. Measured on the template version the broadcast sends, so this is the real body we send and differs per recipient only by that recipient's own merge values. Returned on a single broadcast read, and absent from the list and from the broadcast that creating, updating, sending or canceling one returns, none of which measure the content. Absent too when the broadcast has no template or its content can no longer be read.\n"},"text_bytes":{"type":"integer","minimum":0,"readOnly":true,"format":"int64","example":2104,"description":"Size of the plain-text body this broadcast sends, in bytes, or 0 when its content has no plain-text part. Measured, and absent, the same way as `html_bytes`.\n"},"category":{"type":"string","minLength":1,"enum":["marketing","transactional"],"description":"What kind of email this is, which decides how suppressions apply to it. A `marketing` broadcast is held back from every suppressed address. A `transactional` one still goes to addresses suppressed for a complaint or an unsubscribe, because those suppressions are about marketing mail."},"ip_pool_id":{"type":"string","pattern":"^ipp_([0-9a-hjkmnp-tv-z]{26}|shared)$","description":"The IP pool this broadcast sends from, or `ipp_shared` when it sends through the shared pool. Absent when it sends on your organization's default pool."},"reply_to":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddress"},"maxItems":25,"description":"Where replies to this broadcast go, if you want them somewhere other than the `from` address. Absent when you have not set one."},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Any custom email headers set on the broadcast. Returned on a single broadcast read and on the broadcast that creating, updating, sending or canceling one returns, and absent from the list. The unsubscribe headers we add ourselves are not included."},"status":{"readOnly":true,"example":"sent","description":"Where the broadcast itself has got to, separate from what happened to individual recipients: for that, read `sent_count`, `delivered_count`, `bounced_count` and `complained_count` below. When it is `failed`, `failure_reason` says why.\n","allOf":[{"$ref":"#/components/schemas/EmailBroadcastStatus"}]},"next":{"type":"array","readOnly":true,"description":"What to do next about this broadcast, given the state it is in. Each entry names one action and\nsays why it is worth taking. Present on reads that compute it: an empty list means there is\nnothing to do, and the field is absent entirely on responses that do not report next actions.\n","items":{"$ref":"#/components/schemas/NextAction"}},"failure_reason":{"type":["string","null"],"readOnly":true,"enum":["empty_audience","audience_unavailable","content_invalid","insufficient_funds","quota_exceeded","internal_error",null],"example":null,"description":"Why the broadcast failed. Set when `status` is `failed`, and `null` the rest of the time.\n\n- `empty_audience`: There was nobody to send to. Either the audience has no members, or every address in it is suppressed.\n- `audience_unavailable`: The audience no longer exists, so there was nothing to resolve.\n- `content_invalid`: The broadcast could not be set up to send. `failure_detail` says exactly what was wrong. It is one of these:\n  - The broadcast has no template, or its template has been deleted.\n  - The template has no published version, or no sendable content.\n  - The template uses a loop that a broadcast cannot fill.\n  - The template requires every send to name a language.\n  - The sending domain is no longer verified.\n  - The IP pool has nothing to send from.\n  - The message could not be handed off for delivery.\n- `insufficient_funds`: There was not enough in the workspace balance to pay for the send.\n- `quota_exceeded`: The send would have gone past your organization's daily or monthly email allowance, whichever runs out first. This can happen when the broadcast is being prepared, or partway through sending if the remaining recipients no longer fit. `failure_detail` gives you the count and the limit.\n- `internal_error`: Something went wrong on our side. Retry, and open a support ticket if it keeps happening.\n"},"failure_detail":{"type":["string","null"],"readOnly":true,"example":null,"description":"A sentence explaining the failure in more detail than `failure_reason` does, and `null` when the broadcast has not failed. Show it to the person using your app. Do not write code that reads it, because the wording can change. Branch on `failure_reason` instead."},"recipient_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"default":0,"example":4820,"description":"Number of recipients after suppressed addresses are removed from the audience. This is 0 until sending starts and the audience becomes a recipient list."},"sent_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":4820,"description":"How many recipients the broadcast has been sent to, counting every recipient whose status is `processed` or later. The number rises while the broadcast is `sending` and stops changing once the broadcast has finished. These counters are exact. The email stats endpoints report on the same sending but are approximate, so use these numbers when you need the precise count. Absent when the broadcast comes back from creating, updating, sending or canceling it, none of which read the counters. List and single-broadcast reads return 0 when no delivery events are recorded. When counters are unavailable, those reads still return 200 and omit the counters. Read the broadcast again for the numbers."},"delivered_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":4712,"description":"How many recipients' messages were accepted by their mail server. Absent when `sent_count` is."},"bounced_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":96,"description":"How many recipients the message could not be delivered to at all. Absent when `sent_count` is."},"complained_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":12,"description":"How many recipients marked the message as spam. Absent when `sent_count` is."},"open_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":3104,"description":"How many times the message was opened, added up across every recipient. One recipient opening it twice counts twice. Absent when `sent_count` is."},"click_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":812,"description":"How many times a link in the message was clicked, added up across every recipient. One recipient clicking twice counts twice. Absent when `sent_count` is."},"sending_ips":{"type":"array","readOnly":true,"items":{"type":"string","minLength":1},"example":["198.51.100.42"],"description":"The IP addresses this broadcast's messages went out from, up to 100 of them. A broadcast is spread across every address in its pool, so more than one can appear. The receiving mail systems name the address when they deliver, bounce or defer a message, so this stays absent until the first of those comes back. Returned on a single broadcast read, and absent from the list and from the broadcast that creating, updating, sending or canceling one returns, none of which read them. For delivery and latency broken down per address, read the sending-IP stats.\n"},"unique_opens_non_prefetched":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":2140,"description":"How many distinct recipients opened the message at least once, excluding opens auto-fetched by inbox privacy features (such as Apple Mail Privacy Protection and the Gmail image proxy). A recipient who opened several times, or whose inbox prefetched the message, counts once. Absent when `sent_count` is."},"unique_clicks":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":693,"description":"How many distinct recipients clicked a link in the message at least once. A recipient who clicked several times counts once. Absent when `sent_count` is."},"out_of_band_bounces":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":14,"description":"How many recipients bounced after the message had already been accepted for delivery. A recipient who bounced this way more than once counts once. Absent when `sent_count` is."},"delivered_recipients":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":4724,"description":"How many distinct recipients a delivery landed for. This is the denominator to measure `unique_opens_non_prefetched`, `unique_clicks` and `complained_count` against. It differs from `delivered_count`, which reports how many recipients are currently in the delivered state: a recipient who was delivered to and then complained moves to `complained_count` and leaves `delivered_count`, but stays here, because the message did reach them. Absent when `sent_count` is."},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"description":"Labels on this broadcast, each one a `name` and a `value`, that you can filter and search broadcasts by. Use tags for anything you want to find broadcasts by later, and `metadata` for data you only want handed back to you."},"metadata":{"type":"object","description":"Any JSON you want to keep on the broadcast. We store it and hand it back in webhook payloads, and that is all it does. If you want to search or filter by it, use `tags` instead.","additionalProperties":true},"track_opens":{"type":"boolean","example":true,"description":"Whether opens are tracked for this broadcast."},"track_clicks":{"type":"boolean","example":true,"description":"Whether link clicks are tracked for this broadcast."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"example":"2026-09-01T09:14:02.418Z","description":"When the broadcast was created."},"scheduled_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-09-02T08:00:00Z","description":"When the broadcast is due to send, and absent when it is not scheduled."},"started_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-09-02T08:00:03.771Z","description":"When the broadcast started sending. Absent until then. Compare with `sent_at`, which is when the broadcast finished sending."},"sent_at":{"type":["string","null"],"format":"date-time","readOnly":true,"example":"2026-09-02T08:11:47.902Z","description":"When the last recipient was sent to and the broadcast became `sent`. Null until then. Compare with `started_at`, which is when the broadcast started sending."},"canceled_at":{"type":"string","format":"date-time","readOnly":true,"description":"When the broadcast was canceled, and absent if it never was. This is when cancellation was requested, so it is set as soon as the status is `canceling` and does not move while the remaining sends stop and the status becomes `canceled`."}}},"EmailBroadcastList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of broadcast objects.","items":{"$ref":"#/components/schemas/EmailBroadcast"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EmailBroadcastCreateRequest":{"type":"object","additionalProperties":false,"description":"A broadcast sends one email to a whole audience. Every field here is optional, so you can create an empty draft and fill it in later. To actually send, a broadcast needs three things: a `from` address on a verified domain, an `audience_id`, and a `template`.\n\nLeave `send` false, which is the default, and you get a draft. Update it as often as you like, then send it when you are ready. Set `send` to true and the broadcast goes out as soon as it is created, or at `scheduled_at` if you set one.\n","properties":{"from":{"$ref":"#/components/schemas/EmailAddressInput","description":"The address the broadcast sends from. Give it as a plain address, as `Jane <jane@acme.com>` to include a display name, or as an object with an address and a name. The domain has to be one this workspace has verified."},"audience_id":{"$ref":"#/components/schemas/AudienceID","description":"The audience this broadcast sends to. We take the audience's contacts as they stand when the send starts and drop any suppressed addresses, and what is left is who gets the email."},"template":{"$ref":"#/components/schemas/EmailBroadcastTemplate","description":"The template the broadcast sends. You can leave it out on a draft, but a broadcast cannot send without one. The template's published version is fixed when the broadcast is prepared for sending, and each recipient's contact properties are filled into the content as the email goes out."},"reply_to":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddressInput"},"minItems":1,"maxItems":25,"description":"Where replies to this broadcast should go. Give each address as a plain address, as `Jane <jane@acme.com>` to include a display name, or as an object with an address and a name. You can list more than one."},"headers":{"type":"object","maxProperties":25,"additionalProperties":{"type":"string","maxLength":998},"description":"Custom email headers to set on the broadcast, as name and value pairs. Up to 25 of them, each value up to 998 characters. Two names are ours and cannot be set here: `List-Unsubscribe` and `List-Unsubscribe-Post` are dropped if you send them, whatever the category. We add the one-click unsubscribe pair to a marketing broadcast ourselves, and a transactional broadcast has neither header.\n"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"maxItems":20,"description":"Labels on this broadcast, each one a `name` and a `value`, up to 20 of them. You can filter the broadcast list by a tag, break your stats down by one, and read them back off webhook payloads. Use tags for anything you want to find broadcasts by later, and `metadata` for data you only want handed back to you."},"metadata":{"type":"object","description":"Any JSON you want to keep on the broadcast. We store it, hand it back when you read the broadcast, and include it in webhook payloads, and you can break stats down by a path inside it such as `metadata.order_id`. It can be up to 2 KB once serialized.","additionalProperties":true},"track_opens":{"type":"boolean","default":true,"description":"Whether to track opens for this broadcast."},"track_clicks":{"type":"boolean","default":true,"description":"Whether to track link clicks for this broadcast."},"ip_pool_id":{"type":"string","pattern":"^ipp_([0-9a-hjkmnp-tv-z]{26}|shared)$","description":"The IP pool to send this broadcast from. Pass a pool ID, or `ipp_shared` to send through the shared pool on purpose. Leave it out and the broadcast uses your organization's default pool. A pool we do not recognize, or one with no IPs available to send from, is refused with a `422`."},"category":{"type":"string","enum":["marketing","transactional"],"default":"marketing","description":"What kind of email this is. A broadcast sets this itself rather than taking it from its template, and it decides two things: which suppressions apply, and whether we add an unsubscribe header.\n\n`marketing`, the default, is held back from every suppressed address and has the one-click unsubscribe headers. `transactional` still goes to addresses suppressed for a complaint or an unsubscribe, and has no unsubscribe header. Only use `transactional` for genuine operational mail such as a terms-of-service update or a service outage notice. Marketing content sent this way still reaches people who have already unsubscribed from you.\n"},"send":{"type":"boolean","default":false,"description":"Whether to send the broadcast as soon as it is created. Set it to true and the broadcast goes out immediately, or at `scheduled_at` if you set one. Leave it false, which is the default, and you get a draft you can update and send later."},"scheduled_at":{"type":"string","format":"date-time","description":"When to send the broadcast. It has to be at least 30 seconds and at most 365 days from now. It requires `send` to be true, so a `scheduled_at` on its own is refused rather than saved on the draft.\n"}},"example":{"from":"newsletter@acme.com","audience_id":"adn_01krdgeqcxet5s7t44vh8rt9mg","template":{"id":"emt_01krdgeqcxet5s7t44vh8rt9mg"},"category":"marketing","tags":[{"name":"campaign","value":"spring_launch"}],"metadata":{"campaign_id":"12345"}}},"EmailBroadcastUpdateRequest":{"type":"object","additionalProperties":false,"description":"Changes a broadcast that is still a draft or is scheduled. Whatever you send here is applied, and anything you leave out keeps the value it already had. Once a broadcast has started sending it can no longer be edited.\n","properties":{"from":{"$ref":"#/components/schemas/EmailAddressInput","description":"The address the broadcast sends from. Give it as a plain address, as `Jane <jane@acme.com>` to include a display name, or as an object with an address and a name. The domain has to be one this workspace has verified."},"audience_id":{"$ref":"#/components/schemas/AudienceID","description":"The audience this broadcast sends to. We take the audience's contacts as they stand when the send starts and drop any suppressed addresses, and what is left is who gets the email."},"template":{"oneOf":[{"$ref":"#/components/schemas/EmailBroadcastTemplate"},{"type":"null"}],"description":"The template the broadcast sends. Its published version is fixed when the broadcast is prepared for sending. Set this to null to take the template off a draft, or leave it out to keep the one already set."},"reply_to":{"type":["array","null"],"items":{"$ref":"#/components/schemas/EmailAddressInput"},"minItems":1,"maxItems":25,"description":"Where replies to this broadcast should go. Set this to null to remove the addresses already set."},"headers":{"type":"object","maxProperties":25,"additionalProperties":{"type":"string","maxLength":998},"description":"Custom email headers to set on the broadcast, as name and value pairs. What you send replaces the headers the draft already had rather than adding to them. Up to 25 of them, each value up to 998 characters. Two names are ours and cannot be set here: `List-Unsubscribe` and `List-Unsubscribe-Post` are dropped if you send them, whatever the category. We add the one-click unsubscribe pair to a marketing broadcast ourselves, and a transactional broadcast has neither header.\n"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"maxItems":20,"description":"Labels on this broadcast, each one a `name` and a `value`, that you can filter and search broadcasts by. What you send replaces the tags the draft already had rather than adding to them."},"metadata":{"type":"object","description":"Any JSON you want to keep on the broadcast, up to 2 KB once serialized. What you send replaces the metadata the draft already had rather than merging into it.","additionalProperties":true},"track_opens":{"type":"boolean","description":"Whether to track opens for this broadcast."},"track_clicks":{"type":"boolean","description":"Whether to track link clicks for this broadcast."},"ip_pool_id":{"type":["string","null"],"pattern":"^ipp_([0-9a-hjkmnp-tv-z]{26}|shared)$","description":"The IP pool to send this broadcast from. Pass a pool ID, or `ipp_shared` to send through the shared pool on purpose. Set it to null to fall back to your organization's default pool."},"category":{"type":"string","enum":["marketing","transactional"],"description":"What kind of email this is. It decides two things: which suppressions apply, and whether we add an unsubscribe header.\n\n`marketing` is held back from every suppressed address and has the one-click unsubscribe headers. `transactional` still goes to addresses suppressed for a complaint or an unsubscribe, and has no unsubscribe header. Only use `transactional` for genuine operational mail such as a terms-of-service update or a service outage notice. Marketing content sent this way reaches people who have already unsubscribed from you.\n"}},"example":{"category":"marketing","template":{"id":"emt_01krdgeqcxet5s7t44vh8rt9mg"}}},"EmailBroadcastCounts":{"type":"object","additionalProperties":false,"required":["broadcast_id","status","total","addressable","sendable"],"description":"How many people a broadcast would reach right now, narrowing from everyone in the audience down to the ones it could actually be sent to.\n\nThese are live numbers, worked out at the moment you ask. Audience membership and suppressions change, so they can drift between now and when the broadcast sends.\n\n**They are about the audience, not about delivery, and sending does not change them.** Once the broadcast has sent, its own `recipient_count` is the number that actually went out, and what each of those recipients did with the message is in [the broadcast's recipients](/docs/api/reference/list-email-broadcast-recipients) and [its events](/docs/api/reference/list-email-broadcast-events). `status` is here so you can tell which question these numbers are answering, and `broadcast_id` names what they are about.\n","properties":{"broadcast_id":{"readOnly":true,"description":"The broadcast these counts are for.","allOf":[{"$ref":"#/components/schemas/EmailBroadcastID"}]},"status":{"readOnly":true,"example":"draft","description":"Where the broadcast is in its lifecycle, so the counts read in context. Anything past `draft` or `scheduled` means these numbers describe an audience the broadcast has already been sent to, not one it is about to reach.\n","allOf":[{"$ref":"#/components/schemas/EmailBroadcastStatus"}]},"next":{"type":"array","readOnly":true,"description":"What to do next, given where the broadcast is. On a broadcast that has already sent this names\nthe reads that carry delivery outcomes, which these counts never do. An empty list means there\nis nothing to do; the field is absent entirely on responses that do not report next actions.\n","items":{"$ref":"#/components/schemas/NextAction"}},"total":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":13000,"description":"How many contacts are in the audience."},"addressable":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":12500,"description":"How many of those contacts have an email address. A contact with no address is not counted. This is never higher than `total`."},"sendable":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":12000,"description":"How many of the addressable contacts are not suppressed for this broadcast's category, which is who the email would actually go to. This is never higher than `addressable`. Which suppressions apply depends on the category, so the same audience can give a higher number for a transactional broadcast than for a marketing one. A transactional broadcast still reaches people who unsubscribed from or complained about marketing mail, and a marketing broadcast does not.\n"}}},"EmailSendAllowanceWindow":{"type":"string","minLength":1,"enum":["none","monthly","daily"],"description":"Which of the organization's email send allowances stops a send from reaching its whole audience.\n\n- `none`: every recipient is covered.\n- `monthly`: the allowance that runs with the billing period.\n- `daily`: the allowance that resets at the end of each UTC day.\n\nWhen both apply, the tighter of the two is reported.\n"},"EmailBroadcastSendQuota":{"type":"object","additionalProperties":false,"required":["recipients","allowed","limited_by"],"description":"How much of a broadcast the organization's email send allowance covers, read before the broadcast is sent. The allowance is shared with every other email the organization sends, so `allowed` moves as those sends land, and `recipients` moves as audience membership and suppressions change. Treat both as a live estimate rather than a promise: a broadcast whose recipients all fit today can still run into the allowance if other sends consume it first.\n","properties":{"recipients":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":12000,"description":"Number of contacts the broadcast would send to right now, after contacts without an email address and contacts suppressed for the broadcast's category are dropped. The same number the broadcast's audience counts report as sendable.\n"},"allowed":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":5000,"description":"Number of those recipients the organization's email send allowance covers. Equal to `recipients` when nothing limits the send, and lower when part of the audience runs past what is left of it. A part-covered send goes out in whole batches, so this is cut back to a batch boundary rather than to the exact number of emails left: it can sit below `remaining` rather than matching it, and should be read rather than worked out from `limit` and `remaining`. 0 means none of them would go out, either because the audience is larger than the whole allowance, which is refused rather than sent in part, or because too little of the allowance is left to carry any of it.\n"},"limited_by":{"readOnly":true,"example":"monthly","$ref":"#/components/schemas/EmailSendAllowanceWindow"},"limit":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":50000,"description":"Size of the allowance named by `limited_by`, in emails. Omitted when nothing limits the send.\n"},"remaining":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":7000,"description":"How much of that allowance is left in the current window, in emails. Omitted when nothing limits the send.\n"}}},"EmailAudienceCounts":{"type":"object","additionalProperties":false,"required":["total","addressable","sendable"],"description":"How many contacts an audience has an address for, and how many of those a marketing email is effectively sent to, narrowing from every member down to the ones actually targeted. This is a live, point-in-time estimate: audience membership and suppressions change, so the counts can drift.\n","properties":{"total":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Number of contacts in the audience."},"addressable":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Number of contacts in the audience that have an email address, and so can receive email. Contacts without an email address are not counted. Always less than or equal to total.\n"},"sendable":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Number of addressable contacts that are not suppressed for marketing email, and so are effectively subscribed. Always less than or equal to addressable.\n"}}},"EmailBroadcastClickedLink":{"type":"object","additionalProperties":false,"required":["url","name","click_count","recipient_count"],"description":"One destination URL a broadcast's recipients clicked, with its exact click and recipient totals. Grouped over every click event the broadcast has, not a sample.\n","properties":{"url":{"type":"string","minLength":1,"readOnly":true,"example":"https://acme.com/whats-new/faster-exports","description":"The clicked URL."},"name":{"type":["string","null"],"readOnly":true,"description":"What the link said, resolved by the name used by the most clicks that carried one. Null when no click through this URL ever carried a name.\n","example":"Faster exports, docs"},"click_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":431,"description":"Total clicks through this URL, including clicks that carried no link name."},"recipient_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"example":388,"description":"Number of distinct recipients who clicked this URL at least once."}}},"EmailBroadcastClickedLinkList":{"type":"object","additionalProperties":false,"required":["data","total"],"properties":{"data":{"type":"array","description":"The broadcast's clicked URLs, most-clicked first, capped at 100 rows.","items":{"$ref":"#/components/schemas/EmailBroadcastClickedLink"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct URLs the broadcast's recipients clicked, regardless of the cap on `data`. When it exceeds the number of rows returned, the list was capped at the 100 most-clicked URLs.\n","example":57}}},"EmailBroadcastSendNowRequest":{"type":"object","additionalProperties":false,"properties":{"scheduled_at":{"type":"string","format":"date-time","description":"When to send the broadcast. It has to be at least 30 seconds and at most 365 days from now. Leave it out to send straight away."}},"example":{"scheduled_at":"2026-12-01T09:00:00Z"}},"ContactIdentifierFilter":{"type":"string","enum":["email","phone_number"],"description":"Which identifier a contact has on file, `email` for an email address or `phone_number` for a phone number."},"ContactID":{"type":"string","minLength":1,"pattern":"^con_[0-9a-hjkmnp-tv-z]{26}$","example":"con_01krdgeqcxet5s7t44vh8rt9mg"},"AudienceRef":{"type":"object","additionalProperties":false,"required":["id","name"],"description":"A compact reference to an audience, carrying its ID and display name.","properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AudienceID","description":"ID of the referenced audience."},"name":{"type":"string","minLength":1,"maxLength":100,"description":"The audience's display name."}}},"Contact":{"allOf":[{"type":"object","required":["id","email","phone_number","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ContactID","description":"ID of the contact, accepted by every operation that takes a `contact_id`."},"email":{"type":["string","null"],"format":"email","maxLength":254,"description":"The contact's email address, in its stored form, trimmed and lowercased before uniqueness is checked. Unique within the workspace. `null` when the contact has no email address."},"phone_number":{"type":["string","null"],"minLength":5,"maxLength":16,"description":"The contact's phone number in normalized international form: a leading `+` and four to 15 digits. We normalize formatting but do not verify the number against numbering-plan metadata. The number is unique within the workspace. Because carriers recycle disconnected numbers, use `external_id` as the durable key for your own records. `null` when the contact has no phone number."},"first_name":{"type":["string","null"],"maxLength":100,"description":"The contact's first name. Available in broadcast templates as `bird.contact.first_name`."},"last_name":{"type":["string","null"],"maxLength":100,"description":"The contact's last name. Available in broadcast templates as `bird.contact.last_name`."},"external_id":{"type":["string","null"],"maxLength":254,"description":"Your own identifier for this contact, such as a user ID in your system. Unique within the workspace when set."},"data":{"type":"object","additionalProperties":true,"description":"Custom property values for this contact, available in broadcast templates as `bird.contact.<key>`. Each key is a property created via the contact properties API, and each value is a string, number, boolean, or RFC 3339 datetime matching the property's declared type (strings up to `500` characters). Total size is capped at 2 KB serialized. Values stored under a property that was later archived remain readable here.\n"},"audiences":{"type":"array","readOnly":true,"description":"The audiences this contact belongs to, most-recently-joined first. Only present when listing contacts; omitted from every other contact operation.","items":{"$ref":"#/components/schemas/AudienceRef"}}}},{"$ref":"#/components/schemas/Timestamps"}]},"ContactList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of contact objects.","items":{"$ref":"#/components/schemas/Contact"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"ContactCreateRequest":{"type":"object","additionalProperties":false,"properties":{"email":{"type":"string","format":"email","maxLength":254,"description":"The contact's email address. Trimmed and lowercased before it is stored and checked for uniqueness. Unique within the workspace. Supply an email address, a phone number, or both."},"phone_number":{"type":"string","maxLength":32,"description":"The contact's phone number in E.164 format, including the leading `+` and country code. Spaces and punctuation are accepted and stripped; the number is stored in its canonical form, which may differ from what you send, and is unique within the workspace. An empty string is treated as if the field were omitted. Supply an email address, a phone number, or both.","example":"+31612345678"},"first_name":{"type":"string","maxLength":100,"description":"The contact's first name."},"last_name":{"type":"string","maxLength":100,"description":"The contact's last name."},"external_id":{"type":"string","maxLength":254,"description":"Your own identifier for this contact, such as a user ID in your system. Unique within the workspace when set."},"data":{"type":"object","additionalProperties":true,"description":"Custom property values for this contact. Each key must be an active contact property. Each value must match the property's declared type: string, number, boolean, or RFC 3339 datetime. Strings can contain up to `500` characters, and a `null` value is ignored. Unregistered or archived keys return a validation error. The serialized data is limited to 2 KB."}},"example":{"email":"alice@acme.com","phone_number":"+31612345678","first_name":"Alice","last_name":"Anderson"}},"ContactMatchKey":{"type":"string","enum":["email","phone_number","external_id"],"description":"A contact identifier a batch entry can be matched on."},"ContactUpsertRequest":{"type":"object","additionalProperties":false,"required":["contacts"],"properties":{"contacts":{"type":"array","minItems":1,"maxItems":1000,"items":{"$ref":"#/components/schemas/ContactCreateRequest"},"description":"Contacts to create or update, matched automatically against every identifier an entry supplies. Existing contacts are updated with the fields each entry supplies; omitted fields keep their stored values, so an entry can set fields but never clear them. Unmatched entries create contacts."},"audience_ids":{"type":"array","minItems":1,"maxItems":10,"items":{"$ref":"#/components/schemas/AudienceID"},"description":"Audiences every contact in this request is added to. Contacts that are already members are left in place. Every listed audience must exist, or the whole request fails with a validation error and nothing is written."},"match_on":{"$ref":"#/components/schemas/ContactMatchKey","description":"Optional field used to match every entry to an existing contact. Every entry must include this field when set. When omitted, each entry is matched against all identifiers it supplies. No match creates a contact, one match updates it, and identifiers that match multiple contacts return an error naming each contact."},"data_mode":{"type":"string","enum":["merge","replace"],"default":"merge","description":"How a supplied `data` object is applied to an existing contact. The default `merge` mode adds the supplied keys to the contact's stored custom values. A key with a `null` value deletes that key. The `replace` mode overwrites the whole stored `data` map with the supplied map. In both modes a contact that omits `data` keeps its stored values unchanged, so an import that touches one attribute never wipes the others.\n"}},"example":{"contacts":[{"email":"alice@acme.com","first_name":"Alice","last_name":"Anderson"},{"email":"bob@acme.com","first_name":"Bob","last_name":"Baker"}],"audience_ids":["adn_01krdgeqcxet5s7t44vh8rt9mg"]}},"ContactUpsertEntry":{"type":"object","additionalProperties":false,"required":["email","phone_number","external_id"],"description":"The identifiers a batch entry supplied, in the normalized form used for matching. A field is `null` when the entry did not include it. These values identify the request entry and do not represent the contact's current state.","properties":{"email":{"type":["string","null"],"description":"Email address this entry carried, trimmed and lowercased. `null` when the entry carried none."},"phone_number":{"type":["string","null"],"description":"Phone number this entry carried, in its normalized international form. `null` when the entry carried none. A row rejected for an invalid phone echoes the value as sent, trimmed, since no normalized form exists."},"external_id":{"type":["string","null"],"description":"Your own identifier for this entry, when the entry supplied one."}}},"ContactMatchedOn":{"type":["string","null"],"enum":["email","phone_number","external_id",null],"description":"Which identifier matched a batch entry to an existing contact. `null` when the entry created a new contact."},"ContactUpsertError":{"type":"object","additionalProperties":false,"required":["type","code","message"],"properties":{"type":{"type":"string","minLength":1,"description":"Machine-readable error category for this entry, such as `validation_error` or `conflict_error`, in the same vocabulary as the top-level error `type`. New categories may be added over time, so treat unrecognized values as a generic failure."},"code":{"type":"string","minLength":6,"pattern":"^E\\d{5}$","description":"Specific error code for this entry, from the same catalog as the top-level error `code`. `E04058` means the entry matched two contacts and requires review. `E04055` means the phone number belongs to another contact and you must retry with different data. Both are `conflict_error` errors; the code distinguishes them."},"message":{"type":"string","minLength":1,"description":"Human-readable explanation of why this entry failed."}}},"ContactUpsertResultItem":{"type":"object","additionalProperties":false,"required":["entry","matched_on","status"],"properties":{"entry":{"$ref":"#/components/schemas/ContactUpsertEntry"},"matched_on":{"$ref":"#/components/schemas/ContactMatchedOn","description":"Which identifier matched this entry to an existing contact. `null` when the entry created a new contact."},"status":{"type":"string","minLength":1,"enum":["created","updated","failed"],"description":"What happened to this contact.\n\n- `created`: a new contact was created for the address.\n- `updated`: an existing contact with the address was updated.\n- `failed`: the entry was rejected and `error` explains why. A failed entry\n  does not affect the other entries in the request.\n"},"contact_id":{"$ref":"#/components/schemas/ContactID","description":"ID of the created or updated contact. Absent when the entry failed."},"error":{"$ref":"#/components/schemas/ContactUpsertError","description":"Why this entry failed. Absent for successful entries."}}},"ContactUpsertResult":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactUpsertResultItem"},"description":"One entry per contact in the request, in submission order."}}},"ContactUpdateRequest":{"type":"object","additionalProperties":false,"properties":{"email":{"type":["string","null"],"format":"email","maxLength":254,"description":"New email address for the contact. Trimmed and lowercased before it is stored and checked for uniqueness. Must not be in use by another contact in the workspace. Omit to keep the current address; set to `null` to remove it, as long as the contact keeps at least one identifier."},"phone_number":{"type":["string","null"],"maxLength":32,"description":"New phone number for the contact, in E.164 format with the leading `+` and country code. Spaces and punctuation are accepted and stripped. Stored in its canonical form, which may differ from what you send, and unique within the workspace. Omit to keep the current number; set to `null` to remove it, as long as the contact keeps at least one identifier. An empty string behaves as `null`."},"first_name":{"type":["string","null"],"maxLength":100,"description":"The contact's first name. Set to `null` to clear."},"last_name":{"type":["string","null"],"maxLength":100,"description":"The contact's last name. Set to `null` to clear."},"external_id":{"type":["string","null"],"maxLength":254,"description":"Your own identifier for this contact. Unique within the workspace when set. Set to `null` to clear."},"data":{"type":"object","additionalProperties":true,"description":"Custom property values to merge into the contact's existing data. Supplied keys are set, keys with a `null` value are removed, and omitted keys remain unchanged. Each key must be an active contact property. Each value must match the property's declared type: string, number, boolean, or RFC 3339 datetime. Strings can contain up to `500` characters. An unregistered or archived key returns a validation error. The serialized result is limited to 2 KB."}},"example":{"first_name":"Alice","last_name":"Anderson"}},"Audience":{"allOf":[{"type":"object","required":["id","name","type","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AudienceID","description":"ID of the audience, accepted by every operation that takes an `audience_id`."},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Display name for the audience."},"description":{"type":["string","null"],"maxLength":500,"description":"Longer description of who this audience is."},"type":{"type":"string","minLength":1,"enum":["static"],"x-enum-varnames":["AudienceTypeStatic"],"default":"static","description":"How the audience's recipients are determined. `static` is an explicit member list you manage by adding and removing contacts."}}},{"$ref":"#/components/schemas/Timestamps"}]},"AudienceList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of audience objects.","items":{"$ref":"#/components/schemas/Audience"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"PreferenceID":{"type":"string","minLength":1,"pattern":"^prf_[0-9a-hjkmnp-tv-z]{26}$","example":"prf_01krdgeqcxet5s7t44vh8rt9mg"},"PreferenceChannel":{"type":"string","minLength":1,"description":"The channel a preference statement applies to. A preference addresses one channel: the handle that identifies the person differs per channel, so opting out of one channel says nothing about the others. New channels can be added over time, so a value outside this list can be returned.","x-extensible-enum":["email","sms","whatsapp"],"example":"sms"},"PreferenceStatus":{"type":"string","minLength":1,"description":"What the statement says: `granted` records consent to receive messages, `revoked` records an opt-out. There is no third state: a person who never stated anything simply has no preference on record.","enum":["granted","revoked"],"example":"revoked"},"PreferenceCoverage":{"type":"string","minLength":1,"description":"How much traffic the statement covers. `non_transactional` covers marketing and other non-essential messages while transactional messages such as receipts and verification codes keep flowing; `all` covers every message including transactional ones.","enum":["all","non_transactional"],"example":"non_transactional"},"PreferenceOrigin":{"type":"string","minLength":1,"description":"How the statement was made. Statements the person made themselves (`unsubscribe_link`, `unsubscribe_event`, `keyword`, `preference_page`) carry more weight than ones asserted on their behalf (`api_key`, `user`, `import`): a person's own opt-out cannot be overridden or deleted through this API. New origins can be added over time, so a value outside this list can be returned.","x-extensible-enum":["unsubscribe_link","unsubscribe_event","keyword","preference_page","api_key","user","import"],"example":"api_key"},"Preference":{"description":"One person's current stated preference for one channel: whether they have consented to or opted out of receiving messages at a handle, and how much traffic the statement covers. A key holds one current statement (a newer statement replaces it, an older one is refused), and the full history behind the current state is kept internally. `sender_scope` and `topic_id` are part of the key: a preference with both null applies channel-wide.\n","allOf":[{"type":"object","required":["id","channel","handle","sender_scope","topic_id","status","coverage","effective_at","origin"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PreferenceID"},"channel":{"allOf":[{"$ref":"#/components/schemas/PreferenceChannel"}],"readOnly":true},"handle":{"type":"string","minLength":1,"maxLength":320,"readOnly":true,"description":"Who the statement is about: an email address on the email channel, a phone number in E.164 format on SMS and WhatsApp.","example":"+15550001234"},"sender_scope":{"type":["string","null"],"readOnly":true,"description":"The sender the statement is limited to, or null when it covers the whole channel. On SMS this is the originator the person replied to; on WhatsApp it identifies the business account that messaged them. Email preferences are always channel-wide, so it is always null there.","example":"+15557654321"},"topic_id":{"type":["string","null"],"readOnly":true,"description":"The topic the statement is limited to, or null when it covers every topic. Part of the key that identifies a statement, alongside `sender_scope`.","example":null},"status":{"allOf":[{"$ref":"#/components/schemas/PreferenceStatus"}],"readOnly":true},"coverage":{"allOf":[{"$ref":"#/components/schemas/PreferenceCoverage"}],"readOnly":true},"effective_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the statement was made, as reported by whoever made it. This is what orders one key's statements: a write dated before this moment is refused rather than applied."},"origin":{"allOf":[{"$ref":"#/components/schemas/PreferenceOrigin"}],"readOnly":true},"source":{"type":["string","null"],"maxLength":255,"readOnly":true,"description":"Free-form note on where the statement came from, as supplied when it was recorded: a form name, an import batch, a campaign. Null when none was given.","example":"signup-form-v2"},"consented_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the person consented, as evidenced by whoever asserted the grant. Null on statements that carry no consent evidence, including every opt-out."},"contact_id":{"oneOf":[{"$ref":"#/components/schemas/ContactID"},{"type":"null"}],"readOnly":true,"description":"The contact whose handle matched when the statement was recorded. Null when no contact matched at that moment; it is not updated when contacts change later."}}},{"$ref":"#/components/schemas/Timestamps"}]},"PreferenceList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of preferences, most recently created first.","items":{"$ref":"#/components/schemas/Preference"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"PreferenceStatement":{"type":"object","required":["channel","status"],"properties":{"channel":{"$ref":"#/components/schemas/PreferenceChannel"},"status":{"$ref":"#/components/schemas/PreferenceStatus"},"coverage":{"allOf":[{"$ref":"#/components/schemas/PreferenceCoverage"}],"default":"non_transactional","description":"How much traffic the statement covers. Defaults to `non_transactional`, which keeps transactional messages such as receipts and verification codes flowing."},"sender_scope":{"type":"string","minLength":1,"maxLength":255,"description":"Limit the statement to one sender instead of the whole channel. On SMS this is the originator; on WhatsApp it identifies the business account. Not supported on email, where preferences are always channel-wide.","example":"+15557654321"},"source":{"type":"string","minLength":1,"maxLength":255,"description":"Free-form note on where the statement came from: a form name, an import batch, a campaign. Stored verbatim and returned on the preference.","example":"signup-form-v2"},"consented_at":{"type":"string","format":"date-time","description":"When the person consented, on a `granted` statement. Required evidence when granting over a stored opt-out: the grant applies only if this is later than the opt-out it reverses. May not be in the future."}}},"PreferenceCreate":{"description":"Records one preference statement for a handle. Writing is an upsert by key (channel, handle, sender scope, and topic), and statements are causally ordered: a statement dated older than the key's current one is refused and returned with `applied: false` rather than applied out of order.\n","allOf":[{"type":"object","required":["handle"],"properties":{"handle":{"type":"string","minLength":1,"maxLength":320,"description":"Who the statement is about: an email address on the email channel, a phone number in E.164 format on SMS and WhatsApp.","example":"+15550001234"}}},{"$ref":"#/components/schemas/PreferenceStatement"}]},"PreferenceTransitionID":{"type":"string","minLength":1,"pattern":"^prt_[0-9a-hjkmnp-tv-z]{26}$","example":"prt_01krdgeqcxet5s7t44vh8rt9mg"},"PreferenceWriteResult":{"type":"object","additionalProperties":false,"description":"The outcome of one preference write or delete. `applied: true` means the request took effect: either it changed the record, or an identical statement already had. `applied: false` means it was refused as older than the key's current statement; `preference` then carries the statement that survived, and `transition_id` identifies the refusal on the key's record.\n","required":["applied","transition_id","preference"],"properties":{"applied":{"type":"boolean","readOnly":true,"description":"Whether the request took effect. False only when it was refused as out of order; the surviving, newer statement is returned in `preference`."},"transition_id":{"oneOf":[{"$ref":"#/components/schemas/PreferenceTransitionID"},{"type":"null"}],"readOnly":true,"description":"Identifies this write on the key's record, for applied and refused requests alike. Null when the write was a repeat of the current statement and recorded nothing new."},"preference":{"oneOf":[{"$ref":"#/components/schemas/Preference"},{"type":"null"}],"readOnly":true,"description":"The key's surviving statement. Null after an applied delete, when the key is back to having no record."}}},"PreferenceBulkCreate":{"type":"object","additionalProperties":false,"required":["statements"],"properties":{"statements":{"type":"array","minItems":1,"maxItems":1000,"items":{"$ref":"#/components/schemas/PreferenceCreate"},"description":"Statements to record, one row per preference write, in the same shape as `POST /v1/preferences`. Each row is applied independently through the same ordering rules as a single write, and reported at the same position in the response. Rows written through this endpoint are recorded with origin `import`."}},"example":{"statements":[{"channel":"sms","handle":"+15550001234","status":"revoked","coverage":"non_transactional"},{"channel":"email","handle":"alice@example.com","status":"granted","consented_at":"2026-01-15T10:00:00Z"}]}},"PreferenceBulkOutcome":{"type":"string","minLength":1,"x-extensible-enum":["created","unchanged","not_applied","error"],"description":"What happened to this row.\n\n- `created`: applied, and this statement changed the key's current record (a fresh\n  transition, whether the key had no prior record or replaced an existing one).\n- `unchanged`: applied, but this statement repeated the key's current one. Nothing changed.\n- `not_applied`: refused as older than the key's current statement. The key's current\n  statement is unaffected; `preference` returns the statement that survived.\n- `error`: the row itself was invalid and nothing was recorded; see `error`.\n\nThis list may grow over time. Treat an unrecognized value as informational rather than a\nreason to reject the response.\n"},"ErrorType":{"type":"string","minLength":1,"description":"Machine-readable error category for coarse client branching, the closed vocabulary shared by the top-level error `type` and per-row bulk errors.","enum":["auth_error","bad_request_error","billing_error","conflict_error","gone_error","internal_error","misdirected_error","not_found_error","not_implemented_error","payload_too_large_error","permission_error","precondition_error","rate_limit_error","service_unavailable_error","too_early_error","validation_error"]},"BulkRowError":{"type":"object","additionalProperties":false,"required":["type","code","message"],"properties":{"type":{"$ref":"#/components/schemas/ErrorType","description":"Machine-readable error category for this row, from the same closed vocabulary as the top-level error `type`."},"code":{"type":"string","minLength":6,"pattern":"^E\\d{5}$","description":"Specific error code for this row, from the same catalog as the top-level error `code`. The code distinguishes cases a coarse `type` cannot, such as two different reasons a row is invalid."},"message":{"type":"string","minLength":1,"description":"Human-readable explanation of why this row failed."}}},"PreferenceBulkResultItem":{"type":"object","description":"One row's result. Which optional fields accompany the outcome is stated per field rather than encoded as a `oneOf`: `outcome` is an extensible enum, so a closed discriminated union would turn every future outcome into a breaking change.","additionalProperties":false,"required":["outcome"],"properties":{"outcome":{"$ref":"#/components/schemas/PreferenceBulkOutcome"},"preference":{"$ref":"#/components/schemas/Preference","description":"The key's surviving statement after this row was applied. Absent when the row errored before anything could be recorded."},"transition_id":{"$ref":"#/components/schemas/PreferenceTransitionID","description":"Identifies this write on the key's record. Absent when the row errored, or when it repeated the key's current statement and recorded nothing new."},"error":{"$ref":"#/components/schemas/BulkRowError","description":"Why this row errored. Absent for every other outcome."}}},"PreferenceBulkResult":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PreferenceBulkResultItem"},"description":"One entry per statement in the request, in submission order."}}},"ContactPropertyID":{"type":"string","minLength":1,"pattern":"^prp_[0-9a-hjkmnp-tv-z]{26}$","example":"prp_01krdgeqcxet5s7t44vh8rt9mg"},"ContactPropertyType":{"type":"string","minLength":1,"enum":["string","number","boolean","datetime"],"x-enum-varnames":["ContactPropertyTypeString","ContactPropertyTypeNumber","ContactPropertyTypeBoolean","ContactPropertyTypeDatetime"],"description":"The value type every contact must use for a property. Cannot be changed after creation.\n\n`datetime` values are RFC 3339 timestamps with an explicit offset. Examples include `2024-01-15T09:30:00Z` and `2024-01-15T11:30:00+02:00`. A bare date or a time with no offset is rejected. The value is normalized to UTC with second precision on write, so `2024-01-15T11:30:00+02:00` is stored and returned as `2024-01-15T09:30:00Z`, and any fractional seconds are dropped.\n"},"ContactProperty":{"allOf":[{"type":"object","required":["id","key","type","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ContactPropertyID","description":"ID of the property, accepted by every operation that takes a `property_id`."},"key":{"type":"string","minLength":1,"maxLength":50,"pattern":"^[a-z][a-z0-9_]*$","description":"The property key, used as the key in contact data and as the attribute in the `bird.contact.<key>` broadcast template variable. Lowercase letters, digits, and underscores, starting with a letter. Cannot be changed after creation."},"type":{"$ref":"#/components/schemas/ContactPropertyType"},"fallback_value":{"maxLength":500,"description":"Default used when a contact has no value for this property and the template does not supply an inline fallback. A string, number, boolean, or RFC 3339 datetime matching the declared type (strings up to `500` characters), or `null` when no fallback is set."},"archived":{"type":"boolean","readOnly":true,"description":"Whether the property is archived. An archived property is rejected in new contact writes and stops rendering in templates, but every value already stored on contacts is preserved. Reactivate it with unarchive."}}},{"$ref":"#/components/schemas/Timestamps"}]},"ContactPropertyList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of contact property objects.","items":{"$ref":"#/components/schemas/ContactProperty"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"ContactPropertyCreateRequest":{"type":"object","additionalProperties":false,"required":["key","type"],"properties":{"key":{"type":"string","minLength":1,"maxLength":50,"pattern":"^[a-z][a-z0-9_]*$","description":"The property key, used as the key in contact data and as the attribute in the `bird.contact.<key>` broadcast template variable. Lowercase letters, digits, and underscores, starting with a letter. Cannot be changed after creation."},"type":{"$ref":"#/components/schemas/ContactPropertyType"},"fallback_value":{"maxLength":500,"description":"Default used when a contact has no value for this property and the template does not supply an inline fallback. A string, number, boolean, or RFC 3339 datetime matching the declared type (strings up to `500` characters), or `null` for no fallback; a value of another type returns a validation error."}},"example":{"key":"plan","type":"string","fallback_value":"free"}},"ContactPropertyUpdateRequest":{"type":"object","additionalProperties":false,"properties":{"fallback_value":{"maxLength":500,"description":"Default used when a contact has no value for this property and the template does not supply an inline fallback. A string, number, boolean, or RFC 3339 datetime matching the declared type (strings up to `500` characters); a value of another type returns a validation error. Set to `null` to remove the fallback."}},"example":{"fallback_value":"free"}},"AudienceCreateRequest":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Display name for the audience."},"description":{"type":"string","maxLength":500,"description":"Longer description of who this audience is."},"type":{"type":"string","enum":["static"],"x-enum-varnames":["AudienceTypeStatic"],"default":"static","description":"How the audience's recipients are determined. `static` is an explicit member list you manage by adding and removing contacts."}},"example":{"name":"Newsletter subscribers","description":"Contacts who opted into the monthly product newsletter"}},"AudienceUpdateRequest":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"New display name for the audience. Omit to keep the current name. The name cannot be cleared, and a whitespace-only value returns a validation error."},"description":{"type":["string","null"],"maxLength":500,"description":"Longer description of who this audience is. Set to null to clear."}},"example":{"name":"Newsletter subscribers"}},"AudienceMember":{"type":"object","additionalProperties":false,"required":["contact","joined_at"],"properties":{"contact":{"$ref":"#/components/schemas/Contact"},"joined_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this contact joined the audience. Members are listed in join order, most recent first."},"audiences":{"type":"array","readOnly":true,"description":"The audiences this contact belongs to, including the one being listed, most-recently-joined first.","items":{"$ref":"#/components/schemas/AudienceRef"}}}},"AudienceMemberList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of audience members, each a contact paired with the time it joined the audience.","items":{"$ref":"#/components/schemas/AudienceMember"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AudienceContactsAddRequest":{"type":"object","additionalProperties":false,"required":["contact_ids"],"properties":{"contact_ids":{"type":"array","minItems":1,"maxItems":1000,"items":{"$ref":"#/components/schemas/ContactID"},"description":"Contacts to add to the audience. Adding a contact that is already a member has no effect and keeps its original join time. Duplicate IDs in the list are collapsed. If any ID does not exist in the workspace, the whole request fails with a validation error and no contacts are added."}},"example":{"contact_ids":["con_01krdgeqcxet5s7t44vh8rt9mg"]}},"AudienceContactsRemoveRequest":{"type":"object","additionalProperties":false,"required":["contact_ids"],"properties":{"contact_ids":{"type":"array","minItems":1,"maxItems":1000,"items":{"$ref":"#/components/schemas/ContactID"},"description":"Contacts to remove from the audience. Removing a contact that is not a member has no effect. Duplicate IDs in the list are collapsed. If any ID does not exist in the workspace, the whole request fails with a validation error and no memberships are removed."}},"example":{"contact_ids":["con_01krdgeqcxet5s7t44vh8rt9mg"]}},"MessageDirection":{"type":"string","enum":["outbound","inbound"],"description":"Whether a message was sent from the workspace (`outbound`) or received by it (`inbound`)."},"SMSMessageCategory":{"type":"string","minLength":1,"enum":["transactional","marketing","authentication","service"],"description":"Content classification: why you are sending. Carriers see it, and where a destination country requires the sender to be registered, that registration is approved for a category: a send outside what it covers returns a `422` `SenderCategoryNotPermitted`. A registration approved for `marketing` covers all four values; one approved for `transactional`, `authentication`, or `service` covers those three and not `marketing`. Use `authentication` for a one-time passcode and `marketing` for a promotion; otherwise pick the value matching the message's purpose."},"SMSMessageID":{"type":"string","minLength":1,"pattern":"^sms_[0-9a-hjkmnp-tv-z]{26}$","example":"sms_01krdgeqcxet5s7t44vh8rt9mg"},"SMSMessageStatus":{"type":"string","minLength":1,"enum":["scheduled","accepted","sent","delivered","undelivered","failed","rejected","canceled","expired","received"],"description":"Delivery status:\n\n- `accepted`: Accepted and awaiting carrier handoff.\n- `sent`: Handed to the carrier and awaiting a delivery receipt.\n- `delivered`: Confirmed as delivered.\n- `undelivered`: The carrier reported delivery as failed for a reason that may clear later, such as a handset out of coverage or a carrier at capacity. Final all the same: the message is not retried, so reaching the recipient means sending again.\n- `failed`: The carrier reported delivery as failed for a reason that will not clear, such as an unassigned number, a recipient who has opted out, or content the carrier refused.\n- `rejected`: Refused before carrier handoff.\n- `expired`: Reached its validity limit without a final receipt.\n- `received`: Received as an inbound message.\n\n`scheduled` and `canceled` are declared ahead of the send-later scheduling\nfeature that produces them, so their arrival is not a breaking change. No\nmessage carries either status today.\n"},"SMSTemplateID":{"type":"string","minLength":1,"pattern":"^smt_[0-9a-hjkmnp-tv-z]{26}$","example":"smt_01krdgeqcxet5s7t44vh8rt9mg"},"SMSTemplateVersionID":{"type":"string","minLength":1,"pattern":"^smv_[0-9a-hjkmnp-tv-z]{26}$","example":"smv_01krdgeqcxet5s7t44vh8rt9mg"},"SMSTemplateContentHash":{"type":"string","minLength":1,"readOnly":true,"description":"A fingerprint of SMS template text, prefixed with its algorithm. Compare it within this API version to identify the exact source without transferring it.\n","example":"sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"},"SMSSegments":{"type":"object","additionalProperties":false,"required":["count","encoding","characters"],"description":"Segment breakdown for the message body. Segment count drives billing.","properties":{"count":{"type":"integer","minimum":1,"readOnly":true,"description":"Number of segments the body is split into. Each segment is a billable unit."},"encoding":{"type":"string","minLength":1,"readOnly":true,"enum":["GSM_7BIT","UCS2"],"description":"Encoding used for the body. The `GSM_7BIT` encoding fits 160 septets\n(seven-bit units) in one segment, or 153 per part in a multi-segment\nmessage. The `UCS2` encoding applies when the body contains a character\noutside the GSM 03.38 alphabet, including emoji, CJK, and some accented\ncharacters. It fits 70 UTF-16 code units in one segment, or 67 per part.\n\nNeither limit counts characters, and both alphabets have characters that\ncost two units. Under `GSM_7BIT` there are ten such entries, and they are\nthe whole set: `^`, `{`, `}`, `\\`, `[`, `]`, `~`, `|`, `€`, and the form\nfeed control. Eighty of those fill a single segment. Under `UCS2` an emoji\noutside the Basic Multilingual Plane is a surrogate pair costing two code\nunits, so 35 of those fill a single segment.\n"},"characters":{"type":"integer","minimum":0,"readOnly":true,"description":"Character count of the body, counted in Unicode code points under either encoding. This is not the segment measure: a `GSM_7BIT` extended-table character counts once here but costs two septets, and a `UCS2` emoji outside the Basic Multilingual Plane counts once here but costs two of the segment's 70 code units.\n"}}},"MessageCost":{"type":["object","null"],"additionalProperties":false,"required":["amount","currency_code","transaction_amount","passthrough_amount"],"description":"What was charged for a message, split into the components that make it up. `null` until at least one component has been priced.\n","properties":{"amount":{"type":"string","minLength":1,"readOnly":true,"description":"Total charged, as a decimal string: the sum of the components below. Net of tax, which applies to your wallet balance rather than to an individual charge.\n","example":"0.00990"},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code. Every component is denominated in this currency.","example":"USD"},"transaction_amount":{"type":["string","null"],"readOnly":true,"description":"What we charged to carry the message, as a decimal string. `null` when this component was not priced; `\"0.00000\"` when it priced at zero.\n","example":"0.00790"},"passthrough_amount":{"type":["string","null"],"readOnly":true,"description":"Third-party fees we pass on, as a decimal string, such as US 10DLC carrier surcharges. `null` when this component was not priced; `\"0.00000\"` when it priced at zero.\n","example":"0.00200"}}},"SMSMessageEffectiveOptions":{"type":"object","additionalProperties":false,"description":"The settings Bird applied to this message. Every option is reported, whether you set it on the send or took the default that was in force at the time.\n","required":["smart_encoding"],"properties":{"smart_encoding":{"type":"boolean","description":"Whether Bird replaced characters outside the GSM-7 alphabet in this message's body with their closest equivalent before sending it. When `true`, `text` is the body as sent and `segments` describes that body.\n"}},"example":{"smart_encoding":true}},"SMSErrorCode":{"type":"string","minLength":1,"x-extensible-enum":["invalid_destination","unreachable","blocked_by_carrier","blocked_by_recipient","landline_unreachable","content_rejected","sender_unregistered","recipient_opted_out","provider_unavailable","insufficient_balance","unknown"],"description":"Standardized failure reason:\n\n- `invalid_destination`: The number is unassigned, ported out, or malformed.\n- `unreachable`: The handset is off or outside coverage.\n- `blocked_by_carrier`: The carrier filtered the message.\n- `blocked_by_recipient`: The recipient device blocked the sender.\n- `landline_unreachable`: The destination is a landline that does not accept SMS.\n- `content_rejected`: The carrier rejected the content.\n- `sender_unregistered`: The sender is not registered for the destination.\n- `recipient_opted_out`: The recipient is on a suppression list.\n- `provider_unavailable`: The provider remained unavailable after retries.\n- `insufficient_balance`: The workspace wallet could not fund the send.\n- `unknown`: The failure could not be classified.\n\nThis is an open enum. Accept unrecognized values.\n"},"SMSError":{"type":["object","null"],"additionalProperties":false,"readOnly":true,"required":["code","description","occurred_at"],"description":"Failure detail for a message that could not be delivered or was rejected.","properties":{"code":{"$ref":"#/components/schemas/SMSErrorCode"},"description":{"type":"string","minLength":1,"description":"The failure in words, from whatever refused the message: the carrier's own reason text on a delivery receipt, or ours on a message stopped before a carrier saw it. Free-form, so branch on `code` and show this to a human.","example":"Carrier filtered as spam"},"carrier_error_code":{"type":["string","null"],"description":"Raw provider-supplied error code, finer-grained than the `code` that normalizes it. Not a Bird-defined value, so quote it to support when asking why a message failed. Null when the provider sent none, including any failure decided before one was reached."},"occurred_at":{"type":"string","format":"date-time","minLength":1,"description":"When the failure occurred."}}},"SMSMedia":{"type":"object","additionalProperties":false,"description":"A media attachment on a received MMS. Bird records what arrived, its media type and the sender's filename, so you can tell an attachment came in and what it is.\n","required":["mime_type"],"properties":{"mime_type":{"type":"string","minLength":1,"readOnly":true,"description":"Media type of the attachment, as reported by the carrier.","example":"image/jpeg"},"filename":{"type":["string","null"],"readOnly":true,"description":"Filename the sender's device supplied, when it supplied one.","example":"photo.jpg"},"url":{"type":["string","null"],"readOnly":true,"description":"Where to download the attachment. Currently always null: Bird reports that an attachment arrived but does not yet host its contents.\n"}}},"SMSMessage":{"type":"object","additionalProperties":false,"required":["id","direction","status","to","from","segments","requested_language","resolved_language","template_id","template_version_id","template_content_hash","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SMSMessageID","description":"ID of the message, assigned when the send is accepted. Pass it as `message_id` to the get-message endpoint.\n"},"direction":{"type":"string","minLength":1,"readOnly":true,"enum":["outbound","inbound"],"description":"Whether the message was sent from a Bird sender (`outbound`) or received from a subscriber (`inbound`)."},"status":{"allOf":[{"$ref":"#/components/schemas/SMSMessageStatus"}],"readOnly":true},"to":{"type":"string","minLength":1,"description":"Where the message went. On an outbound message this is the recipient's phone number in E.164 format; on an inbound one it is your own number that received it.\n","example":"+15551234567"},"from":{"type":"string","minLength":1,"description":"Where the message came from. On an outbound message this is the sender you sent it from: an E.164 number, an alphanumeric sender ID, or a short code. On an inbound message, this is the phone number that sent it to you.\n","example":"+15557654321"},"text":{"type":"string","minLength":1,"description":"The message body. Every message carries body text, attachments, or both, so this is absent only on a received message that carried attachments and no text. For a template send, this is the rendered text after parameter substitution. When `category` is `authentication` (a message carrying a one-time code), this is `**REDACTED**`: the code still reaches the recipient, but the API does not retain it for later reads.\n","example":"Your order has shipped and is on its way."},"category":{"oneOf":[{"$ref":"#/components/schemas/SMSMessageCategory"},{"type":"null"}],"description":"Content classification supplied for free text or derived from the template. Null for inbound messages."},"requested_language":{"readOnly":true,"description":"The template language requested by the send, in canonical form. Null when the send named no language or used no template.\n","oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}]},"resolved_language":{"readOnly":true,"description":"The template language whose text was rendered, in canonical form. Null when the send used no template. This can differ from `requested_language` when the template's fallback policy selects another language.\n","oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}]},"template_id":{"readOnly":true,"description":"The template rendered for this message, or null for a free-text message.","oneOf":[{"$ref":"#/components/schemas/SMSTemplateID"},{"type":"null"}]},"template_version_id":{"readOnly":true,"description":"The workspace published version or synthetic built-in version rendered at acceptance, or null for a free-text message. For a built-in template, `template_content_hash` identifies the exact catalogue source.\n","oneOf":[{"$ref":"#/components/schemas/SMSTemplateVersionID"},{"type":"null"}]},"template_content_hash":{"readOnly":true,"description":"The rendered language's source fingerprint, or null for a free-text message.","oneOf":[{"$ref":"#/components/schemas/SMSTemplateContentHash"},{"type":"null"}]},"segments":{"$ref":"#/components/schemas/SMSSegments","description":"Segment breakdown for the body."},"cost":{"$ref":"#/components/schemas/MessageCost","description":"What the message cost, split into Bird's charge and any third-party fees passed through. Null until the message has been priced."},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"description":"Structured `{name, value}` filter labels applied to this message."},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary JSON metadata stored on the message and echoed in webhook payloads."},"options":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSMessageEffectiveOptions"}],"description":"The settings applied to this message, with any option you omitted filled in with the default in force when you sent it. Absent on inbound messages, and on any outbound message for which no settings were recorded.\n"},"validity_period":{"type":"integer","readOnly":true,"description":"Preview feature: how long, in seconds, the carrier may keep attempting delivery before the message is marked `expired`. Not returned yet."},"carrier":{"type":"string","readOnly":true,"description":"Carrier that handled the message. Absent until a delivery receipt identifies it, and on a received message the carrier reports it only where a carrier fee applies.","example":"Verizon"},"mcc_mnc":{"type":"string","readOnly":true,"description":"Mobile country code and mobile network code of the carrier. Absent until the carrier is identified.","example":"311480"},"last_error":{"$ref":"#/components/schemas/SMSError","description":"Failure detail on a message that failed, was rejected, was not delivered, or expired. Absent otherwise."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the message was accepted (outbound) or received (inbound)."},"sent_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the message was handed to the carrier. Null until then."},"delivered_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When delivery was confirmed. Null until then."}}},"SMSMessageList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of SMS messages, newest first.","items":{"$ref":"#/components/schemas/SMSMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"SMSSendOptions":{"type":"object","additionalProperties":false,"description":"Settings that change how Bird processes this message. Each option applies to this send only; omit one to use its default.\n","properties":{"smart_encoding":{"type":"boolean","default":false,"description":"Replace characters outside the GSM-7 alphabet with their closest GSM-7 equivalent before sending: typically curly quotes, dashes, ellipses, fullwidth forms, and non-breaking spaces.\n\nOne such character forces the whole body into `UCS2`, which more than halves the characters that fit in a segment, so replacing them often lowers the segment count and the cost.\n\nDisabled by default, because it alters the body you composed. The replacement is all-or-nothing: a body that still holds a character outside the alphabet afterwards, such as an emoji or a non-Latin script, is sent exactly as you supplied it. Read the message back to see what was applied: `text` is the body as sent.\n"},"track_clicks":{"type":"boolean","description":"Preview feature: link click tracking. Defaults to `false`. Currently unavailable; setting this to `true` returns `422 SMSUnsupportedFeature`."},"max_price_per_segment":{"type":"number","description":"Preview feature: per-segment price ceiling. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`."}},"example":{"smart_encoding":true}},"SMSTemplateSend":{"type":"object","additionalProperties":false,"description":"A send-by-template reference. Identify the template by `id` or `slug`, or use the deprecated `name` for a legacy built-in template. Supply exactly one reference, optionally select a language, and pass variable values in `parameters`.\n","oneOf":[{"required":["id"]},{"required":["slug"]},{"required":["name"]}],"properties":{"id":{"description":"The workspace or built-in template to send, by ID.","$ref":"#/components/schemas/SMSTemplateID"},"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"description":"The workspace or built-in template to send, by its immutable slug. Read the template's live version to see its variables.\n","example":"bird_otp_verification_ttl"},"name":{"type":"string","minLength":1,"deprecated":true,"description":"Deprecated. Use `slug` instead. This resolves legacy built-in catalogue names and never matches a workspace template's display name.\n"},"language":{"allOf":[{"$ref":"#/components/schemas/LanguageTag"}],"description":"Which of the template's languages to send. Omit it to send the template's default language, unless the template sets `language_source_required`, in which case a send naming no language is rejected. When the template does not carry the language you ask for, its own `on_missing_language` setting decides whether the closest available language is sent instead or the send is rejected.\n","example":"fr"},"parameters":{"type":"object","additionalProperties":true,"description":"Values for the template's variables, keyed by variable name. Read the live version to see the accepted keys and formats. A missing key, an undeclared key, an invalid value, or a serialized object over 16 KiB returns `422`.\n","example":{"code":"493021","ttl":"10"}}}},"SMSMessageSendRequest":{"type":"object","additionalProperties":false,"description":"A message to send. Supply exactly one of `text` (free-text, which also requires `category`) or `template` (a stored template, whose category is derived). Supplying both, or neither, is rejected.\n","required":["to"],"anyOf":[{"required":["text"]},{"required":["template"]}],"not":{"anyOf":[{"required":["text","template"],"properties":{"text":{},"template":{}}},{"required":["template","category"],"properties":{"template":{},"category":{}}},{"required":["template","media_urls"],"properties":{"template":{},"media_urls":{}}}]},"dependentRequired":{"text":["category"]},"properties":{"to":{"type":"string","minLength":1,"description":"Recipient phone number in E.164 format (for example `+14155550100`). One recipient per message. The number is stored and returned in canonical E.164; a recipient that cannot be routed returns a `422` `SMSInvalidRecipient`.\n","example":"+14155550100"},"from":{"type":"string","minLength":1,"description":"Sender to send from. It must be a sender the workspace holds: a number it owns in E.164, such as `+15557654321`, a short code it holds, such as `24680`, or an alphanumeric sender ID it has claimed, such as `MyBrand`. A sender the workspace does not hold returns a `422` `SMSSenderNotConfigured`, and an alphanumeric sender must also be permitted, and where required registered, for the destination country. Required on a free-text send and when sending a workspace template. Omitting it in either case returns `422`. A built-in template selects its sender automatically and rejects `from`.\n","example":"+15557654321"},"text":{"type":"string","minLength":1,"description":"Free-text message body. Required unless `template` is supplied (the two are mutually exclusive). At least 1 character, up to a 12-segment cap (roughly 1836 GSM-7 or 804 UCS-2 characters). Bird does not truncate; a body exceeding 12 segments is rejected with a 422. The cap applies to segments because GSM-7 and UCS-2 encodings differ in characters per segment.\n","example":"Your verification code is 123456."},"category":{"allOf":[{"$ref":"#/components/schemas/SMSMessageCategory"}],"description":"Content classification: why you are sending. Required on a free-text send; omit it on a template send, where the category is derived from the template. Where the destination country requires the sender to be registered, a category outside what that registration covers returns a `422` `SenderCategoryNotPermitted`.\n"},"validity_period":{"type":"integer","minimum":60,"maximum":172800,"description":"Preview feature: how long, in seconds (60-172800), the carrier may keep attempting delivery before the message is marked `expired`. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.\n"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"maxItems":20,"description":"Structured `{name, value}` labels for filtering and analytics. Tags become first-class query dimensions: filter the list endpoint by tag name, slice analytics by tag, and surface in webhook payloads. Maximum 20 tags per send. Use tags for low-cardinality dimensions (`category`, `experiment_variant`). For arbitrary structured context you do not need as a filter dimension, use `metadata` instead.\n"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary JSON object stored on the message, returned on API reads, and echoed in webhook payloads. Maximum 2 KB serialized. Use metadata for per-send context like internal IDs and foreign keys. For low-cardinality filterable labels, use `tags` instead.\n"},"options":{"allOf":[{"$ref":"#/components/schemas/SMSSendOptions"}],"description":"What Bird does to this message on its way out, such as `smart_encoding`. The message being relayed stays at the top level: its recipient, sender, content, and the delivery instructions the carrier acts on.\n"},"media_urls":{"type":"array","items":{"type":"string"},"description":"Preview feature: multimedia (MMS) attachments. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`."},"messaging_profile_id":{"type":"string","description":"Preview feature: sender selection from a messaging profile pool. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`."},"scheduled_at":{"type":"string","format":"date-time","description":"Preview feature: send-later scheduling. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`."},"template":{"allOf":[{"$ref":"#/components/schemas/SMSTemplateSend"}],"description":"Send using a stored template instead of free text. The category is derived from the template, so `category` and `media_urls` are rejected. A workspace template requires `from`; a built-in template selects its sender and rejects `from`.\n"},"broadcast_id":{"type":"string","description":"Preview feature: broadcast correlation. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`."},"campaign_id":{"type":"string","description":"Preview feature: campaign correlation for analytics. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`."},"audience_id":{"type":"string","description":"Preview feature: audience-targeted sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`."},"contact_id":{"type":"string","description":"Preview feature: contact-targeted sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`."},"topic_id":{"type":"string","description":"Preview feature: topic-gated sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`."},"personalization":{"type":"object","additionalProperties":true,"description":"Preview feature: per-recipient substitution for batch sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`."}},"example":{"to":"+14155550100","from":"+15557654321","text":"Your verification code is 123456.","category":"authentication","options":{"smart_encoding":true},"tags":[{"name":"campaign","value":"signup"}],"metadata":{"user_id":"usr_12345"}}},"SMSMessageBatchRequest":{"type":"object","additionalProperties":false,"description":"Batch of SMS message send requests.","required":["messages"],"properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/SMSMessageSendRequest"},"minItems":1,"maxItems":100,"description":"SMS message send requests, up to 100. Each is an independent send; all are validated before any is queued."}}},"SMSBatchSummary":{"type":"object","additionalProperties":false,"readOnly":true,"required":["accepted_count"],"description":"Aggregate result for an SMS batch.","properties":{"accepted_count":{"type":"integer","minimum":0,"description":"Number of messages accepted in the batch. Acceptance is all-or-nothing, so this equals the number of messages submitted.\n"}}},"SMSMessageBatchResponse":{"type":"object","additionalProperties":false,"required":["data","summary"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SMSMessage"},"description":"One entry per message in the batch, in submission order."},"summary":{"$ref":"#/components/schemas/SMSBatchSummary","description":"Aggregate result for the batch."}}},"SMSEvent":{"type":"object","additionalProperties":false,"required":["id","type","occurred_at"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"pattern":"^evt_[0-9a-hjkmnp-tv-z]{26}$","description":"Unique identifier for this event, stable across repeated fetches of the message.","example":"evt_01krdgeqcxet5s7t44vh8rt9mg"},"type":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["sms.accepted","sms.sent","sms.delivered","sms.undelivered","sms.failed","sms.rejected","sms.expired"],"description":"Lifecycle event type. The `sms.accepted` event means the API accepted the request. The `sms.sent` event means the message reached the carrier. The `sms.delivered` event confirms delivery. The `sms.undelivered`, `sms.failed`, and `sms.expired` events describe delivery failures. The `sms.rejected` event means the message was refused before carrier handoff. This is an open enum. Accept unrecognized values.\n","example":"sms.delivered"},"occurred_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this event occurred."},"carrier":{"type":"string","readOnly":true,"description":"Carrier that handled the message. Present on `sms.sent` and `sms.delivered` once identified, absent otherwise.","example":"Verizon"},"mcc_mnc":{"type":"string","readOnly":true,"description":"Mobile country code and mobile network code of the carrier. Present on `sms.sent` and `sms.delivered` once identified, absent otherwise.","example":"311480"},"error":{"$ref":"#/components/schemas/SMSError","description":"Failure detail. Present only on `sms.failed`, `sms.undelivered`, `sms.rejected`, and `sms.expired` events."}}},"SMSEventList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Timeline events for this SMS message, in chronological order. The bounded timeline is returned in full and is not paginated.","items":{"$ref":"#/components/schemas/SMSEvent"}}}},"TemplateScope":{"type":"string","minLength":1,"readOnly":true,"enum":["system","workspace"],"description":"Whether the template is one of our built-in templates (`system`) or one your workspace created (`workspace`).\n"},"SMSTemplateCategory":{"type":"string","minLength":1,"enum":["transactional","marketing","authentication"],"description":"Why messages use this template. Use `authentication` for one-time codes, `marketing` for promotions, and `transactional` for service messages.\n"},"TemplateStatus":{"type":"string","minLength":1,"readOnly":true,"enum":["draft","pending","active","rejected","inactive"],"description":"Where the template stands as a whole. The same five states on every channel.\n\n- `draft`: nothing has ever gone live.\n- `pending`: nothing is live and at least one language is in review.\n- `active`: at least one language is live, so something can be sent.\n- `rejected`: it was reviewed and every language was refused.\n- `inactive`: nothing is live and nothing is in review, so content was withdrawn or was blocked before anything went live.\n\nA template with one language live is `active` even while another is still\ndrafted or refused. Read `languages` for the state of each language and its\nreason.\n\nWhich values a channel reports follows its review model. A channel whose\ncontent a third party reviews uses all five. On email and SMS, where content\ngoes live on publish, a template is `draft`, `active` or `inactive`, and\n`pending` and `rejected` are reserved for the review stage coming to both, so\na template reaching either is not a breaking change.\n","example":"active"},"SMSTemplateSortField":{"type":"string","enum":["created_at"],"default":"created_at","description":"Field to sort SMS templates and their versions by."},"TemplateLanguageStatus":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["draft","live","superseded"],"description":"Status of one template language on channels without third-party review.\n\n- `draft`: it has never been published.\n- `live`: it is available to sends.\n- `superseded`: a later version replaced it.\n\nTreat an unknown value as not sendable.\n","example":"live"},"SMSTemplateLanguageState":{"type":"object","additionalProperties":false,"description":"Whether a language is live and whether its draft has unpublished changes.","required":["status"],"properties":{"status":{"readOnly":true,"$ref":"#/components/schemas/TemplateLanguageStatus"},"draft":{"type":"boolean","readOnly":true,"description":"Whether the draft has an unpublished change for this language. When true beside `live`, sends keep using the older published text until submit.\n"}}},"SMSTemplateSummary":{"type":"object","additionalProperties":false,"description":"An SMS template without content or draft concurrency settings.","required":["id","workspace_id","slug","name","description","scope","status","category","default_language","available_languages","languages","draft_version_id","live_version_id","published_version_id","last_submitted_at","created_at","updated_at"],"properties":{"id":{"readOnly":true,"description":"Template ID.","$ref":"#/components/schemas/SMSTemplateID"},"workspace_id":{"readOnly":true,"description":"The workspace that owns the template. Null for a built-in `system` template.","oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}]},"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"readOnly":true,"description":"The immutable handle used to address and send the template."},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The template's display name."},"description":{"type":["string","null"],"description":"What the template is for. Null if it has no description."},"scope":{"$ref":"#/components/schemas/TemplateScope"},"status":{"$ref":"#/components/schemas/TemplateStatus"},"category":{"$ref":"#/components/schemas/SMSTemplateCategory"},"draft_version_id":{"readOnly":true,"description":"The permanent editable draft version. Null for a built-in template.","oneOf":[{"$ref":"#/components/schemas/SMSTemplateVersionID"},{"type":"null"}]},"live_version_id":{"readOnly":true,"description":"The version sends resolve to, or null before first publication.","oneOf":[{"$ref":"#/components/schemas/SMSTemplateVersionID"},{"type":"null"}]},"published_version_id":{"readOnly":true,"deprecated":true,"description":"Deprecated. Use `live_version_id`, which carries the same value.","oneOf":[{"$ref":"#/components/schemas/SMSTemplateVersionID"},{"type":"null"}]},"languages":{"type":"object","readOnly":true,"propertyNames":{"type":"string","minLength":2,"maxLength":35},"additionalProperties":{"$ref":"#/components/schemas/SMSTemplateLanguageState"},"description":"Each language and its live or draft state, keyed by canonical BCP-47 tag."},"default_language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The draft's default language."},"available_languages":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/LanguageTag"},"description":"Languages the live version can currently send. Empty before first publication."},"last_submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was last published. Null before first publication and for built-in templates."},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was created. Null for built-in templates."},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was last modified. Null for built-in templates."}}},"SMSTemplateList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"One page of SMS templates.","items":{"$ref":"#/components/schemas/SMSTemplateSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"SMSTemplateText":{"type":["string"],"minLength":0,"description":"SMS template text, limited to 16 KiB of UTF-8 source. Blank text can be saved in a draft but cannot be published. Workspace templates support scalar variables, conditional text, and bounded filters. Loops, assignments, captures, partials, collections, and string-expanding filters are rejected.\n"},"SMSTemplateLanguageContent":{"type":"object","additionalProperties":false,"description":"The editable SMS text for one language.","required":["text"],"properties":{"text":{"$ref":"#/components/schemas/SMSTemplateText","description":"Template text in this language. Liquid-style `{{ variable }}` placeholders declare text variables. Blank text can be saved in a draft but cannot be published. The UTF-8 source is limited to 16 KiB.\n","example":"Your order {{ order_number }} has shipped."}}},"TemplateOnMissingLanguage":{"type":"string","minLength":1,"enum":["fallback","fail"],"x-enum-varnames":["TemplateOnMissingLanguageFallback","TemplateOnMissingLanguageFail"],"description":"What a send or a preview does when it asks for a language the template\ncannot serve.\n\n`fallback` serves the closest match instead. It tries a broader form of the\nsame language first, so a request for `pt-BR` can be served by a stocked\n`pt`, and then the template's default language. A send never fails because a\nlanguage is missing.\n\n`fail` rejects the send rather than serving a different language, for content\nwhere sending the wrong language is worse than not sending at all. It matches\nthe requested tag or a broader form of it and refuses a sibling variant, so\n`pt-BR` is never served by `pt-PT`. A send that names no language still uses\nthe default language.\n\nThe default is per channel and stated on each channel's own field, because\nwhat a wrong-language send costs differs. Where every language is separately\nreviewed and separately priced, falling back silently would send content the\nrecipient did not expect at a rate the sender did not choose.\n","example":"fallback"},"SMSTemplateCreate":{"type":"object","additionalProperties":false,"description":"Parameters for creating an SMS template and its permanent draft.","required":["slug","category"],"properties":{"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"description":"The workspace-unique handle used to address and send the template. It is immutable. Slugs beginning with `bird_` or `smt_` are reserved.\n","example":"order-shipped"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The display name. It defaults to `slug` when omitted.","example":"Order shipped"},"description":{"type":"string","description":"What the template is for.","example":"Sent when an order leaves the warehouse."},"category":{"$ref":"#/components/schemas/SMSTemplateCategory"},"languages":{"type":"object","maxProperties":25,"propertyNames":{"type":"string","minLength":2,"maxLength":35},"additionalProperties":{"$ref":"#/components/schemas/SMSTemplateLanguageContent"},"description":"Initial draft text keyed by language tag. Tags are canonicalized, and two keys that canonicalize to the same tag are rejected. Omit or send an empty object to create one blank row for the default language.\n","example":{"en":{"text":"Your order {{ order_number }} has shipped."}}},"default_language":{"$ref":"#/components/schemas/LanguageTag","description":"The draft's default language. It must be present when `languages` is not empty. Omit it to use the single supplied language, or `en` otherwise.\n"},"on_missing_language":{"allOf":[{"$ref":"#/components/schemas/TemplateOnMissingLanguage"}],"default":"fallback","description":"How sends handle a requested language the live version does not contain."},"language_source_required":{"type":"boolean","default":false,"description":"Whether every send must name a language instead of using the live default."}}},"SMSTemplate":{"type":"object","additionalProperties":false,"description":"One SMS template identity and its authoring state. Content and variables live on versions, so this resource stays shallow.\n","required":["id","workspace_id","slug","name","description","scope","status","category","default_language","available_languages","languages","on_missing_language","language_source_required","draft_version_id","live_version_id","published_version_id","revision","last_submitted_at","created_at","updated_at"],"properties":{"id":{"readOnly":true,"description":"Template ID.","$ref":"#/components/schemas/SMSTemplateID"},"workspace_id":{"readOnly":true,"description":"The workspace that owns the template. Null for a built-in `system` template.","oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}]},"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"readOnly":true,"description":"The immutable handle used to address and send the template. A built-in template's slug starts with `bird_`.\n","example":"order-shipped"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The template's display name. It defaults to the slug and can be changed on workspace templates.","example":"Order shipped"},"description":{"type":["string","null"],"description":"What the template is for. Null if it has no description."},"scope":{"$ref":"#/components/schemas/TemplateScope"},"status":{"$ref":"#/components/schemas/TemplateStatus"},"category":{"$ref":"#/components/schemas/SMSTemplateCategory"},"draft_version_id":{"readOnly":true,"description":"The permanent editable draft version. Null for a built-in template.","oneOf":[{"$ref":"#/components/schemas/SMSTemplateVersionID"},{"type":"null"}]},"live_version_id":{"readOnly":true,"description":"The version sends resolve to, or null before a workspace template is first published. A built-in template points to a synthetic published version that projects its current catalogue content.\n","oneOf":[{"$ref":"#/components/schemas/SMSTemplateVersionID"},{"type":"null"}]},"published_version_id":{"readOnly":true,"deprecated":true,"description":"Deprecated. Use `live_version_id`, which carries the same value.","oneOf":[{"$ref":"#/components/schemas/SMSTemplateVersionID"},{"type":"null"}]},"revision":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"The draft revision to use for concurrent-edit checks. Null for a built-in template."},"languages":{"type":"object","readOnly":true,"propertyNames":{"type":"string","minLength":2,"maxLength":35},"additionalProperties":{"$ref":"#/components/schemas/SMSTemplateLanguageState"},"description":"Each language the template has, keyed by canonical BCP-47 tag, with its live state and whether the draft contains unpublished changes. Content is available from version reads.\n","example":{"en":{"status":"live"},"nl":{"status":"live","draft":true}}},"default_language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The draft's default language. Sends continue using the live version's default until the draft is published.\n"},"available_languages":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/LanguageTag"},"description":"Languages the live version can currently send. Empty before first publication.\n","example":["en"]},"on_missing_language":{"allOf":[{"$ref":"#/components/schemas/TemplateOnMissingLanguage"}],"readOnly":true,"description":"How a send handles a requested language that the live version does not have."},"language_source_required":{"type":"boolean","readOnly":true,"description":"Whether each send must name a language instead of using the live version's default."},"last_submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was last published. Null before first publication and for built-in templates."},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was created. Null for built-in templates."},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was last modified. Null for built-in templates."}}},"SMSTemplateDraftRevision":{"type":"integer","minimum":0,"description":"The draft revision last read from the template. A stale value returns a conflict so the caller can reload and retry.\n","example":3},"SMSTemplateUpdate":{"type":"object","additionalProperties":false,"description":"Template metadata and draft settings to change.","required":["revision"],"properties":{"revision":{"$ref":"#/components/schemas/SMSTemplateDraftRevision"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"A new display name. The immutable slug stays unchanged."},"description":{"type":["string","null"],"description":"A new description. Send null to clear it."},"default_language":{"$ref":"#/components/schemas/LanguageTag","description":"A new default language already present in the draft."},"on_missing_language":{"allOf":[{"$ref":"#/components/schemas/TemplateOnMissingLanguage"}],"description":"How sends handle a requested language the live version does not contain."},"language_source_required":{"type":"boolean","description":"Whether every send must name a language instead of using the live default."}}},"SMSTemplateDuplicate":{"type":"object","additionalProperties":false,"description":"Optional settings for an SMS template copy.","properties":{"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"description":"The copy's workspace-unique handle. Omit it to derive an available handle from the source slug. Slugs beginning with `bird_` or `smt_` are reserved.\n","example":"order-shipped-copy"}}},"SMSTemplatePreviewOptions":{"type":"object","additionalProperties":false,"description":"Rendering options for this SMS template preview.","properties":{"smart_encoding":{"type":"boolean","default":false,"description":"Replace supported look-alike characters with GSM-7 equivalents before calculating segments. The returned text shows the replacement result.\n"}}},"SMSTemplatePreviewRequest":{"type":"object","additionalProperties":false,"x-sensitive":true,"description":"Sample values, language selection, and rendering options for an SMS template preview.","properties":{"parameters":{"type":"object","additionalProperties":true,"description":"Scalar sample values keyed by variable name. The serialized object is limited to 16 KiB. Missing, unexpected, or invalid values return `422`.\n","example":{"order_number":"A1042"}},"language":{"$ref":"#/components/schemas/LanguageTag","description":"The language to render. Omit it to use the version's default. The template's missing-language policy controls fallback.\n"},"version":{"allOf":[{"$ref":"#/components/schemas/SMSTemplateVersionID"}],"description":"A draft or published version to preview instead of the current draft."},"options":{"$ref":"#/components/schemas/SMSTemplatePreviewOptions"}}},"TemplateVariable":{"type":"object","additionalProperties":false,"description":"A single variable slot a template fills in from the values supplied when sending. The same shape on email, SMS and WhatsApp, so reading what a template needs works the same way whichever channel you are sending on.\n","required":["key","type","required","constraint"],"properties":{"key":{"type":"string","minLength":1,"readOnly":true,"description":"The key this slot is filled by. On email and SMS it is the key you set in the send's `parameters` object. On WhatsApp it is the `name` you repeat on the matching parameter inside `components`, or, for a template whose placeholders are positional, the position itself as `1`, `2` and so on.\n"},"type":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["code","ttl","count","ref","date","date_time","amount","currency","text"],"description":"The value type this slot accepts. Built-in SMS templates use typed slots (`code`, `amount` and the rest), each of which rejects a value that does not match its `constraint`. Email, WhatsApp and workspace SMS templates use `text`. Workspace SMS parameters must be scalar values. Open enum: treat an unrecognized value as a future type rather than an error.\n"},"required":{"type":"boolean","readOnly":true,"description":"Whether the send must supply this variable. Omitting a required value returns `422` on email, SMS, and WhatsApp sends.\n"},"constraint":{"type":"string","minLength":1,"readOnly":true,"description":"A plain-language description of what values this variable accepts."},"sensitive":{"type":"boolean","readOnly":true,"default":false,"description":"Whether this slot's value is redacted from stored message content. A placeholder replaces the sensitive value in message history; transport queues can still carry the text needed for delivery.\n"}}},"SMSTemplatePreview":{"type":"object","additionalProperties":false,"description":"Rendered SMS template text with its segment calculation and source provenance.","required":["text","language","variables","segments","template_version_id","draft_revision","content_hash"],"properties":{"text":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Rendered text after parameter substitution and optional smart encoding."},"language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language that was rendered after applying the template's fallback policy."},"variables":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/TemplateVariable"},"description":"Variables used by this template version."},"segments":{"readOnly":true,"$ref":"#/components/schemas/SMSSegments"},"template_version_id":{"readOnly":true,"description":"The draft, workspace published version, or synthetic built-in version rendered. For a built-in template, `content_hash` identifies the exact catalogue source.\n","$ref":"#/components/schemas/SMSTemplateVersionID"},"draft_revision":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"The rendered draft's revision. Null for published and built-in versions."},"content_hash":{"readOnly":true,"$ref":"#/components/schemas/SMSTemplateContentHash"}}},"SMSTemplateVersionStatus":{"type":"string","minLength":1,"readOnly":true,"enum":["draft","published"],"description":"Whether the version is the editable draft or published. Published workspace versions are immutable and remain `published` after a later version goes live. A built-in template's synthetic published version projects the current catalogue entry.\n","example":"published"},"SMSTemplateVersionSummary":{"type":"object","additionalProperties":false,"description":"One SMS template version without its text.","required":["id","template_id","version_number","status","revision","variables","default_language","available_languages","created_at","published_at"],"properties":{"id":{"readOnly":true,"description":"Template version ID.","$ref":"#/components/schemas/SMSTemplateVersionID"},"template_id":{"readOnly":true,"description":"The template this version belongs to.","$ref":"#/components/schemas/SMSTemplateID"},"version_number":{"type":["integer","null"],"minimum":1,"readOnly":true,"description":"Sequential publication number. Null for the draft; a built-in template reports 1."},"status":{"readOnly":true,"$ref":"#/components/schemas/SMSTemplateVersionStatus"},"revision":{"type":"integer","minimum":0,"readOnly":true,"description":"The version's revision counter."},"variables":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/TemplateVariable"},"description":"Variables inferred from the version's text and shared by each language."},"default_language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language this version treats as its default."},"available_languages":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/LanguageTag"},"description":"Languages this version contains, without their text."},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the version was created. Null for a built-in template's synthetic version."},"published_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the version was published. Null for the draft and for a built-in template's synthetic version."}},"example":{"id":"smv_01krdgeqcxet5s7t44vh8rt9mg","template_id":"smt_01krdgeqcxet5s7t44vh8rt9mg","version_number":1,"status":"published","revision":1,"variables":[{"key":"order_number","type":"text","required":true,"constraint":"A string, number, or boolean."}],"default_language":"en","created_at":"2026-09-10T09:00:00Z","published_at":"2026-09-10T09:00:00Z","available_languages":["en"]}},"SMSTemplateVersionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"One page of the template's versions, newest first.","items":{"$ref":"#/components/schemas/SMSTemplateVersionSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"SMSTemplateVersionLanguage":{"type":"object","additionalProperties":false,"description":"One language's content and revision metadata within a version.","required":["text","revision","content_hash","updated_at"],"properties":{"text":{"$ref":"#/components/schemas/SMSTemplateText","readOnly":true,"description":"Stored template text, including its `{{ variable }}` placeholders."},"revision":{"type":"integer","minimum":0,"readOnly":true,"description":"This language's revision counter."},"content_hash":{"readOnly":true,"$ref":"#/components/schemas/SMSTemplateContentHash"},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this language was last saved. Null for a built-in template."}},"example":{"text":"Your order {{ order_number }} is on its way.","revision":1,"content_hash":"sha256:3d948846f5f773fb7bed8ab81b57f3d0ff2cbff24cb549718f4d389f1ab0300a","updated_at":"2026-09-10T09:00:00Z"}},"SMSTemplateVersion":{"type":"object","additionalProperties":false,"description":"One SMS template version with its content. A workspace template keeps one editable draft and immutable published versions. A built-in template exposes its current catalogue content as one synthetic published version.\n","required":["id","template_id","version_number","status","revision","variables","languages","default_language","created_at","published_at"],"properties":{"id":{"readOnly":true,"description":"Template version ID.","$ref":"#/components/schemas/SMSTemplateVersionID"},"template_id":{"readOnly":true,"description":"The template this version belongs to.","$ref":"#/components/schemas/SMSTemplateID"},"version_number":{"type":["integer","null"],"minimum":1,"readOnly":true,"description":"Sequential publication number. Null for the draft; a built-in template reports 1."},"status":{"readOnly":true,"$ref":"#/components/schemas/SMSTemplateVersionStatus"},"revision":{"type":"integer","minimum":0,"readOnly":true,"description":"The version revision. A draft revision advances with each metadata or content change. Published workspace versions are frozen; a built-in template's synthetic version reports 0.\n"},"variables":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/TemplateVariable"},"description":"Variables inferred from the version's text. Every language in a publishable SMS version uses the same set. Built-in templates may apply additional typed constraints described by each variable.\n"},"languages":{"type":"object","readOnly":true,"propertyNames":{"type":"string","minLength":2,"maxLength":35},"additionalProperties":{"$ref":"#/components/schemas/SMSTemplateVersionLanguage"},"description":"Full content for each language, keyed by canonical BCP-47 tag."},"default_language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language this version treats as its default."},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the version was created. Null for a built-in template's synthetic version."},"published_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the version was published. Null for the draft and for a built-in template's synthetic version."}},"example":{"id":"smv_01krdgeqcxet5s7t44vh8rt9mg","template_id":"smt_01krdgeqcxet5s7t44vh8rt9mg","version_number":1,"status":"published","revision":1,"variables":[{"key":"order_number","type":"text","required":true,"constraint":"A string, number, or boolean."}],"languages":{"en":{"text":"Your order {{ order_number }} is on its way.","revision":1,"content_hash":"sha256:3d948846f5f773fb7bed8ab81b57f3d0ff2cbff24cb549718f4d389f1ab0300a","updated_at":"2026-09-10T09:00:00Z"}},"default_language":"en","created_at":"2026-09-10T09:00:00Z","published_at":"2026-09-10T09:00:00Z"}},"SMSTemplateLanguageSummary":{"type":"object","additionalProperties":false,"description":"One language of an SMS template version without its text.","required":["language","revision","content_hash","updated_at"],"properties":{"language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language in canonical BCP-47 form."},"revision":{"type":"integer","minimum":0,"readOnly":true,"description":"This language's revision counter."},"content_hash":{"readOnly":true,"$ref":"#/components/schemas/SMSTemplateContentHash"},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this language was last saved. Null for a built-in template."}},"example":{"language":"en","revision":1,"content_hash":"sha256:3d948846f5f773fb7bed8ab81b57f3d0ff2cbff24cb549718f4d389f1ab0300a","updated_at":"2026-09-10T09:00:00Z"}},"SMSTemplateLanguageList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The version's languages ordered by canonical tag, without text.","items":{"$ref":"#/components/schemas/SMSTemplateLanguageSummary"}}}},"SMSTemplateLanguage":{"type":"object","additionalProperties":false,"description":"One language's full SMS template text and revision metadata.","required":["language","text","revision","content_hash","updated_at"],"properties":{"language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language in canonical BCP-47 form."},"text":{"$ref":"#/components/schemas/SMSTemplateText","readOnly":true,"description":"Stored template text, including its `{{ variable }}` placeholders."},"revision":{"type":"integer","minimum":0,"readOnly":true,"description":"This language's revision counter, used for concurrent-edit checks on draft writes."},"content_hash":{"readOnly":true,"$ref":"#/components/schemas/SMSTemplateContentHash"},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this language was last saved. Null for a built-in template."}},"example":{"language":"en","text":"Your order {{ order_number }} is on its way.","revision":1,"content_hash":"sha256:3d948846f5f773fb7bed8ab81b57f3d0ff2cbff24cb549718f4d389f1ab0300a","updated_at":"2026-09-10T09:00:00Z"}},"SMSTemplateLanguageUpsert":{"type":"object","additionalProperties":false,"description":"Complete text for one draft language and an optional concurrent-edit guard.","required":["text"],"properties":{"text":{"$ref":"#/components/schemas/SMSTemplateText","description":"Replacement template text. Blank text can be saved in a draft but cannot be published. The UTF-8 source is limited to 16 KiB.\n","example":"Your order {{ order_number }} has shipped."},"revision":{"type":"integer","minimum":0,"description":"The language revision last read. Omit it to save unconditionally."}}},"SMSTemplateRef":{"$ref":"#/components/schemas/SMSTemplateID","readOnly":true,"description":"The template a call addressed, returned as its ID even when the request used a slug. Send it back as `template_ref` on a later call.\n","example":"smt_01krdgeqcxet5s7t44vh8rt9mg"},"SMSTemplateLanguageSaved":{"type":"object","additionalProperties":false,"description":"The identifiers, new revisions, and content hash of a saved draft language.","required":["template_ref","version_id","language","revision","draft_revision","content_hash","updated_at"],"properties":{"template_ref":{"readOnly":true,"$ref":"#/components/schemas/SMSTemplateRef"},"version_id":{"readOnly":true,"description":"The draft version that was updated.","$ref":"#/components/schemas/SMSTemplateVersionID"},"language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The saved language in canonical BCP-47 form."},"revision":{"type":"integer","minimum":0,"readOnly":true,"description":"The language's new revision for the next concurrent-edit check."},"draft_revision":{"type":"integer","minimum":0,"readOnly":true,"description":"The draft's new revision for the next template update or submit."},"content_hash":{"readOnly":true,"$ref":"#/components/schemas/SMSTemplateContentHash"},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the language was saved."}}},"SMSTemplateLanguageUpdate":{"type":"object","additionalProperties":false,"required":["text"],"description":"New text for an existing draft language and an optional concurrent-edit guard.","properties":{"text":{"$ref":"#/components/schemas/SMSTemplateText","description":"New template text. Blank text can be saved in a draft but cannot be published. The UTF-8 source is limited to 16 KiB.\n","example":"Your order {{ order_number }} is on its way."},"revision":{"type":"integer","minimum":0,"description":"The language revision last read. Omit it to update unconditionally."}}},"SMSTemplateRollback":{"type":"object","additionalProperties":false,"description":"The draft revision required to roll an SMS template back.","required":["revision"],"properties":{"revision":{"$ref":"#/components/schemas/SMSTemplateDraftRevision"}}},"SMSTemplateSubmit":{"type":"object","additionalProperties":false,"description":"Options for validating or publishing the complete SMS template draft.","properties":{"validate_only":{"type":"boolean","default":false,"description":"Check every language and return all findings without publishing. Use a different idempotency key for a later real submit.\n"},"expected_revision":{"$ref":"#/components/schemas/SMSTemplateDraftRevision"}},"example":{"expected_revision":3}},"SMSTemplateSubmitProblem":{"type":"object","additionalProperties":false,"description":"One problem found while checking whether an SMS template draft can be published.","required":["language","field","code","message"],"properties":{"language":{"readOnly":true,"description":"The affected language, or null for a version-wide problem.","oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}]},"field":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The affected field, or null for a version-wide problem.","example":"text"},"code":{"type":"string","minLength":1,"pattern":"^E\\d{5}$","readOnly":true,"description":"The error code a real submit would return.","example":"E12109"},"message":{"type":"string","minLength":1,"readOnly":true,"description":"A customer-readable explanation of the problem.","example":"Template text is required to submit this language."}}},"SMSTemplateSubmitResult":{"type":"object","additionalProperties":false,"description":"The outcome of validating or publishing an SMS template draft.","required":["valid","errors","version"],"properties":{"valid":{"type":"boolean","readOnly":true,"description":"Whether the draft passed every publication check."},"errors":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/SMSTemplateSubmitProblem"},"description":"Every problem found across the draft. Empty when `valid` is true."},"version":{"readOnly":true,"description":"The published version, or null for a validation-only request. A non-null version is already live for new sends.\n","oneOf":[{"$ref":"#/components/schemas/SMSTemplateVersion"},{"type":"null"}]}}},"SMSSuppressionReasonFilter":{"type":"string","enum":["keyword_stop","carrier_opted_out","manual"]},"SMSSuppressionID":{"type":"string","minLength":1,"pattern":"^ssu_[0-9a-hjkmnp-tv-z]{26}$","example":"ssu_01krdgeqcxet5s7t44vh8rt9mg"},"SMSSuppressionReason":{"type":"string","minLength":1,"x-extensible-enum":["keyword_stop","carrier_opted_out","manual"],"description":"Reason this sender cannot message the subscriber:\n\n- `keyword_stop`: The subscriber sent a stop keyword. A start keyword clears it.\n- `carrier_opted_out`: The carrier reported the opt-out.\n- `manual`: Your workspace added the suppression through the API or dashboard.\n\nThis is an open enum. Accept unrecognized values.\n"},"SMSSuppressionOrigin":{"type":"string","minLength":1,"x-extensible-enum":["keyword","dlr_event","api_key","user"],"description":"How the opt-out reached us:\n\n- `keyword`: an inbound message from the subscriber.\n- `dlr_event`: a carrier delivery report.\n- `api_key`: an API call.\n- `user`: someone acting in the dashboard.\n\nKept beside the reason because one reason can arrive by more than one route.\n\nThis list grows over time, so treat an unknown value as informational rather than rejecting the\nrecord.\n"},"SMSSuppressionCoverage":{"type":"string","minLength":1,"x-extensible-enum":["all","non_transactional"],"description":"Message categories blocked for this sender and subscriber. `all` blocks every category, including authentication and transactional messages. `non_transactional` blocks marketing messages only. Responses currently use `all`. Treat unrecognized values as blocking every category.\n"},"SMSSuppressionEndReason":{"type":"string","minLength":1,"x-extensible-enum":["keyword_start","api_key","user","carrier_cleared"],"description":"What ended it:\n\n- `keyword_start`: the subscriber texting a start keyword to the same sender.\n- `api_key`: an API call.\n- `user`: someone acting in the dashboard.\n- `carrier_cleared`: the carrier reporting its own opt-out cleared.\n\nThis list grows over time, so treat an unknown value as informational rather than rejecting the\nrecord.\n"},"SMSSuppression":{"type":"object","additionalProperties":false,"description":"One period during which a sender's messages to a subscriber are stopped: when it started, what started it, and what ended it. A subscriber who opts out, opts back in, and opts out again has three of these on record rather than one current state, and each keeps its own dates once it ends. The list returns the periods in force; fetch one by ID to read an ended one.\n","required":["id","destination","originator","reason","origin","applies_to","blocking","effective_at","created_at","last_asserted_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SMSSuppressionID"},"destination":{"type":"string","minLength":2,"maxLength":20,"description":"The subscriber, in E.164 format.","example":"+15550001234"},"originator":{"type":"string","minLength":1,"maxLength":20,"description":"The sender this stops. A suppression covers one sender, so your other senders still reach this subscriber. Opting out of one of your programs does not opt out of the others.\n","example":"+15557654321"},"reason":{"allOf":[{"$ref":"#/components/schemas/SMSSuppressionReason"}],"readOnly":true},"origin":{"allOf":[{"$ref":"#/components/schemas/SMSSuppressionOrigin"}],"readOnly":true},"applies_to":{"allOf":[{"$ref":"#/components/schemas/SMSSuppressionCoverage"}],"readOnly":true},"blocking":{"type":"boolean","readOnly":true,"description":"Whether this is stopping messages right now. Always true in a list, which carries only the suppressions in force; false when you fetch one by ID that has since ended, which is also when `ended_at` is set.\n"},"source_sms_id":{"oneOf":[{"$ref":"#/components/schemas/SMSMessageID"},{"type":"null"}],"readOnly":true,"description":"The inbound message the subscriber opted out with, or the outbound message whose delivery report reported the opt-out. Null when neither applies.\n"},"effective_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the subscriber opted out, as reported by whoever reported it. This is what orders one subscriber's history, and it can be earlier than `created_at` when a message reached us late.\n"},"ended_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this stopped applying. Null while it is still stopping messages."},"ended_reason":{"oneOf":[{"$ref":"#/components/schemas/SMSSuppressionEndReason"},{"type":"null"}],"readOnly":true,"description":"What ended it. Null while it is still stopping messages."},"ended_effective_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the subscriber opted back in, as reported. Null while it is still stopping messages."},"source_end_sms_id":{"oneOf":[{"$ref":"#/components/schemas/SMSMessageID"},{"type":"null"}],"readOnly":true,"description":"The inbound message the subscriber opted back in with, when there was one. Null while it is still stopping messages, and when something other than a start keyword ended it.\n"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When we recorded it."},"last_asserted_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When we last recorded the subscriber opting out of this sender. Later than `created_at` when they texted a stop keyword again while already suppressed, which adds no new record but does earn another confirmation reply.\n"}}},"SMSSuppressionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of suppressions, most recent opt-out first.","items":{"$ref":"#/components/schemas/SMSSuppression"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"SMSSuppressionCreate":{"type":"object","additionalProperties":false,"description":"Stops one sender's messages to one subscriber. Both ends are required, because a suppression covers a sender-and-subscriber pair rather than a subscriber alone.\n","required":["destination","originator"],"properties":{"destination":{"type":"string","minLength":2,"maxLength":20,"description":"The subscriber to stop messaging, in E.164 format.","example":"+15550001234"},"originator":{"type":"string","minLength":1,"maxLength":20,"description":"The sender to stop. Your other senders keep reaching this subscriber, so stopping every one of them means one call per sender.\n","example":"+15557654321"}}},"SMSSuppressionBulkCreate":{"type":"object","additionalProperties":false,"required":["suppressions"],"properties":{"suppressions":{"type":"array","minItems":1,"maxItems":1000,"items":{"$ref":"#/components/schemas/SMSSuppressionCreate"},"description":"Sender-and-subscriber pairs to stop, one row per pair, in the same shape as `POST /v1/sms/suppressions`. Each row is applied independently through the same idempotent-by-pair rule as a single create, and reported at the same position in the response."}},"example":{"suppressions":[{"destination":"+15550001234","originator":"+15557654321"},{"destination":"+15550005678","originator":"+15557654321"}]}},"SuppressionBulkOutcome":{"type":"string","minLength":1,"x-extensible-enum":["created","already_suppressed","error"],"description":"What happened to this row.\n\n- `created`: a new suppression was recorded for this row.\n- `already_suppressed`: a suppression for this row already existed and is returned unchanged.\n- `error`: the row itself was invalid and nothing was recorded; see `error`.\n\nThis list may grow over time. Treat an unrecognized value as informational rather than a\nreason to reject the response.\n"},"SMSSuppressionBulkResultItem":{"type":"object","description":"One row's result. Which optional fields accompany the outcome is stated per field rather than encoded as a `oneOf`: `outcome` is an extensible enum, so a closed discriminated union would turn every future outcome into a breaking change.","additionalProperties":false,"required":["outcome"],"properties":{"outcome":{"$ref":"#/components/schemas/SuppressionBulkOutcome"},"suppression":{"$ref":"#/components/schemas/SMSSuppression","description":"The created or already-existing suppression. Absent when the row errored."},"error":{"$ref":"#/components/schemas/BulkRowError","description":"Why this row errored. Absent for every other outcome."}}},"SMSSuppressionBulkResult":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SMSSuppressionBulkResultItem"},"description":"One entry per pair in the request, in submission order."}}},"SMSKeywordOperation":{"type":"string","minLength":1,"x-extensible-enum":["stop","start","help","info","confirm","custom"],"description":"What Bird does when an inbound message matches the rule.\n\n- `stop` unsubscribes the sender from further messages.\n- `start` resubscribes them.\n- `help` replies with your support information.\n- `info` replies with your program information. It behaves exactly as `help` does and is\n  separate so a country whose INFO answer must differ from its HELP answer can carry both.\n  Where Bird ships no `info` rule for a country, INFO is one of that country's `help`\n  keywords and answers with the `help` reply.\n- `confirm` marks a double opt-in reply. It sends nothing today, so answer it from your own\n  handler.\n- `custom` replies with the text you configured and has no other effect.\n\nBird's built-in rules fix the operation for `stop`, `start` and `help`; you can change their\nreply but not what they do. The same holds for `info` in any country where Bird ships an\n`info` rule. This is an open enum. Accept unrecognized values.\n","example":"stop"},"SMSKeywordRuleScope":{"type":"string","minLength":1,"readOnly":true,"enum":["system","workspace"],"description":"Whether the rule is one of Bird's defaults (`system`) or one your workspace created (`workspace`). A `workspace` rule takes precedence over Bird's default for the same country, so it is how you replace a reply without losing the keywords Bird ships.\n"},"SMSKeywordRuleID":{"type":"string","minLength":1,"pattern":"^(sks|skw)_[0-9a-hjkmnp-tv-z]{26}$","example":"skw_01krdgeqcxet5s7t44vh8rt9mg","description":"Identifier of a keyword rule. An `sks_` id is one of Bird's defaults, which you can read but not change; an `skw_` id is a rule your workspace created.\n"},"SMSKeywordRule":{"type":"object","additionalProperties":false,"required":["id","scope","operation","keywords","effective_keywords","mandatory","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/SMSKeywordRuleID"},"scope":{"$ref":"#/components/schemas/SMSKeywordRuleScope"},"operation":{"$ref":"#/components/schemas/SMSKeywordOperation"},"country":{"type":["string","null"],"minLength":2,"maxLength":2,"description":"The country the rule applies in, as an ISO 3166-1 alpha-2 code. A rule for `NL` covers messages received on your Dutch numbers, and messages from a subscriber whose own number is Dutch whichever of your numbers they text. Rules for the country a message arrives in always outrank rules for the country its sender is in; within each, your rule wins over Bird's keywords for that country. `number` confines a rule to one number. Null means the rule applies worldwide, which is allowed for `custom` operations only.\n","example":"NL"},"language":{"type":["string","null"],"minLength":2,"description":"The language this rule covers, in countries where Bird ships keywords in more than one. Canada has separate English and French rules, so a Canadian rule names which one it replaces and the other keeps Bird's reply. Null in countries with a single set.\n","example":"fr"},"number":{"type":["string","null"],"minLength":1,"description":"Narrows the rule to one of your numbers in E.164 format, instead of every number you hold in the country. Null means it applies to all of them.\n","example":"+18005551234"},"keywords":{"type":"array","description":"The keywords this rule adds. For one of Bird's defaults this is the full set Bird ships. For a rule you created it is only what you added on top. It never restates or removes Bird's keywords, so `effective_keywords` is what actually matches.\n","items":{"type":"string","minLength":1},"example":["pizza","menu"]},"effective_keywords":{"type":"array","readOnly":true,"description":"Every keyword that matches this rule: Bird's keywords for the same operation, country and language, plus the ones you added. This is what an inbound message is compared against. Keywords Bird adds later join it without you changing anything.\n","items":{"type":"string","minLength":1},"example":["stop","stoppen","afmelden"]},"reply":{"type":["string","null"],"minLength":1,"description":"The message sent back when one of the keywords matches, except on a `confirm` rule, which never sends one. Null when the auto-reply is switched off, which `reply_disabled_at` distinguishes from a rule that has not been given one.\n","example":"You have been unsubscribed and will receive no further messages."},"reply_suffix":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"Text appended to your reply that you cannot change: the rates and opt-out wording carriers require on a help response. Your reply is sent in front of it, and both count against the length a single message allows. Null when the operation carries none.\n","example":"Msg&data rates may apply. Reply STOP to unsubscribe."},"reply_disabled_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the auto-reply for this rule was switched off, or null if it is on. Switching it off records that you send this reply from your own system, which is what Bird points to if a carrier asks why no reply went out.\n","example":"2026-08-12T09:00:00Z"},"mandatory":{"type":"boolean","readOnly":true,"description":"Whether what this operation does is fixed. When true you can change the reply but not the behavior. An opt-out keyword always unsubscribes the sender, whichever rule matched it, because carriers and regulators require it.\n","example":true},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the rule was created.","example":"2026-08-12T09:00:00Z"},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the rule was last changed. On one of Bird's defaults this is when Bird last changed the keywords or the reply for that country.","example":"2026-08-12T09:00:00Z"}}},"SMSKeywordRuleList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The keyword rules that apply to your workspace, Bird's defaults included. Ordered most specific first, so the first rule whose keywords match an inbound message is the one that runs. The set is small and returned in full; this list is not paginated.\n","items":{"$ref":"#/components/schemas/SMSKeywordRule"}}}},"SMSKeywordRuleCreate":{"type":"object","additionalProperties":false,"required":["operation"],"properties":{"operation":{"$ref":"#/components/schemas/SMSKeywordOperation"},"country":{"type":["string","null"],"minLength":2,"maxLength":2,"description":"The country this rule applies in, as an ISO 3166-1 alpha-2 code. It matches a message two ways: one received on any of your numbers in this country, and one sent by a subscriber whose own number is in it, wherever they text you. Rules for the country a message arrives in always outrank rules for the country its sender is in; within each, your rule wins over Bird's keywords for that country. To confine a rule to one of your numbers, set `number` instead. Required for `stop`, `start` and `help`, because those replace what Bird ships for one country and a worldwide rule would replace every country's. Omit it only for `custom`, which then applies everywhere you send. Derived from `number` when you supply an E.164 number and leave this out; a short code carries no country, so a rule for one must name it.\n","example":"NL"},"language":{"type":["string","null"],"minLength":2,"description":"Which language this rule replaces, in countries where Bird ships keywords in more than one. Required there and rejected elsewhere. Listing the country's rules shows whether it applies and which languages are available.\n","example":"fr"},"number":{"type":["string","null"],"minLength":1,"description":"Narrows the rule to one number you hold, in E.164 format or as a short code. Omit to cover every number you hold in the country. The number must be one of yours and able to receive messages.\n","example":"+18005551234"},"keywords":{"type":"array","description":"Extra keywords to match, on top of the ones Bird already ships for this operation and country. Omit to keep Bird's keywords and change only the reply, including keywords Bird adds later. You cannot remove one of Bird's keywords, and a keyword Bird has bound to another operation cannot be reused here. Required for `custom`, which inherits none.\n","items":{"type":"string","minLength":1},"example":["pizza","menu"]},"reply":{"type":["string","null"],"minLength":1,"description":"The message to send back when a keyword matches, except on a `confirm` rule, which never sends one whatever this is set to. Set it to null together with `confirmed_self_managed` to send nothing at all.\n","example":"You have been unsubscribed and will receive no further messages."},"confirmed_self_managed":{"type":"boolean","description":"Set this with `reply: null` to confirm you send this reply from your own system, which switches Bird's auto-reply off for the rule. Required to send no reply, and rejected when a reply is given, so the two can never disagree.\n","example":true}}},"SMSKeywordRuleUpdate":{"type":"object","additionalProperties":false,"description":"Changes the reply and the added keywords. What a rule applies to (its operation, country, language and number) is fixed once created: those decide which inbound messages reach it, so changing one would make it a different rule. Delete it and create the one you want.\n","properties":{"keywords":{"type":"array","description":"Replaces the extra keywords this rule matches, on top of the ones Bird ships. Send an empty array to keep Bird's keywords only. Omit to leave the current ones unchanged.\n","items":{"type":"string","minLength":1},"example":["pizza","menu"]},"reply":{"type":["string","null"],"minLength":1,"description":"Replaces the message sent back when a keyword matches, except on a `confirm` rule, which never sends one whatever this is set to. Set it to null together with `confirmed_self_managed` to switch the auto-reply off. Omit to leave it unchanged.\n","example":"You have been unsubscribed and will receive no further messages."},"confirmed_self_managed":{"type":"boolean","description":"Set this with `reply: null` to confirm you send this reply from your own system. Required to switch the auto-reply off, and rejected when a reply is given.\n","example":true}}},"TCRBrandStatus":{"type":"string","minLength":1,"enum":["pending","approved","rejected"],"description":"The registration status of the brand. A newly created brand is `pending`; `approved` and `rejected` reflect the registration outcome.\n"},"TCRBrandID":{"type":"string","minLength":1,"pattern":"^brd_[0-9a-hjkmnp-tv-z]{26}$","example":"brd_01krdgeqcxet5s7t44vh8rt9mg"},"TCRBrandSubmissionID":{"type":"string","minLength":1,"pattern":"^brs_[0-9a-hjkmnp-tv-z]{26}$","example":"brs_01krdgeqcxet5s7t44vh8rt9mg"},"TCRSubmissionStatus":{"type":"string","minLength":1,"enum":["submitted","approved","rejected"],"description":"The outcome of a single submission attempt, for either a 10DLC brand or a campaign. An attempt is `submitted` while the registration it belongs to is pending; `approved` and `rejected` are the terminal outcomes. `rejected` also covers a registration the campaign registry suspended or limited. In every rejected case, you can correct the details and submit a new attempt.\n"},"TCRSubmissionRejectionReason":{"type":"object","additionalProperties":false,"description":"One reason a 10DLC submission attempt was rejected. The campaign registry reports a list of these, so a single rejection can name several problems at once, each pointing at the fields it applies to.","required":["description"],"properties":{"registry_error_code":{"type":"string","maxLength":100,"description":"The campaign registry's own error code for this reason, when it reported one, for low-level debugging."},"description":{"type":"string","minLength":1,"maxLength":4096,"description":"What is wrong, in the campaign registry's own words when it reported one."},"fields":{"type":"array","maxItems":50,"description":"The submitted fields this reason applies to, named as they appear on the submission. Empty when it is not specific to a field.","items":{"type":"string","minLength":1,"maxLength":100}}}},"TCREntityType":{"type":"string","minLength":1,"enum":["sole_proprietor","private_profit","public_profit","non_profit","government"],"description":"The legal-entity classification of the brand. This determines which other fields are required. Sole proprietors must supply `contact_first_name`, `contact_last_name`, and `contact_mobile_phone`. Publicly traded companies must supply `stock_symbol`, `stock_exchange`, and `business_verification_email`. All other types must supply `legal_entity_name` and `business_registration_number`.\n"},"TCRStockExchange":{"type":"string","minLength":1,"enum":["amex","amx","asx","b3","bme","bse","fra","icex","jpx","jse","krx","lon","nasdaq","nse","nyse","omx","sehk","sgx","sse","sto","swx","szse","tsx","twse","vse","none","other"],"description":"The stock exchange the brand is listed on. Required for publicly-traded companies."},"TCRVertical":{"type":"string","minLength":1,"enum":["agriculture","communication","construction","education","energy","entertainment","financial","gambling","government","healthcare","hospitality","human_resources","insurance","legal","manufacturing","ngo","political","postal","professional","real_estate","retail","technology","transportation"],"description":"The industry vertical the brand operates in."},"TCRAltBusinessIDType":{"type":"string","minLength":1,"enum":["none","duns","giin","lei"],"description":"The scheme of the alternative business identifier supplied in `alt_business_id`:\n\n- `none`: No alternative business identifier is supplied.\n- `duns`: A D-U-N-S Number, the nine-digit identifier issued by Dun & Bradstreet.\n- `giin`: A Global Intermediary Identification Number, issued under FATCA registration.\n- `lei`: A Legal Entity Identifier, the ISO 17442 code for parties to financial transactions.\n"},"TCRBrandSubmission":{"description":"One attempt to register a 10DLC brand's details. The submitted business details are fixed at creation. Resubmit a rejected brand as a new attempt instead of editing this one. The details of every attempt therefore remain recoverable. The `submitted_to_registry_at` field is set once the attempt reaches the campaign registry. The `status`, `resolved_at`, and `rejection_reasons` fields update once the attempt is resolved.","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","brand_id","organization_id","workspace_id","attempt_number","rejection_reasons","status","business_entity_type","business_display_name","contact_phone","business_address_line1","business_address_city","business_address_postal_code","business_address_country","contact_email"],"properties":{"next":{"type":"array","readOnly":true,"description":"What to do next about this attempt, given the state it is in. Each entry names one action and\nsays why it is worth taking, so you can act on this response without working out the order\nyourself. Present on reads that compute it: an empty list means there is nothing to do, and\nthe field is absent entirely on responses that do not report next actions.\n\nAn attempt reports its own outcome; the brand it belongs to is what the outcome is applied to.\nSo once this attempt resolves, this points at the brand rather than repeating a verdict the\nbrand owns.\n","items":{"$ref":"#/components/schemas/NextAction"}},"id":{"readOnly":true,"$ref":"#/components/schemas/TCRBrandSubmissionID"},"brand_id":{"readOnly":true,"$ref":"#/components/schemas/TCRBrandID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"attempt_number":{"type":"integer","minimum":1,"readOnly":true,"description":"The 1-based position of this attempt in the brand's submission history."},"status":{"readOnly":true,"$ref":"#/components/schemas/TCRSubmissionStatus"},"rejection_reasons":{"type":"array","readOnly":true,"description":"Why this submission was rejected, one entry per problem. Empty for an attempt that has not been rejected.","items":{"$ref":"#/components/schemas/TCRSubmissionRejectionReason"}},"business_entity_type":{"readOnly":true,"$ref":"#/components/schemas/TCREntityType"},"business_display_name":{"type":"string","minLength":1,"readOnly":true,"description":"The name the business trades under, shown to recipients."},"legal_entity_name":{"type":"string","readOnly":true,"description":"Legal company name."},"contact_first_name":{"type":"string","readOnly":true,"description":"Contact first name."},"contact_last_name":{"type":"string","readOnly":true,"description":"Contact last name."},"business_registration_number":{"type":"string","readOnly":true,"description":"Government-issued tax ID, such as a US EIN or Canadian BN9."},"business_registration_country":{"readOnly":true,"description":"ISO 3166-1 alpha-2 country code that issued the tax ID.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"stock_symbol":{"type":"string","readOnly":true,"description":"Stock ticker symbol."},"stock_exchange":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TCRStockExchange"}]},"contact_phone":{"type":"string","minLength":1,"readOnly":true,"description":"Contact phone number in E.164 format, such as `+12025550123`."},"contact_mobile_phone":{"type":"string","minLength":1,"readOnly":true,"description":"The mobile number the campaign registry texts its identity check to, in E.164 format. Set only for sole-proprietor brands."},"business_address_line1":{"type":"string","minLength":1,"readOnly":true,"description":"Street address of the registered business."},"business_address_city":{"type":"string","minLength":1,"readOnly":true,"description":"City of the registered business."},"business_address_state":{"type":"string","readOnly":true,"description":"State, province, or region of the registered business."},"business_address_postal_code":{"type":"string","minLength":1,"readOnly":true,"description":"Postal or ZIP code of the registered business."},"business_address_country":{"readOnly":true,"description":"ISO 3166-1 alpha-2 country code of the registered business.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"contact_email":{"type":"string","minLength":1,"readOnly":true,"description":"Contact email address for the brand."},"business_website":{"type":"string","readOnly":true,"description":"The brand's website URL."},"vertical":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TCRVertical"}]},"alt_business_id":{"type":"string","readOnly":true,"description":"An alternative business identifier, such as a DUNS, GIIN, or LEI number."},"alt_business_id_type":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TCRAltBusinessIDType"}]},"business_verification_email":{"type":"string","readOnly":true,"description":"Business contact email used to verify a publicly-traded company."},"submitted_to_registry_at":{"type":"string","format":"date-time","readOnly":true,"description":"When this attempt was sent to the campaign registry, once it has been sent."},"resolved_at":{"type":"string","format":"date-time","readOnly":true,"description":"When this attempt was approved or rejected, when applicable."}}}]},"TCRBrand":{"description":"A 10DLC brand registration. The business details are carried by its submissions: `current_submission` embeds the latest attempt.","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","organization_id","workspace_id","status","tcr_brand_id","identity_id","has_campaigns","auto_request_auth_plus","current_submission"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/TCRBrandID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"status":{"readOnly":true,"$ref":"#/components/schemas/TCRBrandStatus"},"next":{"type":"array","readOnly":true,"description":"What to do next about this brand, given the state it is in. Each entry names one action and\nsays why it is worth taking, so you can act on this response without working out the order\nyourself. Present on reads that compute it: an empty list means there is nothing to do, and\nthe field is absent entirely on responses that do not report next actions.\n\nAn approved brand clears no numbers on its own, so an empty list here does not mean you can\nsend: it means nothing about the BRAND is outstanding. What each campaign under it still\nneeds is reported on that campaign.\n","items":{"$ref":"#/components/schemas/NextAction"}},"tcr_brand_id":{"type":["string","null"],"readOnly":true,"description":"Identifier assigned to this brand by The Campaign Registry, such as `BF9FEJS`. Null until the registry has processed the brand. Quote it when referencing the brand with the registry or with a carrier.\n"},"has_campaigns":{"type":"boolean","readOnly":true,"description":"Whether this brand carries at least one campaign. An approved brand clears no numbers on its own, so this is what distinguishes one that still needs its first campaign from one whose campaign already exists, a distinction `status` cannot make: creating a campaign leaves the brand approved. It reports only that a campaign exists, not that any campaign could send. Each campaign's own `status` answers that.\n"},"auto_request_auth_plus":{"type":"boolean","readOnly":true,"description":"Whether Bird will request Auth+ vetting for this brand once it is approved, as chosen when the brand was registered or last resubmitted. Only ever true for an entity type that requires Auth+.\n"},"identity_id":{"readOnly":true,"description":"The compliance party this brand describes, and null when none was named at registration.\n","oneOf":[{"$ref":"#/components/schemas/ComplianceIdentityID"},{"type":"null"}]},"current_submission":{"readOnly":true,"description":"The brand's most recent submission attempt, including the details that were submitted.","allOf":[{"$ref":"#/components/schemas/TCRBrandSubmission"}]}}}]},"TCRBrandList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The workspace's 10DLC brands.","items":{"$ref":"#/components/schemas/TCRBrand"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"TCRBrandCreate":{"type":"object","additionalProperties":false,"description":"A 10DLC brand to register. Which of the optional fields are required depends on `business_entity_type` (see its description). Some fields must be omitted for the wrong entity type. The API validates the combination and returns a `422` response listing every field to fix.","example":{"business_entity_type":"private_profit","business_display_name":"Acme Corp","legal_entity_name":"Acme Corporation Inc.","business_registration_number":"123456789","business_registration_country":"US","contact_phone":"+12025550123","business_address_line1":"1 Infinite Loop","business_address_city":"Cupertino","business_address_state":"CA","business_address_postal_code":"95014","business_address_country":"US","contact_email":"contact@acme.com","business_website":"https://acme.com","vertical":"technology"},"required":["business_entity_type","business_display_name","contact_phone","business_address_line1","business_address_city","business_address_state","business_address_postal_code","business_address_country","contact_email","business_website","vertical"],"properties":{"business_entity_type":{"$ref":"#/components/schemas/TCREntityType"},"business_display_name":{"type":"string","minLength":2,"maxLength":255,"description":"The name the business trades under, shown to recipients. It does not have to match `legal_entity_name`. Required for every entity type.","example":"Acme Corp"},"legal_entity_name":{"type":"string","minLength":2,"maxLength":255,"description":"Legal company name. Required for all entity types except sole proprietors."},"contact_first_name":{"type":"string","maxLength":100,"description":"Contact first name. Required for sole proprietors."},"contact_last_name":{"type":"string","maxLength":100,"description":"Contact last name. Required for sole proprietors."},"business_registration_number":{"type":"string","maxLength":21,"description":"Government-issued tax ID, such as a US EIN or Canadian BN9. Required for all entity types except sole proprietors."},"business_registration_country":{"description":"ISO 3166-1 alpha-2 country code that issued the tax ID. Required for all entity types except sole proprietors.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"stock_symbol":{"type":"string","maxLength":10,"description":"Stock ticker symbol. Required for publicly-traded companies, and must be omitted for every other entity type, as must `stock_exchange`."},"stock_exchange":{"$ref":"#/components/schemas/TCRStockExchange"},"contact_phone":{"type":"string","minLength":1,"description":"Contact phone number in E.164 format, such as `+12025550123`.","example":"+12025550123"},"contact_mobile_phone":{"type":"string","minLength":1,"description":"Mobile phone number in E.164 format, such as `+12025550123`. Required for sole proprietors, and it must be able to receive text messages. The campaign registry texts an identity check to it before approving the brand."},"business_address_line1":{"type":"string","minLength":2,"maxLength":100,"description":"Street address of the registered business.","example":"1 Infinite Loop"},"business_address_city":{"type":"string","minLength":2,"maxLength":100,"description":"City of the registered business.","example":"Cupertino"},"business_address_state":{"type":"string","minLength":2,"maxLength":20,"description":"State, province, or region of the registered business. For a business registered in the United States this must be the two-letter state or district code, such as `CA`. US territories are not accepted by the campaign registry.","example":"CA"},"business_address_postal_code":{"type":"string","minLength":2,"maxLength":10,"description":"Postal or ZIP code of the registered business. A business registered in the United States must use `#####` or `#####-####`.","example":"95014"},"business_address_country":{"description":"ISO 3166-1 alpha-2 country code of the registered business.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"contact_email":{"type":"string","minLength":1,"maxLength":100,"description":"Contact email address for the brand.","example":"contact@acme.com"},"business_website":{"type":"string","minLength":1,"description":"The brand's website URL. The campaign registry requires one for every brand, and uses it to verify that the business is what it says it is.","example":"https://acme.com"},"vertical":{"$ref":"#/components/schemas/TCRVertical"},"alt_business_id":{"type":"string","maxLength":50,"description":"An alternative business identifier, such as a DUNS, GIIN, or LEI number."},"alt_business_id_type":{"$ref":"#/components/schemas/TCRAltBusinessIDType"},"business_verification_email":{"type":"string","maxLength":100,"description":"Business contact email used to verify a publicly-traded company. Required for public companies."},"auto_request_auth_plus":{"type":"boolean","default":false,"description":"Whether Bird should request Auth+ vetting for this brand as soon as it is approved. Auth+ is required before a publicly-traded company's brand can register any campaign, so opting in here saves a round trip once the registry answers. Only accepted for entity types that require Auth+; sending `true` for any other entity type is a `422`. The vetting fee is charged when the request is placed, not at registration."},"identity_id":{"description":"The business this brand is for, recorded so registrations for the same business are recognisable as one. Optional, and it must belong to this workspace. The brand is registered from the details in this request either way.","$ref":"#/components/schemas/ComplianceIdentityID"}}},"TCRBrandPrecheckRequest":{"type":"object","additionalProperties":false,"description":"The brand details to evaluate. Every field is optional. The prediction treats missing fields as risk factors rather than errors, so an in-progress form can still be prechecked.","properties":{"business_entity_type":{"$ref":"#/components/schemas/TCREntityType"},"business_display_name":{"type":"string","maxLength":255,"description":"The name the business trades under, shown to recipients."},"legal_entity_name":{"type":"string","maxLength":255,"description":"Legal company name."},"contact_first_name":{"type":"string","maxLength":100,"description":"Contact first name."},"contact_last_name":{"type":"string","maxLength":100,"description":"Contact last name."},"business_registration_number":{"type":"string","maxLength":21,"description":"Government-issued tax ID, such as a US EIN or Canadian BN9."},"business_registration_country":{"description":"ISO 3166-1 alpha-2 country code that issued the tax ID.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"stock_symbol":{"type":"string","maxLength":10,"description":"Stock ticker symbol."},"stock_exchange":{"$ref":"#/components/schemas/TCRStockExchange"},"contact_phone":{"type":"string","description":"Contact phone number in E.164 format, such as `+12025550123`."},"contact_mobile_phone":{"type":"string","description":"The sole proprietor's mobile number, in E.164 format."},"business_address_line1":{"type":"string","maxLength":100,"description":"Street address of the registered business."},"business_address_city":{"type":"string","maxLength":100,"description":"City of the registered business."},"business_address_state":{"type":"string","description":"State, province, or region of the registered business."},"business_address_postal_code":{"type":"string","description":"Postal or ZIP code of the registered business."},"business_address_country":{"description":"ISO 3166-1 alpha-2 country code of the registered business.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"contact_email":{"type":"string","description":"Contact email address for the brand."},"business_website":{"type":"string","description":"The brand's website URL."},"vertical":{"$ref":"#/components/schemas/TCRVertical"},"alt_business_id":{"type":"string","maxLength":50,"description":"An alternative business identifier, such as a DUNS, GIIN, or LEI number."},"alt_business_id_type":{"$ref":"#/components/schemas/TCRAltBusinessIDType"},"business_verification_email":{"type":"string","description":"Business contact email used to verify a publicly-traded company."}}},"TCRRiskFlag":{"type":"object","additionalProperties":false,"description":"A specific issue in the brand details that could delay or reject registration.","required":["severity","title","field","issue","fix"],"properties":{"severity":{"type":"string","minLength":1,"enum":["error","warning","information"],"readOnly":true,"description":"How the flag should be handled.\n\n- `error`: causes The Campaign Registry to reject the registration.\n- `warning`: does not reject it but may lower the brand's trust score.\n- `information`: context that does not affect the approval outcome.\n"},"title":{"type":"string","minLength":1,"readOnly":true,"description":"A short, one-line summary of the flag, suitable as an accordion row label. The full explanation lives in `issue`, the remediation in `fix`."},"field":{"type":"string","minLength":1,"readOnly":true,"description":"The brand field the issue relates to."},"issue":{"type":"string","minLength":1,"readOnly":true,"description":"What is likely to cause a delay or rejection."},"fix":{"type":"string","minLength":1,"readOnly":true,"description":"How to resolve the issue before submitting."}}},"ComplianceDataType":{"type":"string","minLength":1,"enum":["string","text","enum","number","boolean","date","url","country","document"],"description":"The kind of value a requirement takes. The `string` value is a single line, while `text` runs to several. The `enum` value accepts only the values listed with the requirement. The `number`, `boolean`, and `date` values take their corresponding data types. The `url` value takes a link. The `country` value takes an uppercase ISO 3166-1 alpha-2 code, such as `NL`. The `document` value means a file you upload first and then reference. All other values are supplied inline.\n"},"ComplianceRequirementFieldOption":{"type":"object","additionalProperties":false,"required":["value","label"],"properties":{"value":{"type":"string","minLength":1,"description":"What to send."},"label":{"type":"string","minLength":1,"description":"What to show."}}},"ComplianceFieldValidation":{"type":"object","additionalProperties":false,"description":"Constraints a value must satisfy, where the requirement sets any. Which of these apply follows from its data type: `pattern`, `min_length` and `max_length` constrain `string` and `text` values, `minimum` and `maximum` constrain `number` values, and everything else is `null`. Check these before submitting and you avoid a round trip.\n","properties":{"pattern":{"type":["string","null"],"description":"A regular expression the value must match. `null` when the requirement takes neither `string` nor `text`, or sets no pattern."},"min_length":{"type":["integer","null"],"description":"Shortest accepted value. `null` when the requirement takes neither `string` nor `text`, or sets no minimum length."},"max_length":{"type":["integer","null"],"description":"Longest accepted value. `null` when the requirement takes neither `string` nor `text`, or sets no maximum length."},"minimum":{"type":["number","null"],"description":"Smallest accepted number. `null` when the requirement does not take `number`, or sets no minimum."},"maximum":{"type":["number","null"],"description":"Largest accepted number. `null` when the requirement does not take `number`, or sets no maximum."}}},"AttachmentID":{"type":"string","minLength":1,"pattern":"^tca_[0-9a-hjkmnp-tv-z]{26}$","example":"tca_01krdgeqcxet5s7t44vh8rt9mg"},"ComplianceItemState":{"type":"string","minLength":1,"x-extensible-enum":["not_supplied","supplied","in_review","approved","rejected","not_applicable","expired"],"description":"Where one requirement stands. The `not_supplied` state means nothing has been\ngiven for it yet. The `supplied` state means we have your answer and review\nhas not started. The `in_review` state means someone is looking at it. The\n`approved` and `rejected` states are decisions. You can replace a rejected\nitem without refiling the rest. The `not_applicable` state means this\nrequirement does not apply to you. The `expired` state means the value was\naccepted but has since gone stale.\n\nAn item only ever reports its state. Treat this as an open set because review\ncan gain stages. A value you do not recognise is a valid state that this\nserver added.\n"},"ComplianceRequirementField":{"type":"object","additionalProperties":false,"required":["key","label","help_text","data_type","required","section","section_label","options","validation","template_url","max_items","value","prefilled","attachment_id","values","attachment_ids","state","decided_at","expires_at","reason","editable_for_verifier_repair"],"properties":{"key":{"type":"string","minLength":1,"readOnly":true,"description":"Identifies this requirement. Use it as the key when you submit a value."},"label":{"type":"string","minLength":1,"readOnly":true,"description":"What to call this requirement when you ask someone for it."},"help_text":{"type":["string","null"],"readOnly":true,"description":"Guidance on what a good answer looks like, including anything the destination country expects. `null` when the label speaks for itself.\n"},"data_type":{"allOf":[{"$ref":"#/components/schemas/ComplianceDataType"}],"readOnly":true},"required":{"type":"boolean","readOnly":true,"description":"Whether an answer is needed before the registration can be approved. Optional requirements are still reviewed when you supply them.\n"},"section":{"type":"string","minLength":1,"readOnly":true,"description":"A stable key grouping related requirements together. Use it to keep your own grouping consistent across requests; use section_label for what to show someone.\n"},"section_label":{"type":"string","minLength":1,"readOnly":true,"description":"What to call this requirement's group when you present it."},"options":{"type":"array","readOnly":true,"description":"The accepted values, already resolved, when the data type is `enum`. Empty for every other type.\n","items":{"$ref":"#/components/schemas/ComplianceRequirementFieldOption"}},"validation":{"allOf":[{"$ref":"#/components/schemas/ComplianceFieldValidation"}],"readOnly":true},"template_url":{"type":["string","null"],"readOnly":true,"description":"A blank form to download, complete and upload back, for requirements that need a signed document. `null` when the requirement has no form to download.\n"},"max_items":{"type":["integer","null"],"readOnly":true,"description":"How many answers this requirement takes, when it takes more than one. `null` for a requirement answered once, which is most of them. Where it is set, answer with `values` or `attachment_ids` and read your answer back from the same pair; `value` and `attachment_id` stay `null`. Order is kept as you send it, and it is part of the answer: a keyword list beginning with `STOP` is not the same declaration as one ending with it.\n"},"value":{"type":["string","null"],"readOnly":true,"description":"Your answer, for a requirement answered inline. Every answer travels as a string whatever its data type, so a number reads back as `\"1500\"` and a yes-or-no as `\"true\"`. Use the data type to parse it. `null` for a requirement that takes a document, and before you have answered.\n"},"prefilled":{"type":"boolean","readOnly":true,"description":"`true` when `value` comes from the party this registration is for. Accept it as it stands or replace it; either way it becomes your answer when you submit. Only a requirement that asks about the party is ever prefilled.\n"},"attachment_id":{"readOnly":true,"description":"The document you supplied, for a requirement that takes one. `null` otherwise.\n","oneOf":[{"$ref":"#/components/schemas/AttachmentID"},{"type":"null"}]},"values":{"type":["array","null"],"readOnly":true,"description":"Your answers, for a requirement that takes several and is answered inline. Set only when `max_items` is, and `null` otherwise, including before you have answered. Each answer travels as a string whatever the data type, the same as `value`.\n","items":{"type":"string","minLength":1}},"attachment_ids":{"type":["array","null"],"readOnly":true,"description":"The documents you supplied, for a requirement that takes several. Set only when `max_items` is and the requirement takes documents, and `null` otherwise.\n","items":{"$ref":"#/components/schemas/AttachmentID"}},"state":{"allOf":[{"$ref":"#/components/schemas/ComplianceItemState"}],"readOnly":true},"decided_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this was decided. `null` while no decision has been made."},"expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this answer goes stale and must be supplied again. `null` when it does not expire."},"editable_for_verifier_repair":{"type":"boolean","readOnly":true,"description":"Whether you may replace this accepted answer to correct the submission after external verification. This permission does not change its `state` or `reason`. Submit only answers whose content or attachment identity you change.\n"},"reason":{"type":["string","null"],"readOnly":true,"description":"Why this requirement was rejected, when it was. `null` otherwise."}}},"TCRBrandRequirements":{"type":"object","additionalProperties":false,"description":"What a 10DLC brand needs before the campaign registry will accept it, in the order to present it, plus what you have already supplied and where each item stands.","required":["brand_id","satisfied","needs_input","fields"],"properties":{"next":{"type":"array","readOnly":true,"description":"What to do next about this brand, given the state it is in. Each entry names one\naction and says why it is worth taking. An empty list means there is nothing to do; the\nfield is absent on responses that do not compute next actions.\n","items":{"$ref":"#/components/schemas/NextAction"}},"brand_id":{"readOnly":true,"description":"The brand these answers belong to. `null` when you have not started one, in which case the field list is the blank form.","oneOf":[{"$ref":"#/components/schemas/TCRBrandID"},{"type":"null"}]},"satisfied":{"type":"boolean","readOnly":true,"description":"Whether every required item has an answer the registry accepted. Optional items never hold it back, whatever state they are in."},"needs_input":{"type":"boolean","readOnly":true,"description":"Whether anything is waiting on you: a required item with no answer, or one that was refused. False while every required item is either settled or with the registry, which is what separates \"we need something from you\" from \"we are reviewing\". Those are two states `satisfied` reports alike, since it reports only whether everything was accepted."},"fields":{"type":"array","readOnly":true,"description":"Every item this brand requires, in the order to present them. The same shape the other registration programmes return.","items":{"$ref":"#/components/schemas/ComplianceRequirementField"}}}},"TCRCampaignStatus":{"type":"string","minLength":1,"enum":["pending","approved","rejected","expired","suspended"],"description":"The registration status of the campaign. A newly created campaign is `pending`; `approved` and `rejected` reflect the registration outcome. An approved campaign later becomes `expired` when its registration lapses, or `suspended` when a carrier suspends it. Neither can be resubmitted, so running the campaign again means creating a new one.\n"},"TCRCampaignID":{"type":"string","minLength":1,"pattern":"^cmp_[0-9a-hjkmnp-tv-z]{26}$","example":"cmp_01krdgeqcxet5s7t44vh8rt9mg"},"TCRCampaignSubmissionID":{"type":"string","minLength":1,"pattern":"^cms_[0-9a-hjkmnp-tv-z]{26}$","example":"cms_01krdgeqcxet5s7t44vh8rt9mg"},"TCRCampaignUseCase":{"type":"string","minLength":1,"enum":["two_factor_authentication","account_notification","agents_franchises","charity","customer_care","delivery_notification","fraud_alert","higher_education","k12_education","low_volume","m2m","marketing","mixed","political","polling_voting","proxy","public_service_announcement","security_alert","sweepstake"],"description":"The messaging use case the campaign runs. `mixed` and `low_volume` are umbrella use cases that require a set of sub-use-cases drawn from this same list. `m2m` is machine-to-machine messaging with no human recipient. `agents_franchises` covers messaging sent by a business's agents or franchises on the brand's behalf. `proxy` covers messages relayed between two parties through an intermediary number, such as a marketplace connecting a buyer and a seller, without exposing either party's own number.\n"},"AttachmentStatus":{"type":"string","minLength":1,"enum":["draft","attached"],"description":"Lifecycle of an attachment. `draft` is a file that has been uploaded but nothing\nhas been registered or submitted with it yet, and it is discarded at its\n`expires_at`. `attached` means at least one registration has cited it, so it is\nkept permanently and can no longer be deleted.\n"},"Attachment":{"description":"A supporting document uploaded for an SMS registration (10DLC campaign evidence, toll-free verification opt-in screenshots). Reference it from the registration's own attachment-id field to submit it as evidence.","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","filename","content_type","size_bytes","download_url","download_url_expires_at","status","expires_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AttachmentID"},"filename":{"type":"string","minLength":1,"readOnly":true,"description":"The uploaded file's name."},"content_type":{"type":"string","minLength":1,"readOnly":true,"description":"The file's content type, determined from its contents."},"size_bytes":{"type":"integer","format":"int64","readOnly":true,"description":"The file's size in bytes."},"description":{"type":"string","maxLength":255,"readOnly":true,"description":"A short note describing what the file shows."},"status":{"readOnly":true,"$ref":"#/components/schemas/AttachmentStatus"},"download_url":{"type":"string","format":"uri","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Short-lived signed URL for downloading or previewing the attachment. Valid for 24 hours from when the resource was fetched; request a fresh resource to obtain a new URL after expiry. Do not cache beyond `download_url_expires_at`. Registration authorities (10DLC and toll-free carriers) retrieve evidence via a separate, longer-lived token; this URL is not that token."},"download_url_expires_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When `download_url` expires. Both fields are always present; the server returns an error rather than omitting them."},"expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this attachment is discarded if nothing is registered or submitted with it. Null once its status is `attached`."}}}]},"TCRCampaignSubmission":{"description":"One attempt to register a 10DLC campaign's details. The submitted details are fixed at creation. Resubmit a rejected campaign as a new attempt instead of editing this one. The details of every attempt therefore remain recoverable. The `submitted_to_registry_at` field is set once the attempt reaches the campaign registry. The `status`, `resolved_at`, and `rejection_reasons` fields update once the attempt is resolved.","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","campaign_id","organization_id","workspace_id","attempt_number","rejection_reasons","status","use_case_category","sub_use_cases","name","use_case_description","opt_in_description","message_samples","opt_in_keywords","opt_out_keywords","help_keywords","embedded_link","embedded_phone","age_gated","direct_lending","subscriber_opt_in","subscriber_opt_out","subscriber_help"],"properties":{"next":{"type":"array","readOnly":true,"description":"What to do next about this attempt, given the state it is in. Each entry names one action and\nsays why it is worth taking, so you can act on this response without working out the order\nyourself. Present on reads that compute it: an empty list means there is nothing to do, and\nthe field is absent entirely on responses that do not report next actions.\n\nAn attempt reports its own outcome; the campaign it belongs to is what the outcome is applied\nto. So once this attempt resolves, this points at the campaign rather than repeating a verdict\nthe campaign owns.\n","items":{"$ref":"#/components/schemas/NextAction"}},"id":{"readOnly":true,"$ref":"#/components/schemas/TCRCampaignSubmissionID"},"campaign_id":{"readOnly":true,"$ref":"#/components/schemas/TCRCampaignID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"attempt_number":{"type":"integer","minimum":1,"readOnly":true,"description":"The 1-based position of this attempt in the campaign's submission history."},"status":{"readOnly":true,"$ref":"#/components/schemas/TCRSubmissionStatus"},"rejection_reasons":{"type":"array","readOnly":true,"description":"Why this submission was rejected, one entry per problem. Empty for an attempt that has not been rejected.","items":{"$ref":"#/components/schemas/TCRSubmissionRejectionReason"}},"use_case_category":{"readOnly":true,"$ref":"#/components/schemas/TCRCampaignUseCase"},"sub_use_cases":{"type":"array","readOnly":true,"description":"Sub-use-cases, required when `use_case_category` is `mixed` or `low_volume`.","items":{"$ref":"#/components/schemas/TCRCampaignUseCase"}},"name":{"type":"string","minLength":1,"readOnly":true,"description":"A short name for the campaign."},"use_case_description":{"type":"string","minLength":1,"readOnly":true,"description":"What the campaign is used for."},"opt_in_description":{"type":"string","minLength":1,"readOnly":true,"description":"How recipients opt in to the campaign and what messages they receive."},"help_response":{"type":"string","readOnly":true,"description":"The reply sent when a recipient texts a help keyword. Absent for an `m2m` campaign, which has no human recipient."},"message_samples":{"type":"array","readOnly":true,"description":"Example messages sent by the campaign.","items":{"type":"string","minLength":1}},"opt_in_keywords":{"type":"array","readOnly":true,"description":"Keywords a recipient can text to opt in.","items":{"type":"string"}},"opt_in_confirmation_response":{"type":"string","readOnly":true,"description":"The confirmation reply sent when a recipient opts in."},"opt_out_keywords":{"type":"array","readOnly":true,"description":"Keywords a recipient can text to opt out.","items":{"type":"string"}},"opt_out_confirmation_response":{"type":"string","readOnly":true,"description":"The confirmation reply sent when a recipient opts out."},"help_keywords":{"type":"array","readOnly":true,"description":"Keywords a recipient can text to request help.","items":{"type":"string"}},"embedded_link":{"type":"boolean","readOnly":true,"description":"Whether campaign messages contain a link."},"embedded_phone":{"type":"boolean","readOnly":true,"description":"Whether campaign messages contain a phone number."},"age_gated":{"type":"boolean","readOnly":true,"description":"Whether the campaign contains age-gated content."},"direct_lending":{"type":"boolean","readOnly":true,"description":"Whether the campaign involves direct lending or a loan arrangement."},"subscriber_opt_in":{"type":"boolean","readOnly":true,"description":"Whether recipients can opt in to the campaign."},"subscriber_opt_out":{"type":"boolean","readOnly":true,"description":"Whether recipients can opt out of the campaign."},"subscriber_help":{"type":"boolean","readOnly":true,"description":"Whether recipients can request help from the campaign."},"attachments":{"type":"array","readOnly":true,"description":"The supporting documents submitted with this attempt.","items":{"$ref":"#/components/schemas/Attachment"}},"submitted_to_registry_at":{"type":"string","format":"date-time","readOnly":true,"description":"When this attempt was sent to the campaign registry, once it has been sent."},"resolved_at":{"type":"string","format":"date-time","readOnly":true,"description":"When this attempt was approved or rejected, when applicable."}}}]},"TCRCampaign":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","organization_id","workspace_id","brand_id","status","tcr_campaign_id","numbers_linked","current_submission"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/TCRCampaignID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"brand_id":{"description":"The 10DLC brand this campaign runs under.","allOf":[{"$ref":"#/components/schemas/TCRBrandID"}]},"status":{"readOnly":true,"$ref":"#/components/schemas/TCRCampaignStatus"},"next":{"type":"array","readOnly":true,"description":"What to do next about this campaign, given the state it is in. Each entry names one action and\nsays why it is worth taking, so you can act on this response without working out the order\nyourself. Present on reads that compute it: an empty list means there is nothing to do, and\nthe field is absent entirely on responses that do not report next actions.\n\nAn approved campaign clears nothing until numbers are linked to it, and what the carriers made\nof each number is reported on that number's own assignment rather than here.\n","items":{"$ref":"#/components/schemas/NextAction"}},"tcr_campaign_id":{"type":["string","null"],"readOnly":true,"description":"The identifier The Campaign Registry assigned this campaign, for example `CJKL2MN`. Null until the registry has processed the campaign. Quote it when referencing the campaign with the registry or with a carrier.\n"},"numbers_linked":{"type":"boolean","readOnly":true,"description":"Whether any number is still assigned to this campaign. An approved campaign clears nothing until numbers are linked to it, so this is what distinguishes one waiting for its numbers from one already carrying them, a distinction `status` cannot make: assigning numbers leaves the campaign approved. A number the carriers refused still counts, because that assignment's own status is where its remedy is reported; a number removed from the campaign does not.\n"},"current_submission":{"readOnly":true,"description":"The most recent submission attempt for this campaign, including the details that were submitted.","allOf":[{"$ref":"#/components/schemas/TCRCampaignSubmission"}]}}}]},"TCRCampaignList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The workspace's registered 10DLC campaigns.","items":{"$ref":"#/components/schemas/TCRCampaign"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"TCRCampaignFields":{"type":"object","required":["name","use_case_description","opt_in_description","message_samples"],"properties":{"name":{"type":"string","minLength":1,"maxLength":40,"description":"A short name for the campaign.","example":"Order updates"},"use_case_description":{"type":"string","minLength":40,"maxLength":4096,"description":"What the campaign is used for. Carriers reject descriptions that are too vague to review.","example":"Sends order confirmation and shipping status updates to customers who opted in at checkout."},"opt_in_description":{"type":"string","minLength":40,"maxLength":4096,"description":"How recipients opt in to the campaign and what messages they receive. Describe where consent is collected, since carriers review this before approving the campaign.","example":"Customers opt in by checking a box at checkout on acme.com. They then receive order and shipping status texts."},"help_response":{"type":"string","maxLength":255,"description":"The reply sent when a recipient texts a help keyword. Required when the campaign supports the help command, and must be at least 20 characters when set."},"message_samples":{"type":"array","minItems":1,"maxItems":5,"description":"Example messages sent by the campaign.","items":{"type":"string","minLength":20,"maxLength":1024,"example":"Your order #1234 has shipped and will arrive Thursday."}},"opt_in_keywords":{"type":"array","maxItems":5,"description":"Keywords a recipient can text to opt in.","items":{"type":"string","maxLength":34}},"opt_in_confirmation_response":{"type":"string","maxLength":320,"description":"The confirmation reply sent when a recipient opts in. Must be at least 20 characters when set."},"opt_out_keywords":{"type":"array","maxItems":5,"description":"Keywords a recipient can text to opt out. Must include `STOP`, which carriers treat as the minimum. Required when the campaign supports opt-out.","items":{"type":"string","maxLength":34}},"opt_out_confirmation_response":{"type":"string","maxLength":320,"description":"The confirmation reply sent when a recipient opts out. Required when the campaign supports opt-out, and must be at least 20 characters when set."},"help_keywords":{"type":"array","maxItems":5,"description":"Keywords a recipient can text to request help. Must include `HELP`, which carriers treat as the minimum. Required when the campaign supports the help command.","items":{"type":"string","maxLength":34}},"embedded_link":{"type":"boolean","default":false,"description":"Whether campaign messages contain a link. Declared to the registry as you set it and weighed by carriers when they review. The API does not check it against `message_samples`, so a sample carrying a URL while this is false reaches the reviewer as a contradiction rather than a `422`."},"embedded_phone":{"type":"boolean","default":false,"description":"Whether campaign messages contain a phone number. Declared to the registry as you set it and weighed by carriers when they review. The API does not check it against `message_samples`."},"age_gated":{"type":"boolean","default":false,"description":"Whether campaign messages carry content restricted to recipients aged 18 or over. Declared to the registry, and carriers review an age-gated campaign against their own content rules."},"direct_lending":{"type":"boolean","default":false,"description":"Whether the campaign involves direct lending or a loan arrangement. Declared to the registry, and carriers hold a lending campaign to stricter review."},"attachment_ids":{"type":"array","maxItems":5,"uniqueItems":true,"description":"Supporting documents to submit with this registration. Upload each file with `POST /v1/compliance/attachments` first, then list the returned IDs here. Registering a campaign with an attachment makes it permanent, so the same file can be reused across campaigns and on later attempts.","items":{"$ref":"#/components/schemas/AttachmentID"}}}},"TCRCampaignCreate":{"description":"A 10DLC campaign to register under a brand. When `use_case_category` is `mixed` or `low_volume`, `sub_use_cases` must list two to five sub-use-cases. Two message samples are required for the `k12_education`, `marketing`, `mixed`, `agents_franchises`, `charity`, `political`, `proxy`, and `sweepstake` use cases; one is enough for every other use case, `low_volume` included, even though it is an umbrella use case like `mixed`. The API validates the combination and returns a `422` response listing any problems.","example":{"name":"Order updates","use_case_description":"Sends order confirmation and shipping status updates to customers who opted in at checkout.","opt_in_description":"Customers opt in by checking a box at checkout on acme.com. They then receive order and shipping status texts.","message_samples":["Your order #1234 has shipped and will arrive Thursday."],"brand_id":"brd_01krdgeqcxet5s7t44vh8rt9mg","use_case_category":"delivery_notification","subscriber_opt_in":true,"subscriber_opt_out":true,"subscriber_help":true,"opt_out_keywords":["STOP"],"opt_out_confirmation_response":"You are unsubscribed from Acme order updates and will get no further texts.","help_keywords":["HELP"],"help_response":"Acme order updates. Reply STOP to unsubscribe. Support: acme.com/help"},"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/TCRCampaignFields"},{"type":"object","required":["brand_id","use_case_category","subscriber_opt_in","subscriber_opt_out","subscriber_help"],"properties":{"brand_id":{"description":"The 10DLC brand this campaign runs under.","allOf":[{"$ref":"#/components/schemas/TCRBrandID"}]},"use_case_category":{"$ref":"#/components/schemas/TCRCampaignUseCase"},"sub_use_cases":{"type":"array","description":"Sub-use-cases. Required when `use_case_category` is `mixed` or `low_volume`, which must list between two and five, and must be empty for every other use case.","items":{"$ref":"#/components/schemas/TCRCampaignUseCase"}},"subscriber_opt_in":{"type":"boolean","description":"Whether recipients can opt in to the campaign. Submitted to the registry as you declare it, so it must be true for every use case except `m2m`, which has no human recipient. Carriers reject a campaign that does not support opt-in.","example":true},"subscriber_opt_out":{"type":"boolean","description":"Whether recipients can opt out of the campaign. Submitted to the registry as you declare it, and requires `opt_out_keywords` and `opt_out_confirmation_response` when true. It must be true for every use case except `m2m`, since carriers reject a campaign that does not support the standard opt-out keywords.","example":true},"subscriber_help":{"type":"boolean","description":"Whether recipients can request help from the campaign. Submitted to the registry as you declare it, and requires `help_keywords` and `help_response` when true. It must be true for every use case except `m2m`, since carriers reject a campaign that does not support the standard help command.","example":true}}}]},"TCRCampaignPrecheckRequest":{"type":"object","additionalProperties":false,"description":"The campaign details to evaluate. Every field is optional. The prediction treats missing fields as risk factors rather than errors, so an in-progress form can still be prechecked.","properties":{"brand_id":{"description":"The 10DLC brand the campaign will run under. Supply it to have the campaign checked against the brand's own details, since some registry rules depend on them. A value that is not a valid brand identifier is rejected; a valid one naming a brand this workspace cannot read is ignored instead, and the check runs without the brand-dependent rules.","allOf":[{"$ref":"#/components/schemas/TCRBrandID"}]},"use_case_category":{"$ref":"#/components/schemas/TCRCampaignUseCase"},"sub_use_cases":{"type":"array","description":"Sub-use-cases, for a `mixed` or `low_volume` campaign.","items":{"$ref":"#/components/schemas/TCRCampaignUseCase"}},"name":{"type":"string","description":"A short name for the campaign."},"use_case_description":{"type":"string","description":"What the campaign is used for."},"opt_in_description":{"type":"string","description":"How recipients opt in to the campaign and what messages they receive."},"message_samples":{"type":"array","description":"Example messages sent by the campaign.","items":{"type":"string"}},"help_response":{"type":"string","description":"The reply sent when a recipient texts a help keyword."},"opt_in_keywords":{"type":"array","description":"Keywords a recipient can text to opt in.","items":{"type":"string"}},"opt_in_confirmation_response":{"type":"string","description":"The confirmation reply sent when a recipient opts in."},"opt_out_keywords":{"type":"array","description":"Keywords a recipient can text to opt out.","items":{"type":"string"}},"opt_out_confirmation_response":{"type":"string","description":"The confirmation reply sent when a recipient opts out."},"help_keywords":{"type":"array","description":"Keywords a recipient can text to request help.","items":{"type":"string"}},"embedded_link":{"type":"boolean","description":"Whether campaign messages contain a link."},"embedded_phone":{"type":"boolean","description":"Whether campaign messages contain a phone number."},"age_gated":{"type":"boolean","description":"Whether the campaign contains age-gated content."},"direct_lending":{"type":"boolean","description":"Whether the campaign involves direct lending or a loan arrangement."},"subscriber_opt_in":{"type":"boolean","description":"Whether recipients can opt in to the campaign."},"subscriber_opt_out":{"type":"boolean","description":"Whether recipients can opt out of the campaign."},"subscriber_help":{"type":"boolean","description":"Whether recipients can request help from the campaign."}}},"TCRCampaignRequirements":{"type":"object","additionalProperties":false,"description":"What a 10DLC campaign registration needs before the campaign registry will accept it, in the order to present it, plus what you have already supplied and where each item stands. The field list depends on the use case: the umbrella use cases `mixed` and `low_volume` are the only ones that take a set of sub-use-cases, and the registry refuses them from any other.","required":["campaign_id","satisfied","needs_input","fields"],"properties":{"next":{"type":"array","readOnly":true,"description":"What to do next about this campaign, given the state it is in. Each entry names one\naction and says why it is worth taking. An empty list means there is nothing to do; the\nfield is absent on responses that do not compute next actions.\n","items":{"$ref":"#/components/schemas/NextAction"}},"campaign_id":{"readOnly":true,"description":"The campaign these answers belong to. `null` when you have not started one, in which case the field list is the blank form.","oneOf":[{"$ref":"#/components/schemas/TCRCampaignID"},{"type":"null"}]},"satisfied":{"type":"boolean","readOnly":true,"description":"Whether every required item has an answer the registry accepted. Optional items never hold it back, whatever state they are in."},"needs_input":{"type":"boolean","readOnly":true,"description":"Whether anything is waiting on you: a required item with no answer, or one that was refused. False while every required item is either settled or with the registry, which is what separates \"we need something from you\" from \"we are reviewing\". Those are two states `satisfied` reports alike, since it reports only whether everything was accepted."},"fields":{"type":"array","readOnly":true,"description":"Every item this campaign requires for the use case asked about, in the order to present them. The same shape the other registration programmes return. A field the registry refuses for this use case is not listed at all rather than listed as optional: `sub_use_cases` is accepted only for an umbrella use case.","items":{"$ref":"#/components/schemas/ComplianceRequirementField"}}}},"TCRCampaignNumberLinkStatus":{"type":"string","readOnly":true,"minLength":1,"description":"Where a number stands on its campaign.\n\n- `pending`: Recorded, waiting on the campaign's approval or the carriers' confirmation. The number cannot send to the US yet.\n- `active`: Confirmed by the carriers. The number can send to the US under this campaign.\n- `failed`: The carriers did not accept this number for the campaign. Remove it and try again, or assign it to a different campaign.\n- `unlinked`: Removed from the campaign. The number is free to assign elsewhere.\n","enum":["pending","active","failed","unlinked"]},"TCRCampaignNumberLinkID":{"type":"string","minLength":1,"pattern":"^cnl_[0-9a-hjkmnp-tv-z]{26}$","example":"cnl_01krdgeqcxet5s7t44vh8rt9mg"},"SMSSenderID":{"type":"string","minLength":1,"pattern":"^snd_[0-9a-hjkmnp-tv-z]{26}$","example":"snd_01krdgeqcxet5s7t44vh8rt9mg"},"TCRCampaignNumberLink":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","campaign_id","sender_id","phone_number","status","rejection_reasons","linked_at","unlinked_at"],"properties":{"next":{"type":"array","readOnly":true,"description":"What to do next about this number's place on its campaign, given the state it is in. Each entry names one action and\nsays why it is worth taking, so you can act on this response without working out the order\nyourself. Present on reads that compute it: an empty list means there is nothing to do, and\nthe field is absent entirely on responses that do not report next actions.\n\nThis is where one number's registration ends: either the carriers accepted it and it can send,\nor they did not and it has to be assigned again.\n","items":{"$ref":"#/components/schemas/NextAction"}},"id":{"readOnly":true,"$ref":"#/components/schemas/TCRCampaignNumberLinkID"},"campaign_id":{"readOnly":true,"description":"The 10DLC campaign this number is registered under.","allOf":[{"$ref":"#/components/schemas/TCRCampaignID"}]},"sender_id":{"readOnly":true,"description":"The sender the number is held as.","allOf":[{"$ref":"#/components/schemas/SMSSenderID"}]},"phone_number":{"type":"string","readOnly":true,"minLength":1,"description":"The number in E.164 format.","example":"+15551234567"},"status":{"$ref":"#/components/schemas/TCRCampaignNumberLinkStatus"},"rejection_reasons":{"type":"array","readOnly":true,"description":"Why the carriers did not accept this number for the campaign, one entry per problem. Empty unless the status is `failed`.","items":{"$ref":"#/components/schemas/TCRSubmissionRejectionReason"}},"linked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the carriers confirmed the number for this campaign. Null until then."},"unlinked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the number was removed from this campaign. Null while it is still linked."}}}]},"TCRCampaignNumberLinkList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The numbers registered under the campaign and where each stands with the carriers.","items":{"$ref":"#/components/schemas/TCRCampaignNumberLink"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"TCRCampaignNumberLinkCreate":{"type":"object","additionalProperties":false,"required":["sender_ids"],"properties":{"sender_ids":{"type":"array","minItems":1,"maxItems":49,"uniqueItems":true,"description":"The senders to register under this campaign. Each must be a US long code the workspace owns. Toll-free numbers and alphanumeric senders use their own registration.","items":{"$ref":"#/components/schemas/SMSSenderID"}}}},"TCRCampaignNumberLinkAssignResult":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"One entry per number in the request, in submission order.","items":{"$ref":"#/components/schemas/TCRCampaignNumberLink"}}}},"TCRCampaignSubmissionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The campaign's submission attempts, most recent first.","items":{"$ref":"#/components/schemas/TCRCampaignSubmission"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"TCRCampaignSubmissionCreate":{"description":"The details to register for a 10DLC campaign, submitted as a new attempt. They\nreplace what the previous attempt submitted. Five fields are fixed once a campaign\nexists and are not accepted here at all, carrying over from the previous attempt\nunchanged: `use_case_category`, `sub_use_cases`, and the `subscriber_opt_in`,\n`subscriber_opt_out`, and `subscriber_help` flags. Create a new campaign to run a\ndifferent use case. The keywords and replies those flags require are not fixed:\n`opt_out_keywords`, `opt_out_confirmation_response`, `help_keywords`, and\n`help_response` must be carried for whichever flags the campaign declared.\n","example":{"name":"Order updates","use_case_description":"Sends order confirmation and shipping status updates to customers who opted in at checkout.","opt_in_description":"Customers opt in by checking a box at checkout on acme.com. They then receive order and shipping status texts.","message_samples":["Your order #1234 has shipped and will arrive Thursday.","Your order #1234 is out for delivery and arrives today."],"opt_out_keywords":["STOP"],"opt_out_confirmation_response":"You are unsubscribed from Acme order updates and will get no further texts.","help_keywords":["HELP"],"help_response":"Acme order updates. Reply STOP to unsubscribe. Support: acme.com/help"},"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/TCRCampaignFields"}]},"ComplianceIdentityAnswer":{"type":"object","additionalProperties":false,"required":["value","submitted_at"],"description":"One business detail this party last submitted, when it was filed, and where review has got to on it.","properties":{"value":{"type":"string","minLength":1,"readOnly":true,"description":"The detail as last submitted. Every value travels as a string whatever the requirement's data type, so a number reads back as `\"1500\"`."},"submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this party last filed the detail. Filing it is what records it here, so it is never a statement that a reviewer agreed with the value. Filing the same value again can carry a previous approval of it forward instead of putting it in front of a reviewer. `null` for a detail recorded before the container began tracking submissions."}}},"ComplianceIdentity":{"type":"object","additionalProperties":false,"description":"A party you register on behalf of: your own business, or one of your clients. Reusing an identity across registrations is what stops you retyping the same business details for every country, and lets a fact reviewed once stand for the registrations that follow.","required":["id","name","answers","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/ComplianceIdentityID"},"name":{"type":"string","minLength":1,"description":"What you call this party. Yours to choose; it is never sent to a carrier.","example":"Goldcrest Communications SAS"},"answers":{"type":"object","readOnly":true,"additionalProperties":{"$ref":"#/components/schemas/ComplianceIdentityAnswer"},"description":"The business details this party has supplied, keyed by requirement. Filled in for you: a detail lands here the moment you file it on one of your registrations, so the next one prefills it rather than asking again, and each carries where review has got to on it. Only requirements a country marks as being about the party appear; anything about a specific sender is asked fresh each time."},"created_at":{"type":"string","minLength":1,"format":"date-time"},"updated_at":{"type":"string","minLength":1,"format":"date-time"}}},"ComplianceIdentityList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ComplianceIdentity"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"ComplianceIdentityCreate":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"What you call this party. Yours to choose; it is never sent to a carrier.","example":"Goldcrest Communications SAS"}}},"ValidationError":{"description":"Field validation error. Uses the standard error envelope with `type` set to `validation_error` and `details` always present.","allOf":[{"$ref":"#/components/schemas/Error"}]},"ComplianceIdentityUpdate":{"type":"object","additionalProperties":false,"required":["name"],"description":"Renames the party. Its business details are not editable here; they are filled in by the reviews your registrations pass.","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"What you call this party. Yours to choose; it is never sent to a carrier.","example":"Goldcrest Communications SAS"}}},"AttachmentList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AttachmentUpload":{"description":"The file to upload, sent as a `multipart/form-data` body.","type":"object","additionalProperties":false,"required":["file"],"properties":{"file":{"type":"string","format":"binary","minLength":1,"description":"The file to attach. PNG, JPEG, or PDF, up to 10 MB. The file's contents determine its type; a declared content type is ignored."},"description":{"type":"string","maxLength":255,"description":"A short note describing what the file shows."}}},"AssetKind":{"type":"string","minLength":1,"description":"The category a media-library asset's type falls into. Use it to group or filter a\nmixed library. It also helps you present an asset your interface cannot preview.\n\n- `image`: covers PNG, JPEG, and GIF.\n- `video`: covers MP4, WebM, and QuickTime.\n- `audio`: covers MP3, M4A, Ogg, and WAV.\n- `document`: covers PDF.\n","enum":["image","video","audio","document"],"example":"image"},"AssetFolderID":{"type":"string","minLength":1,"pattern":"^asf_[0-9a-hjkmnp-tv-z]{26}$","example":"asf_01krdgeqcxet5s7t44vh8rt9mg"},"AssetID":{"type":"string","minLength":1,"pattern":"^ast_[0-9a-hjkmnp-tv-z]{26}$","example":"ast_01krdgeqcxet5s7t44vh8rt9mg"},"Asset":{"description":"A file in the workspace's media library, hosted at a permanent public URL. Embed or link `url` anywhere hosted media is needed, including in email that has already been sent: an asset's bytes and URL never change.","allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","additionalProperties":false,"required":["id","url","thumbnail_url","name","content_type","kind","size_bytes","width","height","alt_text","folder_id","deleted_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AssetID"},"url":{"type":"string","format":"uri","minLength":1,"readOnly":true,"description":"The asset's permanent public URL. Safe to embed or link in sent messages; for a document the usable form is a link."},"thumbnail_url":{"type":["string","null"],"format":"uri","minLength":1,"readOnly":true,"description":"A small preview of the asset, for rendering a picker or a grid. Intended for your own interfaces rather than for embedding in messages. Null for every kind but `image`: generating a preview of a video, an audio file, or a document would mean rendering its contents, which the library does not do. Show an icon for the `kind` instead."},"name":{"type":"string","minLength":1,"description":"The asset's display name. Defaults to the uploaded file's name."},"content_type":{"type":"string","minLength":1,"readOnly":true,"description":"The stored file's media type, determined from its contents."},"kind":{"readOnly":true,"$ref":"#/components/schemas/AssetKind"},"size_bytes":{"type":"integer","format":"int64","readOnly":true,"description":"The stored file's size in bytes. Images are re-encoded on upload, so for those this can differ from the size of the file you sent. Every other kind is stored byte-for-byte."},"width":{"type":["integer","null"],"readOnly":true,"description":"The stored image's width in pixels. Null for every kind but `image`."},"height":{"type":["integer","null"],"readOnly":true,"description":"The stored image's height in pixels. Null for every kind but `image`."},"alt_text":{"type":["string","null"],"maxLength":1000,"description":"Default alternative text describing the asset, carried along whenever it is placed. Most mail clients block images by default, so for an image this is often the only thing a recipient reads."},"folder_id":{"oneOf":[{"$ref":"#/components/schemas/AssetFolderID"},{"type":"null"}],"description":"The folder the asset is filed in, or null when it sits at the top level of the library. Filing an asset never changes its URL."},"deleted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the asset was removed from the library, or null if it is still listed. A removed asset keeps serving at its original URL."}}}]},"AssetList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Asset"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AssetUpload":{"description":"The file to upload, sent as a `multipart/form-data` body.","type":"object","additionalProperties":false,"required":["file"],"properties":{"file":{"type":"string","format":"binary","minLength":1,"description":"The file to upload. The file's contents determine its type; a declared content\ntype is ignored.\n\nImages: PNG, JPEG, GIF, or WebP, up to 5 MB. WebP is converted on upload, and\nevery image is re-encoded.\n\nVideo (MP4, WebM, QuickTime), audio (MP3, M4A, Ogg, WAV), and PDF: up to 25 MB,\nstored byte-for-byte.\n"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"A display name for the asset. Defaults to the uploaded file's name."},"alt_text":{"type":"string","maxLength":1000,"description":"Default alternative text describing the asset, carried along whenever it is placed."},"folder_id":{"$ref":"#/components/schemas/AssetFolderID","description":"The folder to file the asset in. Omit it to place the asset at the top level of the library. Ignored when the upload matches an existing asset, which is returned where it already sits."}}},"AssetUpdate":{"description":"Fields to change on an asset. Omit a field to leave it as it is. The asset's bytes and URL are fixed and cannot be changed.","type":"object","additionalProperties":false,"minProperties":1,"properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"The asset's display name."},"alt_text":{"type":["string","null"],"maxLength":1000,"description":"Default alternative text describing the image. Send null to clear it."},"folder_id":{"oneOf":[{"$ref":"#/components/schemas/AssetFolderID"},{"type":"null"}],"description":"The folder to file the asset in. Send null to move it to the top level of the library. Moving an asset never changes its URL."}}},"AssetReferenceUsage":{"description":"Which copy of a resource uses an asset.\n\n- `draft`: it is not reaching anyone yet.\n- `published`: the change has to be published before recipients see it.\n- `both`: the same version is in use in both copies.\n","type":"string","minLength":1,"enum":["draft","published","both"]},"AssetReference":{"description":"A resource that uses an asset, so you can find everything to update before replacing an image.","type":"object","additionalProperties":false,"required":["resource_type","resource_id","resource_name","usage"],"properties":{"resource_type":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["email_template"],"description":"What kind of resource this is. New kinds appear as other products start using the media library, so treat an unfamiliar value as a resource this client does not know how to link to rather than as an error."},"resource_id":{"type":"string","minLength":1,"readOnly":true,"description":"The resource's own identifier, carrying that resource's prefix (`emt_` for an email template), for linking straight to it.","example":"emt_01j9zyq8k7f0ra2m6c4x5w8pv3"},"resource_name":{"type":"string","minLength":1,"readOnly":true,"description":"The resource's current display name, read when you list references."},"usage":{"readOnly":true,"$ref":"#/components/schemas/AssetReferenceUsage"}}},"AssetReferenceList":{"type":"object","additionalProperties":false,"required":["data","truncated"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AssetReference"}},"truncated":{"type":"boolean","readOnly":true,"description":"True when more resources use this asset than one response can carry, so the list is a partial answer. Replace the asset's usages you can see, then list again to find the rest."}}},"AssetFolder":{"description":"A folder in the workspace's media library. Folders organize assets for the people browsing them and never appear in an asset's URL, so moving an asset between folders leaves every message that already embeds it untouched.","allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","additionalProperties":false,"required":["id","name","parent_id","path"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AssetFolderID"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The folder's display name. Unique among folders sharing the same parent, compared without regard to case."},"parent_id":{"oneOf":[{"$ref":"#/components/schemas/AssetFolderID"},{"type":"null"}],"description":"The folder this one sits in, or null when it sits at the top level of the library."},"path":{"type":"string","minLength":1,"readOnly":true,"description":"The folder's location written out from the top level, such as `/Marketing/Logos`. Provided so a caller can display a folder without walking its parents."}}}]},"AssetFolderList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AssetFolder"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AssetFolderCreate":{"description":"The folder to create.","type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"The folder's display name. Must be unique among folders sharing the same parent, compared without regard to case. Cannot contain `/`."},"parent_id":{"oneOf":[{"$ref":"#/components/schemas/AssetFolderID"},{"type":"null"}],"description":"The folder to create this one in. Omit it, or send null, to create the folder at the top level of the library."}}},"AssetFolderUpdate":{"description":"Fields to change on a folder. Omit a field to leave it as it is. Renaming a folder or moving it changes nothing about the assets inside it, including their URLs.","type":"object","additionalProperties":false,"minProperties":1,"properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"The folder's display name. Must be unique among folders sharing the same parent, compared without regard to case. Cannot contain `/`."},"parent_id":{"oneOf":[{"$ref":"#/components/schemas/AssetFolderID"},{"type":"null"}],"description":"The folder to move this one into. Send null to move it to the top level of the library. A folder cannot be moved into itself or into one of its own subfolders."}}},"ComplianceSubjectKind":{"type":"string","minLength":1,"enum":["sms_sender","number","identity"],"description":"What is being registered.\n\n- `sms_sender`: a sender registered to send to one country.\n- `number`: a phone number you hold that needs ownership paperwork before it\n  can carry traffic.\n- `identity`: a business you operate as, registered once and reused by\n  later registrations that name it.\n"},"ComplianceSubjectID":{"type":"string","minLength":1,"pattern":"^(scr|nda|cid)_[0-9a-hjkmnp-tv-z]{26}$","example":"scr_01krdgeqcxet5s7t44vh8rt9mg","description":"What is being registered. For `sms_sender` this is the `scr_` id returned when you register a sender for a country. For `number` it is the `nda_` id of a dedicated number you hold, as returned by `GET /v1/numbers`. A shared short code (`nal_`) is not accepted: it is shared, so its ownership paperwork is not yours to file. For `identity` it is the `cid_` id of the party being registered, picked from the parties your workspace holds, as returned by `GET /v1/compliance/identities`.\n"},"ComplianceProgram":{"type":"string","minLength":1,"x-extensible-enum":["alpha","numeric","mobile","local","national","short_code","short_code_fteu","toll_free","business"],"description":"The registration program a country runs. Available programs depend on the registered resource. An SMS sender registers under an authority regime (`alpha` or `numeric`). A phone number proves ownership under its number type (`mobile`, `local`, `national`, `short_code`, `short_code_fteu`, or `toll_free`), matching the values of `NumberType`. An identity registers your business itself under `business`, internationally: its coordinate's country is always `ZZ`. A value is therefore only meaningful alongside the subject it describes. More are added as coverage grows, so treat this as an open set rather than a fixed list.\n"},"ComplianceSubmissionID":{"type":"string","minLength":1,"pattern":"^csb_[0-9a-hjkmnp-tv-z]{26}$","example":"csb_01krdgeqcxet5s7t44vh8rt9mg"},"ComplianceObligationReason":{"type":"string","minLength":1,"x-extensible-enum":["policy_change","evidence_expiry","verdict_withdrawn","verifier_rejected"],"description":"Why a registration that is already authorised has been asked for something\nmore.\n\n- `policy_change` means the country published a newer version of its form and\n  your answers were given under an older one.\n- `evidence_expiry` means an answer we accepted has gone stale and needs\n  supplying again.\n- `verdict_withdrawn` means this registration was relying on an approval earned\n  on another registration, and that approval has since been taken back, so the\n  answer needs checking again here. Your answer has not changed and has not\n  expired.\n- `verifier_rejected` means an outside party we filed your details with reviewed\n  them and refused them. Your answer has not changed and has not expired, and\n  we did not overrule it.\n\nTreat this as an open set rather than a fixed list. More reasons are added as\nmore of a registration is kept under review, so a value you do not recognise is\none this server added rather than an error.\n"},"ComplianceObligationState":{"type":"string","minLength":1,"x-extensible-enum":["open","satisfied","lapsed","extended"],"description":"Where an obligation stands.\n\n- `open`: the due date has not passed and nothing has been supplied.\n- `satisfied`: you supplied what was asked.\n- `lapsed`: the date passed unmet and the registration is no longer\n  authorised.\n- `extended`: someone here gave you more time, and `due_at` reports the later\n  date.\n"},"ComplianceObligation":{"type":"object","additionalProperties":false,"required":["reason","state","due_at","target_version"],"properties":{"reason":{"allOf":[{"$ref":"#/components/schemas/ComplianceObligationReason"}],"readOnly":true},"state":{"allOf":[{"$ref":"#/components/schemas/ComplianceObligationState"}],"readOnly":true},"due_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"The date to supply this by. Where more time has been given, this is the later date rather than the one first asked for.\n"},"target_version":{"type":"integer","minimum":1,"readOnly":true,"description":"The version of the country's form your answers have to reach. For an expiry it is the version you already answered, filled in again.\n"}}},"ComplianceGeneralReasons":{"type":"array","readOnly":true,"description":"Feedback about the submission as a whole from an external verifier. It can remain visible while corrections are under review. Empty when no unresolved verifier feedback applies. On requirements responses, read `needs_input` to determine whether action is needed and `editable_for_verifier_repair` on each field to determine whether an approved or not-applicable answer can be replaced.\n","items":{"type":"string"}},"ComplianceRequirement":{"type":"object","additionalProperties":false,"required":["subject_kind","subject_id","program","country_code","satisfied","needs_input","review_pending","submission_id","obligation","fields","general_reasons","reason"],"properties":{"next":{"type":"array","readOnly":true,"description":"What to do next about this country's requirements, given the state it is in. Each entry names one action and says\nwhy it is worth taking, so you can act on this response without working out the order\nyourself. Present on reads that compute it: an empty list means there is nothing to do,\nand the field is absent entirely on responses that do not report next actions.\n\nWhether anything is asked of you is `needs_input`, and this says what to do about it: send your\nanswers where none have been filed, or replace the ones that were refused. Which items those\nare, and why each was refused, is on the item in `fields`. For feedback about the\nwhole submission, read `reason` and `general_reasons` and update the details that need correction.\n","items":{"$ref":"#/components/schemas/NextAction"}},"subject_kind":{"allOf":[{"$ref":"#/components/schemas/ComplianceSubjectKind"}],"readOnly":true},"subject_id":{"readOnly":true,"description":"The registration these requirements are for, the same one you asked about. `null` when you asked without one, which is how a form read on its own says it is answering for nothing yet.\n","oneOf":[{"$ref":"#/components/schemas/ComplianceSubjectID"},{"type":"null"}]},"program":{"readOnly":true,"description":"The registration program these requirements belong to. `null` when the country runs no registration program at all, in which case nothing is required and the field list is empty.\n","oneOf":[{"$ref":"#/components/schemas/ComplianceProgram"},{"type":"null"}]},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"readOnly":true},"satisfied":{"type":"boolean","readOnly":true,"description":"Whether this country's requirements are met. `true` when no requirements apply, or once the submission is approved and every required requirement is approved or marked not applicable. This can remain true when an external verifier requests a correction. Once you submit that correction, it must pass review before this becomes true again, including any optional answers you change.\n"},"needs_input":{"type":"boolean","readOnly":true,"description":"Whether anything is waiting on you. True when a required requirement has no answer, its answer was refused or has gone stale, or the registration needs correction as described by `reason`. False does not imply that review is pending: a closed verifier correction can leave the requirements unsatisfied with no editable answers. Read `review_pending` to determine whether an answer is awaiting review. Read this independently of `satisfied`, which reports whether your paperwork is accepted. An unresolved verifier correction also sets it while the matching grant is live and the correction deadline has not passed, until you supply updated details. At the deadline or after grant withdrawal, an unresolved rejection alone does not set it because accepted answers can no longer be replaced. A corrected optional answer that is rejected or expires still needs your input through ordinary resupply, including after that deadline.\n"},"review_pending":{"type":"boolean","readOnly":true,"description":"Whether any submitted answer is awaiting or undergoing review. False after all answers have received verdicts. A closed verifier correction can remain unsatisfied with no review pending and no editable answers.\n"},"reason":{"type":["string","null"],"readOnly":true,"description":"What to correct about the registration as a whole, or null when no registration-wide correction is requested. Reasons about individual answers are on the entries in fields.\n"},"submission_id":{"readOnly":true,"description":"The submission carrying your answers, once you have made one. `null` before that.","oneOf":[{"$ref":"#/components/schemas/ComplianceSubmissionID"},{"type":"null"}]},"obligation":{"readOnly":true,"description":"What this registration has to do to keep sending, and by when. Null when nothing is outstanding. An obligation does not stop you sending: the registration stays authorised until the date passes, and supplying what is asked for before then closes it with no interruption.\n","oneOf":[{"$ref":"#/components/schemas/ComplianceObligation"},{"type":"null"}]},"general_reasons":{"$ref":"#/components/schemas/ComplianceGeneralReasons","readOnly":true},"fields":{"type":"array","readOnly":true,"description":"Every requirement for this country, in the order to present them.","items":{"$ref":"#/components/schemas/ComplianceRequirementField"}},"closed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the submission for these requirements was permanently declined. Null while open or not yet submitted."}}},"ComplianceSubmissionIdentity":{"type":"object","additionalProperties":false,"description":"Registers this on behalf of a party you have not stored yet, creating it as part\nof the submission so you do not need a separate call. Exclusive with\n`identity_id`.\n\nEach time you send this you get a new party. Business details approved for one\nare reused only by registrations naming the same party, so send this once and\nuse the `identity_id` from the response for the countries that follow.\n","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"What to call the party. Optional: left out, it takes the legal entity name you supplied. If this country's form does not ask for one, send a name here.","example":"Goldcrest Communications SAS"}}},"ComplianceAnswer":{"type":"object","additionalProperties":false,"example":{"key":"business_registration_number","value":"90012768"},"required":["key"],"description":"One answer. Which field carries it follows from the requirement, as the\nrequirements read reports it:\n\n- answered once, inline: `value`\n- answered once, with a document: `attachment_id`\n- `max_items` set, inline: `values`\n- `max_items` set, with documents: `attachment_ids`\n\nSend exactly one of the four. Order in a list is preserved and is part of the\nanswer.\n","properties":{"key":{"type":"string","minLength":1,"description":"The requirement this answers, as given by its key in the requirements read.","example":"business_registration_number"},"value":{"type":"string","minLength":1,"description":"The answer, for a requirement answered inline. Send every answer as a string whatever its data type, so a number goes as `\"1500\"` and a yes-or-no as `\"true\"`.\n","example":"90012768"},"attachment_id":{"allOf":[{"$ref":"#/components/schemas/AttachmentID"}],"description":"The uploaded document, for a requirement that takes one. Upload the file as an attachment first, then send the id it returns.\n"},"values":{"type":"array","minItems":1,"maxItems":50,"description":"The answers, for a requirement whose `max_items` is set and which is answered inline. Send each as a string whatever its data type, and send them in the order you mean them to be read. At most `max_items` of them.\n","items":{"type":"string","minLength":1}},"attachment_ids":{"type":"array","minItems":1,"maxItems":50,"uniqueItems":true,"description":"The uploaded documents, for a requirement whose `max_items` is set and which takes documents. Upload each file first and send the ids it returns, in order. At most `max_items` of them.\n","items":{"$ref":"#/components/schemas/AttachmentID"}}},"oneOf":[{"required":["value"]},{"required":["attachment_id"]},{"required":["values"]},{"required":["attachment_ids"]}]},"ComplianceSubmissionCreate":{"type":"object","additionalProperties":false,"required":["subject_kind","subject_id","program","country_code","answers"],"properties":{"subject_kind":{"$ref":"#/components/schemas/ComplianceSubjectKind"},"subject_id":{"$ref":"#/components/schemas/ComplianceSubjectID"},"program":{"$ref":"#/components/schemas/ComplianceProgram"},"country_code":{"$ref":"#/components/schemas/CountryCode"},"identity_id":{"allOf":[{"$ref":"#/components/schemas/ComplianceIdentityID"}],"description":"An existing party this registration is for. It fills in the business details you left out from what that party already holds, and an answer approved here is written back onto it for next time. Send this or `identity`, never both. An id belonging to another organization is rejected; nothing is created here. When `subject_kind` is `identity`, send neither: `subject_id` already names the party."},"identity":{"$ref":"#/components/schemas/ComplianceSubmissionIdentity"},"answers":{"type":"array","minItems":1,"description":"Every answer at once. Because submissions have no multi-call draft, send a complete set. Anything missing or malformed comes back named. A requirement the named party already answers may be left out; one you send anyway wins over what the party holds.\n","items":{"$ref":"#/components/schemas/ComplianceAnswer"}}}},"ComplianceItemID":{"type":"string","minLength":1,"pattern":"^cit_[0-9a-hjkmnp-tv-z]{26}$","example":"cit_01krdgeqcxet5s7t44vh8rt9mg"},"ComplianceSubmissionItem":{"type":"object","additionalProperties":false,"required":["id","key","state","value","attachment_id","values","attachment_ids","reason","decided_at","expires_at","inherited"],"properties":{"inherited":{"type":"boolean","readOnly":true,"description":"`true` when this answer was accepted without a fresh review, because the same answer to the same requirement had already been approved for this party in this country. It carries the original verdict, including when that verdict expires.\n"},"id":{"allOf":[{"$ref":"#/components/schemas/ComplianceItemID"}],"readOnly":true},"key":{"type":"string","minLength":1,"readOnly":true,"description":"The requirement this answers."},"state":{"allOf":[{"$ref":"#/components/schemas/ComplianceItemState"}],"readOnly":true},"value":{"type":["string","null"],"readOnly":true,"description":"Your answer, for a requirement answered inline. Every answer travels as a string whatever its data type, so a number reads back as `\"1500\"` and a yes-or-no as `\"true\"`. Use the data type to parse it. `null` for a requirement that takes a document.\n"},"attachment_id":{"readOnly":true,"description":"The document you supplied, for a requirement that takes one. `null` otherwise.","oneOf":[{"$ref":"#/components/schemas/AttachmentID"},{"type":"null"}]},"values":{"type":["array","null"],"readOnly":true,"description":"Your answers, for a requirement that takes several and is answered inline. `null` for one answered once. Order is as you sent it.\n","items":{"type":"string","minLength":1}},"attachment_ids":{"type":["array","null"],"readOnly":true,"description":"The documents you supplied, for a requirement that takes several. `null` otherwise.\n","items":{"$ref":"#/components/schemas/AttachmentID"}},"reason":{"type":["string","null"],"readOnly":true,"description":"Why this was rejected, when it was. `null` otherwise."},"decided_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this was decided. `null` while no decision has been made."},"expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this answer goes stale and must be supplied again. `null` when it does not expire."}}},"ComplianceSubmission":{"type":"object","additionalProperties":false,"required":["id","subject_kind","subject_id","program","country_code","identity_id","satisfied","items","created_at","updated_at"],"properties":{"next":{"type":"array","readOnly":true,"description":"What to do next about this submission, given the state it is in. Each entry names one action and says\nwhy it is worth taking, so you can act on this response without working out the order\nyourself. Present on reads that compute it: an empty list means there is nothing to do,\nand the field is absent entirely on responses that do not report next actions.\n\nA submission reports the answers you filed; whether the country's requirements are met is\nreported by the requirements read. So this points there rather than repeating a verdict it\ndoes not own.\n","items":{"$ref":"#/components/schemas/NextAction"}},"id":{"allOf":[{"$ref":"#/components/schemas/ComplianceSubmissionID"}],"readOnly":true},"subject_kind":{"allOf":[{"$ref":"#/components/schemas/ComplianceSubjectKind"}],"readOnly":true},"subject_id":{"allOf":[{"$ref":"#/components/schemas/ComplianceSubjectID"}],"readOnly":true},"program":{"allOf":[{"$ref":"#/components/schemas/ComplianceProgram"}],"readOnly":true},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"readOnly":true},"identity_id":{"oneOf":[{"$ref":"#/components/schemas/ComplianceIdentityID"},{"type":"null"}],"readOnly":true,"description":"The party this registration was filed for, or `null` if none was named."},"satisfied":{"type":"boolean","readOnly":true,"description":"Whether this country's requirements are met. `true` once the submission is approved and every required requirement is approved or marked not applicable. This can remain true when an external verifier requests a correction. Once you submit that correction, it must pass review before this becomes true again, including any optional answers you change.\n"},"items":{"type":"array","readOnly":true,"description":"Every answer and where it stands. Each is decided on its own.","items":{"$ref":"#/components/schemas/ComplianceSubmissionItem"}},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When this was submitted."},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When it last changed."},"closed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this submission was permanently declined. Null while it remains open."},"closure_reason":{"type":["string","null"],"readOnly":true,"description":"The decline reason for a closed submission, or null while open."}}},"ComplianceSubmissionResupply":{"type":"object","additionalProperties":false,"required":["answers"],"description":"Replacement answers for an existing submission. Rejected, expired, or missing answers can\nbe supplied again; accepted answers normally cannot be changed.\n\nWhen the requirements response marks an accepted answer as `editable_for_verifier_repair`,\nyou can replace it during that open correction and before its deadline. Accepted-answer\nrepairs must change content; unchanged accepted entries are ignored. Rejected, expired,\nor missing answers follow the ordinary resupply rules, including same-value retries\nafter a grant is withdrawn. Supplied replacements are reviewed again.\n","properties":{"answers":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/ComplianceAnswer"}}}},"TCRBrandSubmissionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The brand's submission attempts, most recent first.","items":{"$ref":"#/components/schemas/TCRBrandSubmission"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"TCRBrandVettingID":{"type":"string","minLength":1,"pattern":"^tcv_[0-9a-hjkmnp-tv-z]{26}$","example":"tcv_01krdgeqcxet5s7t44vh8rt9mg"},"TCRVettingClass":{"type":"string","minLength":1,"enum":["auth_plus","standard","enhanced","political"],"description":"Which external vetting product to order for a 10DLC brand. The `auth_plus` class is required for `public_profit` brands before creating any campaign under them, and has to be ordered once the brand is approved. The `standard` and `enhanced` classes return a score that can improve carrier throughput. The `political` class is required for a `non_profit` brand that wants to run political campaigns without a recognized tax-exempt status.\n"},"TCRVettingStatus":{"type":"string","minLength":1,"enum":["pending","active","unscored","failed","expired"],"description":"The status of one vetting attempt. The `pending` status means the provider is still reviewing. For `auth_plus`, the business contact has not yet confirmed the verification email sent to them. The `active` status is a completed vet whose result applies. The `unscored` status means the provider could not reach a result and is reviewing manually. The `failed` and `expired` statuses both need a new attempt.\n"},"TCRBrandVetting":{"description":"One attempt to order external vetting for a 10DLC brand.","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","brand_id","vetting_class","status","reasons"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/TCRBrandVettingID"},"brand_id":{"readOnly":true,"$ref":"#/components/schemas/TCRBrandID"},"vetting_class":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TCRVettingClass"}]},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TCRVettingStatus"}]},"score":{"type":["integer","null"],"readOnly":true,"description":"The 0-100 score the provider returned, when one was given. Null while pending, or if the provider reported none."},"reasons":{"type":"array","readOnly":true,"description":"Feedback the provider returned about this attempt, one entry per reason. Empty when none was given.","items":{"type":"string","minLength":1}},"vetted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this attempt reached a final result (active, failed, or expired). Null while pending."}}}]},"TCRBrandVettingList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The brand's external vetting attempts, across all vetting classes, most recent first.","items":{"$ref":"#/components/schemas/TCRBrandVetting"}}}},"TCRBrandVettingRequest":{"type":"object","additionalProperties":false,"description":"Order external vetting for a 10DLC brand.","required":["vetting_class"],"properties":{"vetting_class":{"description":"Which vetting product to order.","allOf":[{"$ref":"#/components/schemas/TCRVettingClass"}]}}},"StatsComparePeriod":{"type":"string","description":"Set to `previous_period` to also return the same figures for the immediately preceding window of equal length, plus the change between the two, so you can show \"+X% vs last period\" without a second request.\n","enum":["previous_period"]},"SMSStatsSummaryPeriod":{"type":"object","additionalProperties":false,"description":"The window the server actually computed against. The summary serves two window grains: calendar days (bounds are YYYY-MM-DD) and hours (bounds are RFC 3339 instants on the hour). The grain of `from` and `to` mirrors the grain of the request's bounds.\n","required":["from","to"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary.","example":"2026-05-25"},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable.\n","example":"2026-05-25T14:03:10Z"}}},"SMSDeliveryStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"SMS lifecycle counts and rates for a whole period or breakdown. Counts use the message send time, so a later delivery stays attributed to the bucket in which the message was sent. Rates are null when their denominator is zero.\n","required":["accepted","sent","delivered","undelivered","failed","rejected","expired","delivery_rate","failure_rate"],"properties":{"accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages accepted for sending after admission checks. This is the denominator for `delivery_rate` and `failure_rate`.","example":14820},"sent":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages handed off to the carrier for delivery.","example":14810},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages the carrier confirmed as delivered to the handset.","example":14720},"undelivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages the carrier reported as not delivered.","example":60},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages that failed during sending.","example":25},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages rejected before any send attempt, for example by sending policy or a message-generation failure.","example":10},"expired":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages that could not be delivered within their validity window and expired.","example":5},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of accepted messages that were delivered, computed as `delivered / accepted`. Null when no messages were accepted in scope.\n","example":0.9932},"failure_rate":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Share of accepted messages that ultimately failed, computed as `(undelivered + failed + expired) / accepted`. Null when no messages were accepted in scope.\n","example":0.0061}}},"SMSLatencyQuantiles":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family. All three are null when no qualifying event contributed a measurement.\n","required":["p50_ms","p95_ms","p99_ms"],"properties":{"p50_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement.","example":420},"p95_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.","example":1820},"p99_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.","example":4920}}},"SMSLatencyStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Latency percentiles in milliseconds for the requested scope:\n\n- `processing`: From acceptance to carrier handoff.\n- `delivery`: From carrier handoff to delivery confirmation.\n- `total`: From acceptance to delivery confirmation.\n\nEach family is omitted when no qualifying event contributes a measurement.\nIndividual percentiles can also be null.\n","properties":{"processing":{"$ref":"#/components/schemas/SMSLatencyQuantiles"},"delivery":{"$ref":"#/components/schemas/SMSLatencyQuantiles"},"total":{"$ref":"#/components/schemas/SMSLatencyQuantiles"}}},"SMSStatsComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Changes from the previous period. A `*_pct_change` value is the signed relative change `(current - previous) / previous` and is null when the previous count is zero. A `*_rate_pp` value is the signed difference between rate fractions and is null when either rate is undefined.\n","required":["accepted_pct_change","sent_pct_change","delivered_pct_change","undelivered_pct_change","failed_pct_change","rejected_pct_change","expired_pct_change","delivery_rate_pp","failure_rate_pp"],"properties":{"accepted_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in accepted messages (`delivery.accepted`) versus the previous period, as a signed fraction. Null when the previous period accepted none.","example":0.508},"sent_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in sent messages (`delivery.sent`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0.121},"delivered_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in delivered messages (`delivery.delivered`) versus the previous period, as a signed fraction. Null when the previous period delivered none.","example":0.122},"undelivered_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in undelivered messages (`delivery.undelivered`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":-0.031},"failed_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in failed messages (`delivery.failed`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":-0.018},"rejected_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in rejected messages (`delivery.rejected`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0},"expired_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in expired messages (`delivery.expired`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0.04},"delivery_rate_pp":{"type":["number","null"],"minimum":-1,"maximum":1,"readOnly":true,"description":"Signed difference between this period's and the previous period's delivery rate, both fractions in [0,1] (multiply by 100 for percentage points). Null when either period's delivery rate is undefined.","example":0.004},"failure_rate_pp":{"type":["number","null"],"readOnly":true,"description":"Signed difference between the current and previous failure-rate fractions. Multiply by 100 for percentage points. The value can fall outside `[-1, 1]` because a message can contribute to more than one failure outcome and high-volume counts are approximate. Null when either rate is undefined.\n","example":-0.0008}}},"SMSStatsComparison":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The same statistics for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request.\n","required":["period","delivery","latency","delta"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"Equal-length window ending immediately before the requested start."},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSDeliveryStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSLatencyStats"}]},"delta":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSStatsComparisonDelta"}]}}},"SMSStatsSummary":{"type":"object","additionalProperties":false,"description":"Single-row aggregate across the full requested period, covering SMS lifecycle counts plus the derived delivery and failure rates, and latency percentiles. Use this endpoint for KPI tiles and reporting; the daily and hourly endpoints carry the same counts per bucket.\n\nEvery count is a sum of per-bucket counts across the window. Latency percentiles are computed across the whole period rather than summed per bucket. Rates are null when their denominator is zero.\n","required":["period","delivery","latency"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"The window the response covers (echoed back from the request, day or hour grain), plus `data_as_of`, the freshness boundary the data is current to."},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSDeliveryStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSLatencyStats"}]},"comparison":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSStatsComparison"}]}}},"StatsGrain":{"type":"string","minLength":1,"enum":["day","hour"],"readOnly":true,"description":"The bucket grain of the series, either `day` or `hour`.","example":"day"},"SMSStatsSeriesPeriod":{"type":"object","additionalProperties":false,"description":"The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to.\n","required":["from","to","grain"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window. A calendar day (YYYY-MM-DD) on the day grain, an RFC 3339 instant rounded to the hour on the hour grain.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window. A calendar day (YYYY-MM-DD) on the day grain, an RFC 3339 instant rounded to the hour on the hour grain.","example":"2026-05-31"},"grain":{"$ref":"#/components/schemas/StatsGrain","readOnly":true},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable.\n","example":"2026-05-25T14:03:10Z"}}},"SMSDeliveryCounts":{"type":"object","additionalProperties":false,"readOnly":true,"description":"SMS lifecycle counts for a time bucket. Counts use the message send time, so a message accepted on Monday and delivered on Tuesday counts in Monday's bucket. Rates are available only for whole periods and breakdowns.\n","required":["accepted","sent","delivered","undelivered","failed","rejected","expired"],"properties":{"accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages accepted for sending after admission checks.","example":14820},"sent":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages handed off to the carrier for delivery.","example":14810},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages the carrier confirmed as delivered to the handset.","example":14720},"undelivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages the carrier reported as not delivered.","example":60},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages that failed during sending.","example":25},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages rejected before any send attempt, for example by sending policy or a message-generation failure.","example":10},"expired":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages that could not be delivered within their validity window and expired.","example":5}}},"SMSStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"SMS lifecycle counts for one time bucket (a calendar day or hour), bucketed by send time. Every count in a bucket describes the messages accepted in it, regardless of when their later events arrived. Per-bucket values include counts only. The summary and breakdown endpoints report rates and latency as whole-window aggregates.\n","required":["bucket","delivery"],"properties":{"bucket":{"type":"string","minLength":1,"readOnly":true,"description":"The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the period's grain.","example":"2026-05-25"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSDeliveryCounts"}]}}},"SMSStatsResponse":{"type":"object","additionalProperties":false,"description":"Time-series stats payload. `period` echoes the range and bucket grain the server computed against; `data` is one row per bucket in chronological order.\n","required":["period","data"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSeriesPeriod"},"data":{"type":"array","readOnly":true,"description":"One row per day or hour in chronological order. Buckets with no activity contain zero counts.","items":{"$ref":"#/components/schemas/SMSStatsPoint"}}}},"SMSStatsSortMetric":{"type":"string","default":"accepted","description":"Metric to rank breakdown rows by, applied descending. Shared by the volume breakdowns whose rows carry the full delivery and latency block (originators, countries, categories, carriers). Any lifecycle count or derived rate may be used; rows whose rate is undefined (zero denominator) sort last.\n","enum":["accepted","sent","delivered","undelivered","failed","rejected","expired","delivery_rate","failure_rate"]},"StatsTrendGrain":{"type":"string","enum":["daily","hourly"],"default":"daily","description":"Bucket grain for a stats trend series."},"SMSOriginatorStatsPoint":{"type":"object","additionalProperties":false,"description":"Aggregate delivery and latency stats for a single originator (the sender address messages were sent from) over the requested period.","required":["originator","delivery","latency"],"properties":{"originator":{"type":"string","minLength":1,"readOnly":true,"description":"Sender address this row aggregates, either an alphanumeric sender ID or a phone number. Matches the message `from` value.","example":"BirdSMS"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSDeliveryStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket lifecycle counts for this originator, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`.","items":{"$ref":"#/components/schemas/SMSStatsPoint"}}}},"SMSStatsByOriginatorResponse":{"type":"object","additionalProperties":false,"description":"Per-originator breakdown for the requested period, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Originator breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no messages were sent in the period.","items":{"$ref":"#/components/schemas/SMSOriginatorStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct originators with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":42}}},"SMSCountryStatsPoint":{"type":"object","additionalProperties":false,"description":"Aggregate delivery and latency stats for a single destination country over the requested period.","required":["country","delivery","latency"],"properties":{"country":{"type":"string","minLength":1,"readOnly":true,"description":"The destination country this row aggregates, as an ISO 3166-1 alpha-2 code.","example":"US"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSDeliveryStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket lifecycle counts for this country, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`.","items":{"$ref":"#/components/schemas/SMSStatsPoint"}}}},"SMSStatsByCountryResponse":{"type":"object","additionalProperties":false,"description":"Per-country breakdown for the requested period, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Country breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no messages were sent in the period.","items":{"$ref":"#/components/schemas/SMSCountryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct destination countries with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":42}}},"SMSCategoryStatsPoint":{"type":"object","additionalProperties":false,"description":"Aggregate delivery and latency stats for a single message category over the requested period.","required":["category","delivery","latency"],"properties":{"category":{"type":"string","minLength":1,"readOnly":true,"description":"The category this row aggregates, as set at send time. `transactional` is one-to-one messaging triggered by a user action; `marketing` is bulk sending. New categories may be added over time.","example":"transactional"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSDeliveryStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket lifecycle counts for this category, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`.","items":{"$ref":"#/components/schemas/SMSStatsPoint"}}}},"SMSStatsByCategoryResponse":{"type":"object","additionalProperties":false,"description":"Per-category breakdown for the requested period, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Category breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no messages were sent in the period.","items":{"$ref":"#/components/schemas/SMSCategoryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct categories with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":8}}},"SMSStatsLifecycleSortMetric":{"type":"string","default":"failed","description":"Metric to rank breakdown rows by, applied descending. Shared by the breakdowns whose rows carry lifecycle counts only, with no derived rates to sort on.\n","enum":["accepted","sent","delivered","undelivered","failed","rejected","expired"]},"SMSErrorCodeStatsPoint":{"type":"object","additionalProperties":false,"description":"Delivery and latency statistics for one standardized failure reason over the requested period.","required":["error_code","delivery","latency"],"properties":{"error_code":{"readOnly":true,"description":"Standardized failure reason this row aggregates. Matches the `error_code` message-list filter.","allOf":[{"$ref":"#/components/schemas/SMSErrorCode"}]},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSDeliveryStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket lifecycle counts for this error code, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`.","items":{"$ref":"#/components/schemas/SMSStatsPoint"}}}},"SMSStatsByErrorCodeResponse":{"type":"object","additionalProperties":false,"description":"Per-error-code breakdown for the requested period, ranked by the `sort` metric (default `failed`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Error-code breakdown rows, ranked by the `sort` metric (default `failed`) descending. Empty when no delivery failures occurred in the period.","items":{"$ref":"#/components/schemas/SMSErrorCodeStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct error codes with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":17}}},"SMSCarrierStatsPoint":{"type":"object","additionalProperties":false,"description":"Aggregate delivery and latency stats for a single delivery carrier over the requested period.","required":["carrier","delivery","latency"],"properties":{"carrier":{"type":"string","minLength":1,"readOnly":true,"description":"The delivery carrier this row aggregates, as resolved for the destination handset.","example":"Verizon"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSDeliveryStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket lifecycle counts for this carrier, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`.","items":{"$ref":"#/components/schemas/SMSStatsPoint"}}}},"SMSStatsByCarrierResponse":{"type":"object","additionalProperties":false,"description":"Per-carrier breakdown for the requested period, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Carrier breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no messages were sent in the period.","items":{"$ref":"#/components/schemas/SMSCarrierStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct carriers with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":24}}},"SMSTagStatsPoint":{"type":"object","additionalProperties":false,"description":"Aggregate delivery and latency stats for a single tag (`name:value`) over the requested period.","required":["tag","delivery","latency"],"properties":{"tag":{"type":"string","minLength":1,"readOnly":true,"description":"The tag this row aggregates, in `name:value` form. Each distinct name-and-value pair is its own row, and a message carrying several tags is counted once under each of them, so rows do not sum to the period total.\n","example":"campaign:summer_sale"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSDeliveryStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket lifecycle-count series for this tag over the window, bucketed by `trend_grain`. Sparse, so only buckets with activity are present rather than zero-filled, unlike the daily and hourly series. Present only when `include_trend=true`.\n","items":{"$ref":"#/components/schemas/SMSStatsPoint"}}}},"SMSStatsByTagResponse":{"type":"object","additionalProperties":false,"description":"Per-tag breakdown for the requested period, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Tag breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no tagged messages were sent in the period.","items":{"$ref":"#/components/schemas/SMSTagStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct tags with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":18}}},"SMSStatusStatsPoint":{"type":"object","additionalProperties":false,"description":"The number of messages that ended the requested period in a single lifecycle status. This transposes the lifecycle counts into one row per status, so it carries no rates or latency.","required":["status","count"],"properties":{"status":{"type":"string","minLength":1,"readOnly":true,"description":"The lifecycle status this row counts. These are successive lifecycle stages. The `accepted` status was admitted for sending, `sent` was handed to the carrier, and `delivered` was confirmed by the carrier. The `undelivered`, `failed`, and `expired` statuses are failure outcomes. The `rejected` status was refused before a send attempt. Counted outcomes are a subset of the full message status vocabulary. The pre-send `scheduled`, cancellation `canceled`, and inbound-only `received` statuses are not send outcomes, so they never appear here.\n","enum":["accepted","sent","delivered","undelivered","failed","rejected","expired"],"example":"delivered"},"count":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages that reached this lifecycle status in the period, attributed to the message's send time rather than the event's own.","example":14720}}},"SMSStatsByStatusResponse":{"type":"object","additionalProperties":false,"description":"Lifecycle-status breakdown for the requested period, ordered by message count descending. Statuses with no activity are omitted.","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Status breakdown rows, one per lifecycle status with activity, ordered by count descending. Empty when no messages had activity in the period.","items":{"$ref":"#/components/schemas/SMSStatusStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of distinct lifecycle statuses with activity in the period (at most seven). Equal to the number of rows returned, since this breakdown is never capped.","example":5}}},"SMSInboundStatsComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The change from the preceding period to the requested one. The `received_pct_change` field is a signed relative change, computed as `(current - previous) / previous`. A value of `0.5` means 50% higher, and `-0.2` means 20% lower. The field is null when the previous period received none.\n","required":["received_pct_change"],"properties":{"received_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in received messages versus the previous period, as a signed fraction. Null when the previous period received none.","example":0.058}}},"SMSInboundStatsComparison":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The received-message count for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request.\n","required":["period","received","delta"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"Equal-length window ending immediately before the requested start."},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received in the preceding period.","example":3980},"delta":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSInboundStatsComparisonDelta"}]}}},"SMSInboundStatsSummaryResponse":{"type":"object","additionalProperties":false,"description":"Total received messages over the requested period.\n","required":["period","received"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod"},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received in the period, counted by the time the carrier received them.","example":4210},"comparison":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSInboundStatsComparison"}]}}},"SMSInboundStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One time bucket of received-message counts, attributed by arrival time.","required":["bucket","received"],"properties":{"bucket":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Start of the bucket this row covers, as a calendar day (YYYY-MM-DD) for the daily series or an hour boundary (RFC 3339) for the hourly one.","example":"2026-05-01"},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received in this bucket, counted by the time the carrier received them.","example":128}}},"SMSInboundStatsResponse":{"type":"object","additionalProperties":false,"description":"Received-message time series. `period` echoes the range and bucket grain the server computed against; `data` is one row per bucket in chronological order.\n","required":["period","data"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSeriesPeriod"},"data":{"type":"array","readOnly":true,"description":"One row per bucket (day or hour, per the grain) in the period, in chronological order. Buckets with no activity are included with a count of zero, so the series charts continuously without client-side gap handling.","items":{"$ref":"#/components/schemas/SMSInboundStatsPoint"}}}},"SMSInboundCountryStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Received-message volume for one country.\n","required":["country","received"],"properties":{"country":{"type":"string","minLength":1,"readOnly":true,"description":"The country of the Bird number the messages arrived on, as an ISO 3166-1 alpha-2 code. This identifies where the message was received. It does not identify the sender's country.","example":"US"},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received on numbers in this country during the period.","example":1840}}},"SMSInboundStatsByCountryResponse":{"type":"object","additionalProperties":false,"description":"Received-message volume broken down by country, ranked by volume.\n","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod"},"data":{"type":"array","readOnly":true,"description":"One row per country with activity in the period, most messages first, capped at the requested `limit`. A country with no messages in the period is absent rather than zero-filled, because unlike a time bucket it is not part of a continuous axis.\n","items":{"$ref":"#/components/schemas/SMSInboundCountryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct countries the messages arrived in with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.","example":12}}},"SMSInboundOperatorStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Received-message volume for one mobile operator.\n","required":["mcc_mnc","received"],"properties":{"mcc_mnc":{"type":"string","minLength":1,"readOnly":true,"description":"Mobile country code and mobile network code of the network the sending subscriber is on. The breakdown keys on this rather than on an operator name because the carrier reports a name only where a surcharge applies, which would leave most of the world in one unnamed bucket.","example":"311480"},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received from senders on this operator during the period.","example":640}}},"SMSInboundStatsByOperatorResponse":{"type":"object","additionalProperties":false,"description":"Received-message volume broken down by operator, ranked by volume.\n","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod"},"data":{"type":"array","readOnly":true,"description":"One row per operator with activity in the period, most messages first, capped at the requested `limit`. An operator with no messages in the period is absent rather than zero-filled, because unlike a time bucket it is not part of a continuous axis.\n","items":{"$ref":"#/components/schemas/SMSInboundOperatorStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct sending operators with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.","example":38}}},"SMSInboundNumberStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Received-message volume for one of your numbers.\n","required":["number","received"],"properties":{"number":{"type":"string","minLength":1,"readOnly":true,"description":"The Bird number the messages arrived on, in E.164, or the short code they were sent to. This is the same value the message resource exposes as `to`.","example":"+14155557701"},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received on this number during the period.","example":412}}},"SMSInboundStatsByNumberResponse":{"type":"object","additionalProperties":false,"description":"Received-message volume broken down by number, ranked by volume.\n","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod"},"data":{"type":"array","readOnly":true,"description":"One row per number with activity in the period, most messages first, capped at the requested `limit`. A number with no messages in the period is absent rather than zero-filled, because unlike a time bucket it is not part of a continuous axis.\n","items":{"$ref":"#/components/schemas/SMSInboundNumberStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct numbers with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.","example":6}}},"DestinationSuperRegion":{"type":["string","null"],"minLength":1,"x-extensible-enum":["europe","americas","middle_east_africa","asia_pacific"],"example":"europe","description":"The continent-scale group the region rolls up into. Derived from `region`, and `null` whenever that is. Treat the set as open.\n"},"SMSDestinationStatus":{"type":"string","minLength":1,"x-extensible-enum":["available","registration_required","not_supported"],"description":"Whether we deliver SMS to this country, independent of your enabled setting.\n\n- `available`: we deliver SMS to this country.\n- `registration_required`: deprecated; use `sender_requirement` for sender\n  registration requirements.\n- `not_supported`: we do not deliver SMS to this country.\n\nThis is an open enum. Accept unrecognized values.\n"},"SMSDestination":{"type":"object","additionalProperties":false,"required":["country_code","country_name","enabled","status","high_risk_destination"],"properties":{"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"readOnly":true},"country_name":{"type":"string","minLength":1,"description":"Full English country name.","example":"Netherlands","readOnly":true},"dial_code":{"type":"string","description":"International dialing prefix without the leading plus sign. Absent for countries that have none.","example":"31","readOnly":true},"region":{"allOf":[{"$ref":"#/components/schemas/DestinationRegion"}],"readOnly":true},"super_region":{"allOf":[{"$ref":"#/components/schemas/DestinationSuperRegion"}],"readOnly":true},"enabled":{"type":"boolean","description":"Whether your workspace has enabled SMS sending to this country."},"status":{"readOnly":true,"$ref":"#/components/schemas/SMSDestinationStatus"},"next":{"type":"array","readOnly":true,"description":"What to do next about this country. Present on reads that compute it: an empty list\nmeans there is nothing to do, and the field is absent entirely on responses that do not\nreport next actions.\n\nThis routes rather than advises: it names where to go, and the resource it names carries\nwhat to do there.\n","items":{"$ref":"#/components/schemas/NextAction"}},"sender_requirement":{"type":["string","null"],"minLength":1,"x-extensible-enum":["no_registration","registration_required","own_number"],"description":"What this country asks of the sender you send with.\n\n- `no_registration`: an alphanumeric sender reaches it as-is.\n- `registration_required`: every accepted sender type must first be\n  registered.\n- `own_number`: the country accepts no alphanumeric sender, so you need a\n  number of an accepted type.\n\nNull when nothing can currently send there.\n\nThis field describes registration requirements and is independent of the\n`enabled` setting or the senders in your workspace. Check a specific sender with\n[List what a sender needs to send](/docs/api/reference/list-workspace-sms-sender-requirements).\n","readOnly":true},"high_risk_destination":{"type":"boolean","description":"Whether we treat this country as a high-risk messaging destination.","readOnly":true}}},"SMSDestinationList":{"type":"object","additionalProperties":false,"required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SMSDestination"},"description":"The SMS destination countries, each annotated with your workspace's enabled setting."},"total":{"type":"integer","format":"int64","description":"Total number of destination countries."}}},"DestinationSetting":{"type":"object","additionalProperties":false,"required":["country_code","enabled"],"properties":{"country_code":{"$ref":"#/components/schemas/CountryCode"},"enabled":{"type":"boolean","description":"Whether to enable (`true`) or disable (`false`) this destination country.","example":true}}},"SMSDestinationsUpdate":{"type":"object","additionalProperties":false,"required":["destinations"],"properties":{"destinations":{"type":"array","items":{"$ref":"#/components/schemas/DestinationSetting"},"description":"The destination countries to enable or disable. Only the countries listed here change; any country you do not list keeps its current setting.\n","example":[{"country_code":"NL","enabled":true},{"country_code":"GB","enabled":false}]}}},"SMSSenderSourceKind":{"type":"string","minLength":1,"x-extensible-enum":["number","shared_number","alpha"],"description":"What backs an SMS sender: a dedicated number, a shared shortcode, or an alphanumeric sender string.\n"},"AllocatedNumberID":{"type":"string","minLength":1,"pattern":"^(nda|nal)_[0-9a-hjkmnp-tv-z]{26}$","example":"nda_01krdgeqcxet5s7t44vh8rt9mg","description":"Identifier of a number allocated to your workspace, as returned in the id field of `GET /v1/numbers`."},"SMSSenderType":{"type":"string","minLength":1,"enum":["long_code","short_code","alphanumeric","toll_free"],"description":"The kind of sender identity a message was sent from: a standard long-code number, a short code, an alphanumeric sender ID, or a toll-free number. Toll-free is its own value rather than a kind of long code, because it carries a separate regulatory regime and both sending policy and pricing key on it directly.\n"},"SMSSenderStatus":{"type":"string","minLength":1,"enum":["active","pending_number_ownership","pending_registrations"],"description":"Where a sender stands on its own setup: the ownership paperwork its number owes, and the\nregistrations it holds.\n\n- `active`: nothing this sender holds is outstanding.\n- `pending_number_ownership`: the paperwork proving you own this sender's number is not in\n  place, either because it has not been supplied or because the country that issued the\n  number has not accepted it yet. Every send from this sender is refused until it does.\n- `pending_registrations`: the sender holds country registrations and none is approved yet.\n  That covers a registration under review and one that was refused, so read `next` for which.\n\nThis is not whether a message will be accepted. That is decided per destination country, and\n[Get sender requirements](/docs/api/reference/list-workspace-sms-sender-requirements) is the\nonly read that answers it. A sender that has registered nowhere is `active`, even where every\ncountry it can reach wants a registration first. Read `next` for where to go from here.\n"},"SMSSenderDestinations":{"type":"object","additionalProperties":false,"required":["active","pending","needs_attention"],"description":"Where this sender stands with the destinations it is set up for, grouped by whose move is\nnext.\n\nThese are not every country the sender could reach. `pending` and `needs_attention` cover\nonly countries you hold a registration for, so a country you could register for but have not,\nor one you have simply not switched on, appears in none of the three: there is nothing\noutstanding there. To see everything reachable and what each country would take, read\n[Get sender requirements](/docs/api/reference/list-workspace-sms-sender-requirements).\n","properties":{"active":{"type":"array","readOnly":true,"description":"Countries you can send to today: switched on in your SMS destination settings, and either asking for no registration or holding an approved one. A country counts here however it became usable, including one enabled for you automatically when your organization set its business address.\n","items":{"$ref":"#/components/schemas/CountryCode"}},"pending":{"type":"array","readOnly":true,"description":"Countries whose registration is under way and waiting on us or on a carrier. Nothing is asked of you for these; they move to `active` when the registration is approved.\n","items":{"$ref":"#/components/schemas/CountryCode"}},"needs_attention":{"type":"array","readOnly":true,"description":"Countries you have registered for that are not working, and so are waiting on you: the registration was refused or withdrawn, or it is approved but the destination is switched off in your SMS destination settings. While this sender's number is missing the paperwork proving you own it, every country you have registered for is listed here, because none of them can carry a message until that is settled.\n","items":{"$ref":"#/components/schemas/CountryCode"}}}},"SMSSender":{"type":"object","additionalProperties":false,"required":["id","sender","source_kind","status","destinations","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SMSSenderID","description":"Identifier of this sender."},"sender":{"type":"string","minLength":1,"readOnly":true,"description":"The value messages are sent from: a number in E.164 format, a shortcode, or an alphanumeric sender string.\n"},"source_kind":{"allOf":[{"$ref":"#/components/schemas/SMSSenderSourceKind"}],"readOnly":true},"sender_type":{"allOf":[{"$ref":"#/components/schemas/SMSSenderType"}],"readOnly":true,"description":"The kind of sender identity this is, which decides what each destination country asks of it. It is finer than source_kind: a short code and an ordinary long code are both numbers but follow different rules.\n"},"country_code":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"description":"The country of the number this sender sends from. Null for an alphanumeric sender."},"number_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AllocatedNumberID"}],"description":"The number this sender sends from, as returned in the `id` field of `GET /v1/numbers`. Present only when `source_kind` is `number` or `shared_number`; an alphanumeric sender has no number behind it. Exactly one sender exists per number, so this is also how you go the other way: `GET /v1/sms/senders?number_id={id}` returns this sender.\n"},"status":{"allOf":[{"$ref":"#/components/schemas/SMSSenderStatus"}],"readOnly":true},"next":{"type":"array","readOnly":true,"description":"What to do next about this sender, given the state it is in. Present on reads that\ncompute it: an empty list means there is nothing to do, and the field is absent entirely\non responses that do not report next actions.\n\nThis routes rather than advises. A sender only knows about itself, so where the work is\nper-country it names the requirements read rather than guessing which country you meant.\n","items":{"$ref":"#/components/schemas/NextAction"}},"destinations":{"allOf":[{"$ref":"#/components/schemas/SMSSenderDestinations"}],"readOnly":true},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"SMSSenderList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Your workspace's dedicated numbers and alphanumeric senders, newest first.","items":{"$ref":"#/components/schemas/SMSSender"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"SMSSenderCreate":{"type":"object","additionalProperties":false,"required":["type","sender"],"properties":{"type":{"type":"string","minLength":1,"enum":["alpha"],"description":"The kind of sender to create. Only `alpha` (an alphanumeric sender string) is customer-creatable. Dedicated numbers become senders when you allocate them from inventory. This endpoint cannot create their senders.\n"},"sender":{"type":"string","minLength":3,"maxLength":11,"description":"The alphanumeric sender string to create, for example \"BIRD\". 3–11 letters, digits, spaces, dashes, or underscores, at least one of them a letter, and no separator at either end.\n","example":"BIRD"}}},"SMSRegistrationStatus":{"type":"string","minLength":1,"x-extensible-enum":["pending","under_review","bird_approved","carrier_pending","approved","rejected","suspended","registration_required","not_required"],"description":"Country-registration lifecycle state:\n\n- `pending`: Registration work has started.\n- `under_review`: Everything this country requires has been submitted and is being reviewed. Nothing is asked of you while it is.\n- `bird_approved`: Our review passed and carrier approval is still pending.\n- `carrier_pending`: Submitted to the approving carrier or registry.\n- `approved`: Approved for sending.\n- `rejected`: Registration was refused.\n- `suspended`: A previously active registration is suspended.\n- `registration_required`: No required registration has been started.\n- `not_required`: The country does not require registration.\n"},"SMSCountryRegistrationID":{"type":"string","minLength":1,"pattern":"^scr_[0-9a-hjkmnp-tv-z]{26}$","example":"scr_01krdgeqcxet5s7t44vh8rt9mg"},"SMSSenderProgramEntry":{"type":"string","minLength":1,"x-extensible-enum":["none","10dlc_brand","10dlc_campaign","10dlc_campaign_number_link","tfn_verification","compliance_submission"],"description":"The resource this country's walk continues at. `none` means the first one is still to be\ncreated; every other value names what to read next, whose id is `program_entry_ref`.\n\nEvery programme names its entry. US 10DLC enters at a `10dlc_brand` or a `10dlc_campaign` and\ncontinues at the `10dlc_campaign_number_link` a number hangs off. US toll-free both enters and\ncontinues at its `tfn_verification`. A programme answered by filling in a form enters at the\n`compliance_submission` holding your answers, the only entry that names something you filled in\nrather than something a carrier holds. Read the entry whenever you need the detail behind a\ncountry: it carries why a registration was refused, which this row reports only as a status.\n\nThe values are programme-prefixed where a programme owns them, so one vocabulary reads the same\nwherever an entry is reported. Tolerate a value you do not recognize: read `program_entry_ref`\nas the id of a resource to fetch, and read `next` for what to do about the country.\n\nComputed on this read, so it is never stale and never absent. That matters most where a row\ncannot answer for itself: a toll-free verification you have started but not submitted is named\nhere even though no registration exists for it yet, which is what tells you to finish that one\nrather than start a second.\n"},"SMSSenderRequirement":{"type":"object","additionalProperties":false,"required":["sender_id","country_code","registration_mode","sender_id_behavior","status","destination_enabled","program_entry"],"properties":{"sender_id":{"allOf":[{"$ref":"#/components/schemas/SMSSenderID"}],"readOnly":true,"description":"The sender these requirements are for, the same one you asked about. It is repeated on every row so a row is complete on its own: what a country needs is addressed by the sender and the country together, and a step that registers this sender needs both.\n"},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"readOnly":true},"registration_mode":{"allOf":[{"$ref":"#/components/schemas/SMSRegistrationMode"}],"readOnly":true,"description":"Whether country registration is absent, optional, or required before sending."},"sender_id_behavior":{"allOf":[{"$ref":"#/components/schemas/SMSSenderIDBehavior"}],"readOnly":true,"description":"The effective sender ID behavior for this sender and destination. An approved optional registration changes `overwritten` to `preserved` for its permitted message categories. Required registration does not change the configured sender ID behavior after approval. `unknown` means no behavior is published. This is expected behavior, not confirmation of what a recipient saw. Replacement details are published in the country messaging context.\n"},"program":{"readOnly":true,"description":"The registration program available for this destination, including optional registration. An existing registration retains its own program.\n","oneOf":[{"$ref":"#/components/schemas/SMSRegistrationProgram"},{"type":"null"}]},"status":{"allOf":[{"$ref":"#/components/schemas/SMSRegistrationStatus"}],"readOnly":true,"description":"Where this sender stands with this country's registration programme. The `approved` and `not_required` statuses satisfy it. Every other status leaves it unsatisfied: the `registration_required` status means the required registration has not started, the `pending` status means something required is still owed by you, and `under_review` means it is all with us. Read `next` for what to do about any of it.\n\nRegistration blocks sending when `registration_mode` is `required`. Optional registration can be pending while sending remains available with `sender_id_behavior` set to `overwritten`. Check `destination_enabled` too, which is independent of registration progress.\n"},"next":{"type":"array","readOnly":true,"description":"What to do next about this country, given the state it is in. Each entry names one action\nand says why it is worth taking, so you can act on this response without working out the\norder yourself. Present on reads that compute it: an empty list means there is nothing to\ndo, and the field is absent entirely on responses that do not report next actions.\n\nThis routes rather than advises: it names where to go to act on this country, and the\nresource it names carries what to do there. So a country under carrier review names the\nverification to read, rather than restating a verdict that resource owns.\n","items":{"$ref":"#/components/schemas/NextAction"}},"destination_enabled":{"type":"boolean","readOnly":true,"description":"Whether your workspace has this destination country turned on. This is a workspace setting rather than anything about this sender, and it is checked before the sender is even considered, so a country can be fully registered and still refuse your messages because it is switched off here. Turn it on with [Update SMS destinations](/docs/api/reference/update-workspace-sms-destinations).\n"},"category":{"type":["string","null"],"minLength":1,"readOnly":true,"x-extensible-enum":["transactional","marketing","authentication","service"],"description":"Message category the registration covering this country is approved for. Null when the registration admits any category, and null before you register.\n"},"registration_id":{"readOnly":true,"description":"The registration covering this country, once one exists. Null before you register. Where the country's requirements are collected as a form, this is the `subject_id` to pass to `GET /v1/compliance/requirements`.\n","oneOf":[{"$ref":"#/components/schemas/SMSCountryRegistrationID"},{"type":"null"}]},"program_entry":{"$ref":"#/components/schemas/SMSSenderProgramEntry"},"program_entry_ref":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The id of the resource `program_entry` names, or null when it names none. Where the workspace holds several to choose from, this is the one to start from: the only brand or campaign if there is one, otherwise the most recently approved, and otherwise the most recent.\n"},"rejection_reason":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Why the registration was rejected, when it was. Null otherwise."},"obligation_due_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this country's registration must be brought up to date by, if it must. Null when nothing is outstanding, which is the ordinary case. The sender keeps sending until the date passes; `GET /v1/compliance/requirements` says what is being asked for.\n"},"timeline_min_days":{"type":["integer","null"],"readOnly":true,"description":"Shortest time approval usually takes, in business days. Null when the country publishes no estimate."},"timeline_max_days":{"type":["integer","null"],"readOnly":true,"description":"Longest time approval usually takes, in business days. Null when the country publishes no estimate."},"registration_types":{"type":"array","readOnly":true,"items":{"type":"string","minLength":1},"description":"Registration categories this country's program accepts."}}},"SMSSenderProgram":{"type":"object","additionalProperties":false,"required":["program","program_ref","countries","status"],"properties":{"program":{"readOnly":true,"description":"The program this registration runs. Null for the countries that asked for no registration at all, which are grouped together because the only thing they have in common is that you registered nothing for them.\n","oneOf":[{"$ref":"#/components/schemas/SMSRegistrationProgram"},{"type":"null"}]},"program_ref":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The resource this registration produced, which `program` tells you how to read: a toll-free verification for `tfn`, or a campaign number link for `10dlc`. The link connects this number to its campaign. Get the campaign from `GET /v1/sms/10dlc/campaign-number-links/{link_id}`. Null for a program that issues none, and for one you have not registered yet. Follow it when the status is under_review, because only that resource distinguishes a carrier still reviewing from one waiting on more information from you.\n"},"countries":{"type":"array","minItems":1,"readOnly":true,"description":"Every country this one registration covers. Some programs open several at once, so a single toll-free verification or campaign registration can be what permits four destinations.\n","items":{"$ref":"#/components/schemas/CountryCode"}},"status":{"readOnly":true,"description":"Where this registration stands, for all of the countries it covers. `registration_required` means you have not made it yet and every country listed is waiting on it. Null in the one case where the countries have diverged and no single answer is true: read those countries individually.\n","oneOf":[{"$ref":"#/components/schemas/SMSRegistrationStatus"},{"type":"null"}]}}},"SMSSenderRequirementList":{"type":"object","additionalProperties":false,"required":["data","programs"],"properties":{"data":{"type":"array","description":"One entry per destination country this sender can reach, which is the level the decision to carry your message is made at. Ask this list whether you can send somewhere, and what it takes first. A country missing from it is one this sender has no route to at all, so no registration would open it.\n","items":{"$ref":"#/components/schemas/SMSSenderRequirement"}},"programs":{"type":"array","description":"The same countries seen as the registrations they belong to, one entry per registration, with the countries each one covers. Ask this list what to do next: an entry whose status is `registration_required` is a registration you still have to make, and its countries are the ones it would open. Every other entry is a registration you have already made, so this list is the whole picture rather than only the part you have finished.\n","items":{"$ref":"#/components/schemas/SMSSenderProgram"}}}},"SMSCountryRegistrationCreate":{"type":"object","additionalProperties":false,"required":["country_code"],"properties":{"country_code":{"$ref":"#/components/schemas/CountryCode"},"category":{"allOf":[{"$ref":"#/components/schemas/SMSMessageCategory"}],"description":"Message category to register the sender for. Omit to register the sender for all categories.\n"}}},"SMSCountryRegistration":{"type":"object","additionalProperties":false,"required":["id","country_code","status","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SMSCountryRegistrationID","description":"Identifier of this country registration."},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"readOnly":true},"registration_mode":{"allOf":[{"$ref":"#/components/schemas/SMSRegistrationMode"}],"readOnly":true,"description":"Current registration policy for this destination and sender type. Omitted if the policy or sender is no longer available."},"sender_id_behavior":{"allOf":[{"$ref":"#/components/schemas/SMSSenderIDBehavior"}],"readOnly":true,"description":"Expected sender ID behavior for this registration's message categories. Optional registration preserves the sender ID once approved. Required registration can still use an overwritten sender ID after approval. Omitted if the policy or sender is no longer available.\n"},"program":{"readOnly":true,"description":"The registration program behind this row. Null for a country that required none, which is the state a row records rather than a program called \"none\".\n","oneOf":[{"$ref":"#/components/schemas/SMSRegistrationProgram"},{"type":"null"}]},"category":{"type":["string","null"],"minLength":1,"readOnly":true,"x-extensible-enum":["transactional","marketing","authentication","service"],"description":"Message category this registration is approved for. Null means the registration is unrestricted and admits any category.\n"},"status":{"allOf":[{"$ref":"#/components/schemas/SMSRegistrationStatus"}],"readOnly":true},"program_ref":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The program artifact that decided this registration, null where none produced it. Which resource it names follows from `program`, and its id prefix tells you too: a toll-free verification for `tfn`, or a campaign number link for `10dlc`. The link connects this number to its campaign. Get the campaign from [Get a 10DLC campaign number link](/docs/api/reference/get-tcr-campaign-number-link). Worth following when the status is `under_review`, because that value covers both the carrier still reviewing and the carrier waiting on more information from you, and only the program's own resource tells those apart.\n"},"rejection_reason":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Reason the registration was rejected, shown when the status is `rejected`. Null for registrations that were never rejected.\n"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"TFNVerificationStatus":{"type":"string","minLength":1,"enum":["draft","submitted","under_review","info_requested","approved","rejected"],"description":"Lifecycle state of a toll-free number verification:\n\n- `draft`: You can edit or submit the business and messaging details.\n- `submitted`: The verification was sent to the carrier for review.\n- `under_review`: The carrier is reviewing the verification.\n- `info_requested`: The carrier needs changes before deciding. You can edit and resubmit the verification.\n- `approved`: The number is licensed for sending in the United States. Approval is final.\n- `rejected`: The carrier declined the verification. You can correct and resubmit it only when `resubmit_allowed` is true.\n"},"TFNVerificationID":{"type":"string","minLength":1,"pattern":"^tfv_[0-9a-hjkmnp-tv-z]{26}$","example":"tfv_01krdgeqcxet5s7t44vh8rt9mg"},"TFNBusinessRegistrationType":{"type":"string","enum":["ein","cbn","neq","provincial_number","crn","vat","acn","abn","brn","siren","siret","nzbn","ust_idnr","cif","nif","cnpj","uid","other"],"description":"Kind of government-issued business registration identifier supplied in\n`business_registration_number` for a toll-free verification:\n\n- `ein`: US Employer Identification Number.\n- `cbn`: Canada Business Number.\n- `neq`: Numéro d'entreprise du Québec, Quebec's provincial enterprise number.\n- `provincial_number`: A Canadian provincial registration number other than `neq`.\n- `crn`: Company Registration Number, issued by a national companies registry (for example, UK Companies House).\n- `vat`: Value Added Tax identification number.\n- `acn`: Australian Company Number.\n- `abn`: Australian Business Number.\n- `brn`: Business Registration Number, issued by a national business registry.\n- `siren`: French business identification number.\n- `siret`: French business establishment identification number (a siren plus a location suffix).\n- `nzbn`: New Zealand Business Number.\n- `ust_idnr`: Umsatzsteuer-Identifikationsnummer, the Austrian or German vat identification number.\n- `cif`: Certificado de Identificación Fiscal, the Spanish tax ID for legal entities.\n- `nif`: Número de Identificación Fiscal, a Spanish or Portuguese tax identification number.\n- `cnpj`: Cadastro Nacional da Pessoa Jurídica, the Brazilian business tax ID.\n- `uid`: Unternehmens-Identifikationsnummer, the Swiss or Austrian business identification number.\n- `other`: A government-issued business identifier not listed above.\n"},"TFNEntityType":{"type":"string","enum":["sole_proprietor","private_profit","public_profit","non_profit","government"],"description":"Legal structure of the business being verified. Spelled the same as 10DLC's entity type, which names the same five structures; the two stay separate enums because what each authority accepts is its own to change.\n"},"TFNMessageVolume":{"type":"string","enum":["up_to_10","up_to_100","up_to_1k","up_to_10k","up_to_100k","up_to_250k","up_to_500k","up_to_750k","up_to_1m","up_to_5m","above_5m"],"description":"Estimated messages a month, as the band the carrier reviews against. One band\nper value the carrier accepts, so nothing is lost in either direction.\n\nThe top band is `above_5m` rather than a ten-million ceiling on purpose: the\nband below it is `up_to_5m`, so naming this one for ten million would leave\nfive to ten million with no band that claims it.\n"},"TFNVerificationBase":{"type":"object","description":"The server-owned fields returned on every toll-free verification.","required":["id","workspace_id","status","sender_id","identity_id","denial_reasons","resubmit_allowed","created_at","updated_at"],"properties":{"next":{"type":"array","readOnly":true,"description":"What to do next about this verification, given the state it is in. Each entry names one\naction and says why it is worth taking, so you can act on this response without working out\nthe order yourself. Present on reads that compute it: an empty list means there is nothing\nto do, and the field is absent entirely on responses that do not report next actions.\n\nAfter a refusal this reads `resubmit_allowed` as well as `status`: while the window is open\nthe cheaper move is to correct this verification and send it back, and once it closes a new\nverification for the same number is what remains.\n","items":{"$ref":"#/components/schemas/NextAction"}},"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TFNVerificationID"}],"description":"Identifier of this toll-free verification."},"workspace_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WorkspaceID"}],"description":"The workspace this verification belongs to."},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TFNVerificationStatus"}]},"sender_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSSenderID"}],"description":"The toll-free sender this verification licenses for SMS. Immutable once created."},"identity_id":{"readOnly":true,"description":"The compliance party this verification describes, and null when none was named at creation.\n","oneOf":[{"$ref":"#/components/schemas/ComplianceIdentityID"},{"type":"null"}]},"legal_entity_name":{"type":"string","readOnly":true,"description":"Legal name of the business being verified."},"business_display_name":{"type":"string","maxLength":500,"readOnly":true,"description":"Doing business as (DBA) or trading name, if different from the legal business name. Optional; an empty string clears the name."},"business_registration_number":{"type":"string","readOnly":true,"description":"Government-issued business registration or tax identifier."},"business_registration_country":{"readOnly":true,"description":"Country that issued the business registration, as an ISO 3166-1 alpha-2 code such as `US`. Always uppercase.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"business_registration_type":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TFNBusinessRegistrationType"}]},"business_entity_type":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TFNEntityType"}]},"business_address_line1":{"type":"string","readOnly":true,"description":"First line of the business address."},"business_address_line2":{"type":"string","readOnly":true,"description":"Second line of the business address."},"business_address_city":{"type":"string","readOnly":true,"description":"City of the business address."},"business_address_state":{"type":"string","readOnly":true,"description":"State or province of the business address."},"business_address_postal_code":{"type":"string","readOnly":true,"description":"Postal or ZIP code of the business address."},"business_address_country":{"readOnly":true,"description":"Country of the business address, as an ISO 3166-1 alpha-2 code. Always uppercase.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"contact_first_name":{"type":"string","readOnly":true,"description":"First name of the business contact."},"contact_last_name":{"type":"string","readOnly":true,"description":"Last name of the business contact."},"contact_email":{"type":"string","readOnly":true,"description":"Email address of the business contact."},"contact_phone":{"type":"string","readOnly":true,"description":"Phone number of the business contact, in E.164 format."},"business_website":{"type":"string","readOnly":true,"description":"Public website of the business."},"privacy_policy_url":{"type":"string","readOnly":true,"description":"Publicly reachable URL of the privacy policy that applies to recipients of these messages."},"terms_and_conditions_url":{"type":"string","readOnly":true,"description":"Publicly reachable URL of the terms and conditions that apply to recipients of these messages."},"use_case_category":{"type":"string","readOnly":true,"description":"The messaging use case for this number."},"use_case_description":{"type":"string","readOnly":true,"description":"A short description of how the number is used."},"message_sample":{"type":"string","readOnly":true,"description":"A representative example of messages sent from the number."},"opt_in_description":{"type":"string","readOnly":true,"description":"How recipients opt in to receive messages."},"opt_in_evidence_attachment_ids":{"type":"array","readOnly":true,"description":"IDs of uploaded screenshots evidencing the opt-in workflow.","items":{"$ref":"#/components/schemas/AttachmentID"}},"opt_in_evidence_attachments":{"type":"array","readOnly":true,"description":"The uploaded screenshots evidencing the opt-in workflow, resolved from `opt_in_evidence_attachment_ids`.","items":{"$ref":"#/components/schemas/Attachment"}},"monthly_volume_estimate_tier":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TFNMessageVolume"}]},"age_gated":{"type":"boolean","readOnly":true,"description":"Whether the messaging content is age-gated."},"help_response":{"type":"string","readOnly":true,"description":"The `HELP` reply you are declaring to the carrier, reviewed as part of verification. It does not set what is actually sent: configure that under your SMS keyword rules, and keep the two consistent so what goes out matches what was approved.\n"},"opt_in_confirmation_response":{"type":"string","readOnly":true,"description":"The opt-in confirmation you are declaring to the carrier, reviewed as part of verification. It does not set what is actually sent.\n"},"denial_reasons":{"type":"array","readOnly":true,"description":"Reasons the carrier gave when it declined or requested changes. Empty unless the verification was rejected or needs more information.","items":{"type":"string"}},"resubmit_allowed":{"type":"boolean","readOnly":true,"description":"Whether you may edit and submit the verification again after a rejection or an information request."},"submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the verification was submitted to the carrier. Null while it is still a draft."},"decided_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the carrier approved or rejected the verification. Null until a decision is made."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this verification was created."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this verification was last updated."}}},"TFNVerification":{"description":"A workspace's toll-free number verification containing the business and messaging details submitted to license a toll-free number for sending in the United States.","allOf":[{"$ref":"#/components/schemas/TFNVerificationBase"}]},"TFNVerificationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Your workspace's toll-free number verifications, newest first.","items":{"$ref":"#/components/schemas/TFNVerification"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"TFNVerificationInput":{"type":"object","description":"The writable business and messaging dossier shared by the create and update bodies of a toll-free verification.","properties":{"legal_entity_name":{"type":"string","description":"Legal name of the business being verified."},"business_display_name":{"type":"string","maxLength":500,"description":"Doing business as (DBA) or trading name, if different from the legal business name. Optional; an empty string clears the name."},"business_registration_number":{"type":"string","description":"Government-issued business registration or tax identifier."},"business_registration_country":{"description":"Country that issued the business registration, as an ISO 3166-1 alpha-2 code such as `US`. Accepted in either case and stored uppercase.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"business_registration_type":{"$ref":"#/components/schemas/TFNBusinessRegistrationType"},"business_entity_type":{"$ref":"#/components/schemas/TFNEntityType"},"business_address_line1":{"type":"string","description":"First line of the business address."},"business_address_line2":{"type":"string","description":"Second line of the business address."},"business_address_city":{"type":"string","description":"City of the business address."},"business_address_state":{"type":"string","description":"State or province of the business address."},"business_address_postal_code":{"type":"string","description":"Postal or ZIP code of the business address."},"business_address_country":{"description":"Country of the business address, as an ISO 3166-1 alpha-2 code. Accepted in either case and stored uppercase.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"contact_first_name":{"type":"string","description":"First name of the business contact."},"contact_last_name":{"type":"string","description":"Last name of the business contact."},"contact_email":{"type":"string","description":"Email address of the business contact."},"contact_phone":{"type":"string","description":"Phone number of the business contact, in E.164 format."},"business_website":{"type":"string","description":"Public website of the business."},"privacy_policy_url":{"type":"string","maxLength":500,"description":"Publicly reachable URL of the privacy policy that applies to recipients of these messages."},"terms_and_conditions_url":{"type":"string","maxLength":500,"description":"Publicly reachable URL of the terms and conditions that apply to recipients of these messages."},"use_case_category":{"type":"string","description":"The messaging use case for this number."},"use_case_description":{"type":"string","description":"A short description of how the number is used."},"message_sample":{"type":"string","description":"A representative example of messages sent from the number."},"opt_in_description":{"type":"string","description":"How recipients opt in to receive messages."},"opt_in_evidence_attachment_ids":{"type":"array","maxItems":10,"description":"IDs of uploaded screenshots evidencing the opt-in workflow. Upload each file with `POST /v1/compliance/attachments` first, then list the returned IDs here.","items":{"$ref":"#/components/schemas/AttachmentID"}},"monthly_volume_estimate_tier":{"$ref":"#/components/schemas/TFNMessageVolume"},"age_gated":{"type":"boolean","description":"Whether the messaging content is age-gated."},"help_response":{"type":"string","description":"The `HELP` reply you are declaring to the carrier, reviewed as part of verification. It does not set what is actually sent: configure that under your SMS keyword rules, and keep the two consistent so what goes out matches what was approved.\n"},"opt_in_confirmation_response":{"type":"string","description":"The opt-in confirmation you are declaring to the carrier, reviewed as part of verification. It does not set what is actually sent.\n"}}},"TFNVerificationCreate":{"description":"Fields for a new toll-free verification. The toll-free sender is required and cannot be changed later; every other field is optional. Fill in the dossier over one or more updates, then submit it for review.\n","allOf":[{"$ref":"#/components/schemas/TFNVerificationInput"},{"type":"object","required":["sender_id"],"properties":{"sender_id":{"allOf":[{"$ref":"#/components/schemas/SMSSenderID"}],"description":"The toll-free sender to license for SMS. Must be a toll-free, SMS-capable number your workspace owns. Immutable once created."},"identity_id":{"allOf":[{"$ref":"#/components/schemas/ComplianceIdentityID"}],"description":"The business this verification is for, recorded so registrations for the same business are recognisable as one. Optional, and it must belong to this workspace. The verification is built from the details in this request either way."}}}]},"TFNVerificationRequirements":{"type":"object","additionalProperties":false,"description":"What a toll-free verification needs before a carrier will review it, in the order to present it, plus what you have already supplied and where each item stands.","required":["verification_id","satisfied","needs_input","fields"],"properties":{"next":{"type":"array","readOnly":true,"description":"What to do next about this verification, given the state it is in. Each entry names one\naction and says why it is worth taking. An empty list means there is nothing to do; the\nfield is absent on responses that do not compute next actions.\n","items":{"$ref":"#/components/schemas/NextAction"}},"verification_id":{"readOnly":true,"description":"The verification these answers belong to. `null` when you have not started one, in which case the field list is the blank form.","oneOf":[{"$ref":"#/components/schemas/TFNVerificationID"},{"type":"null"}]},"satisfied":{"type":"boolean","readOnly":true,"description":"Whether every required item has an answer the carrier accepted. Optional items never hold it back, whatever state they are in."},"needs_input":{"type":"boolean","readOnly":true,"description":"Whether anything is waiting on you: a required item with no answer, or one that was refused. False while every required item is either settled or with the carrier, which is what separates \"we need something from you\" from \"we are reviewing\". Those are two states `satisfied` reports alike, since it reports only whether everything was accepted."},"fields":{"type":"array","readOnly":true,"description":"Every item this verification requires, in the order to present them. The same shape the other registration programmes return.","items":{"$ref":"#/components/schemas/ComplianceRequirementField"}}}},"TFNVerificationUpdate":{"description":"Fields to update on an editable toll-free verification. Verifications in `draft` or `info_requested` are editable. A `rejected` verification is editable while `resubmit_allowed` is true. Omit a field to leave it unchanged. The assigned toll-free sender cannot be changed after creation.\n","allOf":[{"$ref":"#/components/schemas/TFNVerificationInput"}]},"LookupProperty":{"type":"string","minLength":1,"enum":["classification","porting","presence","roaming","sim_swap","score"],"description":"An intelligence property you can add to a base phone number lookup.\n\n- `classification`: the property resolves `line_type` to its precise\n  allocated service (premium rate, satellite, machine-to-machine,\n  payphone) where the base lookup only distinguishes broad categories.\n- `porting`: the property returns when the number last moved network and\n  its full porting record.\n- `presence`: the property reports whether the number is currently live\n  on the network.\n- `roaming`: the property reports whether it is roaming and on which\n  network.\n- `sim_swap`: the property returns when its SIM last changed.\n- `score`: the property returns a credibility score from 0 to 100.\n\nEach property you request is billed separately, and only when it is\ndelivered.\n","example":"classification"},"PhoneNumberLookupRequest":{"type":"object","additionalProperties":false,"required":["phone_number"],"properties":{"phone_number":{"type":"string","minLength":1,"description":"The phone number to look up, in international format: the country calling code, then the national number. The leading `+` is optional, and `00` works in its place, so `+31612345678`, `31612345678` and `0031612345678` are all the same number. A number written for dialling inside one country, with no country code, is rejected rather than guessed at."},"type":{"type":"array","description":"Properties to add to the base lookup. Omit this field or send an empty\narray to request only the base lookup.\n\nEach delivered property is billed in addition to the base lookup. A\nproperty that could not be answered is returned with its status and is\nnot billed.\n","items":{"$ref":"#/components/schemas/LookupProperty"}}},"example":{"phone_number":"+31612345678","type":["classification","presence"]}},"LookupNetworkInfo":{"type":"object","additionalProperties":false,"description":"The network a number belongs to.","properties":{"carrier_name":{"type":["string","null"],"readOnly":true,"description":"The carrier's name, absent when the carrier could not be identified."},"mcc":{"type":["string","null"],"readOnly":true,"description":"The mobile country code, absent for a network that has none or could not be identified."},"mnc":{"type":["string","null"],"readOnly":true,"description":"The mobile network code, absent for a network that has none or could not be identified."}},"example":{"carrier_name":"KPN","mcc":"204","mnc":"08"}},"LookupFlag":{"type":"string","minLength":1,"x-extensible-enum":["ported"],"description":"A notable characteristic of a number. `ported` means the number has moved\nfrom the network that issued it to another one, so `network_info` and\n`original_network_info` name different carriers.\n\nOpen enum: more flags may be added over time, so treat an unrecognized value\nas a future flag rather than an error.\n","example":"ported"},"LookupLineType":{"type":"string","minLength":1,"enum":["mobile","fixed_line","voip","toll_free","premium_rate","satellite","pager","payphone","m2m","service","other","unknown"],"description":"What kind of line the number is, as reported by the carrier platform.\n\nThis is included in every base lookup, regardless of which properties you\nrequest.\n\n- `unknown`: the carrier platform holds no classification for the\n  range.\n- `other`: it holds a classification with no equivalent here.\n- `m2m`: a range reserved for machine-to-machine traffic, belonging to no\n  individual subscriber.\n\nFor the allocated service of the range at finer precision, request the\n`classification` property. That answers from a different source, with its own\nwider vocabulary, and is reported separately so you can always tell the two\napart.\n","example":"mobile"},"LookupPropertyStatus":{"type":"string","minLength":1,"x-extensible-enum":["ok","unavailable","inconclusive"],"description":"How a requested property resolved.\n\n- `ok`: the property was answered and its value is in the response.\n- `unavailable`: no answer arrived, so the property adds nothing: its\n  block is `null`, or for `classification`, `line_type` retains the value\n  from the base lookup. The property is not billed.\n- `inconclusive`: an answer arrived but does not resolve the\n  property, either because the number is outside the coverage of the data\n  behind it or because the source returned a value this property does not\n  report. It is a real answer rather than a missing one, and it is not\n  billed either.\n\nOpen enum: further statuses may be added over time, so treat an unrecognized\nvalue as a future one rather than an error. Only `ok` carries a value and only\n`ok` is billed, so branching on `ok` and treating everything else as \"not\nanswered\" stays correct however the vocabulary grows.\n","example":"ok"},"LookupClassificationValue":{"type":"string","minLength":1,"enum":["mobile","fixed_line","fixed_line_or_mobile","voip","toll_free","premium_rate","shared_cost","local_rate","national_rate","personal_number","universal_access","satellite","pager","payphone","m2m","isp","vpn","voice_mail","calling_cards","short_codes","service","other"],"description":"The allocated service of the number's range, at the precision the\nintelligence source publishes it.\n\nThis is a finer vocabulary than `line_type`, which reports what the carrier\nplatform alone can tell. Some values have no carrier equivalent. For example,\na number the carrier calls `service` may be classified as `premium_rate`,\n`shared_cost`, `universal_access`, or a voicemail platform. Where the fields\noverlap, they remain independent results rather than one refining the other.\n\nThree values in particular have no carrier-side concept at all:\n\n- `m2m` is a range reserved for machine-to-machine traffic, belonging to no\n  individual subscriber.\n- `national_rate` is a non-geographic landline number charged above the\n  local rate.\n- `fixed_line_or_mobile` is a range a country allocates so a number can port\n  between the two.\n","example":"premium_rate"},"LookupClassification":{"type":"object","additionalProperties":false,"description":"The allocated service of the number's range. Returned when you request the\n`classification` property.\n\nThis sits beside `line_type` rather than replacing it, so you can always see\nwhich source answered. The `line_type` property is included in the base lookup\nand comes from the carrier platform. The `classification` property is\nseparately billed and comes from an intelligence source.\n","required":["status"],"properties":{"status":{"allOf":[{"$ref":"#/components/schemas/LookupPropertyStatus"}],"readOnly":true},"value":{"allOf":[{"$ref":"#/components/schemas/LookupClassificationValue"}],"readOnly":true,"description":"The allocated service of the range. Present only when `status` is `ok`."}},"example":{"status":"ok","value":"premium_rate"}},"LookupPresence":{"type":"object","additionalProperties":false,"description":"Whether the number is live on its network right now. Returned when you\nrequest the `presence` property.\n\nThe result reflects a real-time query to the network where the number is\nregistered.\n","required":["status"],"properties":{"status":{"allOf":[{"$ref":"#/components/schemas/LookupPropertyStatus"}],"readOnly":true},"reachable":{"type":"boolean","readOnly":true,"description":"Whether the number is registered on a network and able to receive traffic. A `false` value means the network answered and reported the number as currently unreachable. This differs from the API being unable to find out. Present only when `status` is `ok`.\n"}},"example":{"status":"ok","reachable":true}},"LookupRoaming":{"type":"object","additionalProperties":false,"description":"Whether the number is roaming, and on which network. Returned when you request the `roaming` property.","required":["status"],"properties":{"status":{"allOf":[{"$ref":"#/components/schemas/LookupPropertyStatus"}],"readOnly":true},"is_roaming":{"type":"boolean","readOnly":true,"description":"Whether the number is currently roaming outside its home network. Present only when `status` is `ok`."},"mcc":{"type":["string","null"],"readOnly":true,"description":"The mobile country code of the visited network. Absent when the number is not roaming or the visited network is not reported."},"mnc":{"type":["string","null"],"readOnly":true,"description":"The mobile network code of the visited network. Absent when the number is not roaming or the visited network is not reported."}},"example":{"status":"ok","is_roaming":true,"mcc":"262","mnc":"01"}},"LookupSimSwap":{"type":"object","additionalProperties":false,"description":"When the number's SIM last changed. Returned when you request the `sim_swap` property.","required":["status"],"properties":{"status":{"allOf":[{"$ref":"#/components/schemas/LookupPropertyStatus"}],"readOnly":true},"last_swapped_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the SIM was last changed. Absent when only a recency band is known."},"min_days":{"type":["integer","null"],"readOnly":true,"description":"The lower bound, in days, of how long ago the SIM was last changed. Networks that do not release an exact date report a band instead; absent when no lower bound is known."},"max_days":{"type":["integer","null"],"readOnly":true,"description":"The upper bound, in days, of how long ago the SIM was last changed. Absent when no upper bound is known; with a lower bound present, that means the change was at least `min_days` ago."}},"example":{"status":"ok","last_swapped_at":"2026-07-02T09:14:00Z","min_days":0,"max_days":7}},"LookupPortingEvent":{"type":"object","additionalProperties":false,"description":"One recorded move of a number between networks.","required":["occurred_at","action"],"properties":{"occurred_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the move was recorded, `null` when the record carries no date."},"action":{"type":["string","null"],"readOnly":true,"description":"What the record describes, as the number's registry reports it. Registries use their own short codes rather than a shared vocabulary, so treat this as a label to display rather than a value to branch on."}},"example":{"occurred_at":"2023-02-08T00:00:00Z","action":"A"}},"LookupPorting":{"type":"object","additionalProperties":false,"description":"Whether the number has ever moved network, when it last did, and its full porting record. Returned when you request the `porting` property. The base lookup only reports whether the number has ever ported, through the `ported` flag.\n","required":["status"],"properties":{"status":{"allOf":[{"$ref":"#/components/schemas/LookupPropertyStatus"}],"readOnly":true},"ported":{"type":"boolean","readOnly":true,"description":"Whether the number has ever moved network. `false` is a positive finding rather than a lack of one: the registry was consulted and holds no move for this number. Present only when `status` is `ok`.\n"},"last_ported_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the number last moved network. Absent when it has never ported or when no date is on record."},"last_ported_at_is_approximate":{"type":"boolean","readOnly":true,"description":"Whether `last_ported_at` is an approximation. Some registries record the period of a move without its exact day."},"history":{"type":"array","readOnly":true,"description":"Every move on record, oldest first. Absent when the number has never ported or when its registry publishes no history.","items":{"$ref":"#/components/schemas/LookupPortingEvent"}}},"example":{"status":"ok","ported":true,"last_ported_at":"2023-02-08T00:00:00Z","last_ported_at_is_approximate":false,"history":[{"occurred_at":"2023-02-08T00:00:00Z","action":"A"}]}},"LookupScore":{"type":"object","additionalProperties":false,"description":"A credibility score for the number. Returned when you request the `score` property.","required":["status"],"properties":{"status":{"allOf":[{"$ref":"#/components/schemas/LookupPropertyStatus"}],"readOnly":true},"value":{"type":"integer","minimum":0,"maximum":100,"readOnly":true,"description":"Credibility from 0 (low) to 100 (high). A low score means the number looks less credible than a typical subscriber line in the same range. Treat it as one signal instead of a verdict. It is a composite and is not derivable from the other properties. Present only when `status` is `ok`.\n"}},"example":{"status":"ok","value":84}},"PhoneNumberLookup":{"type":"object","additionalProperties":false,"description":"Information about a phone number.\n\nThe number, its flags, and its line type are included in the base lookup and\nare always present. The country and two networks are present when identified.\nEach property you request through `type` is returned in a block with the same\nname and its own `status`. A property you did not request is absent. A\nproperty that could not be answered is present with only its `status`. Each\nrequested property is billed only when its status is `ok`.\n\nFields with no value are omitted rather than returned as `null`.\n","required":["phone_number","flags","line_type"],"properties":{"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"The number that was looked up, in E.164 format."},"country_code":{"readOnly":true,"description":"The ISO 3166-1 alpha-2 country of the number. Absent when the number belongs to no single country, as a non-geographic range does.","oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}]},"network_info":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/LookupNetworkInfo"},{"type":"null"}],"description":"The network that serves the number today. Absent when no network could be identified."},"original_network_info":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/LookupNetworkInfo"},{"type":"null"}],"description":"The network that issued the number's range. It differs from `network_info` when the number has been ported. Absent when the issuing network could not be identified."},"flags":{"type":"array","readOnly":true,"description":"Notable characteristics of the number. Empty when none apply.","items":{"$ref":"#/components/schemas/LookupFlag"}},"line_type":{"allOf":[{"$ref":"#/components/schemas/LookupLineType"}],"readOnly":true},"classification":{"allOf":[{"$ref":"#/components/schemas/LookupClassification"}],"readOnly":true,"description":"The allocated service of the number's range. Absent unless you requested the `classification` property."},"presence":{"allOf":[{"$ref":"#/components/schemas/LookupPresence"}],"readOnly":true,"description":"Whether the number is live on its network. Absent unless you requested the `presence` property."},"roaming":{"allOf":[{"$ref":"#/components/schemas/LookupRoaming"}],"readOnly":true,"description":"Whether the number is roaming. Absent unless you requested the `roaming` property."},"sim_swap":{"allOf":[{"$ref":"#/components/schemas/LookupSimSwap"}],"readOnly":true,"description":"When the number's SIM last changed. Absent unless you requested the `sim_swap` property."},"porting":{"allOf":[{"$ref":"#/components/schemas/LookupPorting"}],"readOnly":true,"description":"The number's porting record. Absent unless you requested the `porting` property."},"score":{"allOf":[{"$ref":"#/components/schemas/LookupScore"}],"readOnly":true,"description":"The number's credibility score. Absent unless you requested the `score` property."}},"example":{"phone_number":"+441904123456","country_code":"GB","network_info":{"carrier_name":"BT","mcc":"234","mnc":"00"},"original_network_info":null,"flags":[],"line_type":"service","classification":{"status":"ok","value":"premium_rate"},"score":{"status":"ok","value":48},"presence":{"status":"ok","reachable":true},"roaming":{"status":"unavailable"}}},"EmailLookupRequest":{"type":"object","additionalProperties":false,"required":["email"],"properties":{"email":{"type":"string","minLength":3,"maxLength":254,"description":"The email address to look up. Send it exactly as you hold it. The part before the `@` is case-sensitive, so the API does not lowercase it. A display-name form such as `Aisha <aisha@example.com>` is rejected rather than unwrapped.\n"}},"example":{"email":"aisha.khan@example.com"}},"EmailLookupResult":{"type":"string","minLength":1,"x-extensible-enum":["valid","neutral","risky","undeliverable","typo"],"description":"The verdict on the address, and the one field to decide on.\n\n- `valid`: the address exists and accepts mail.\n- `neutral`: it could not be confirmed either way, usually because the\n  receiving domain answers every recipient the same.\n- `risky`: it probably accepts mail but is likelier than most to\n  bounce or complain. Examples include role, disposable, and low-reputation\n  addresses.\n- `undeliverable`: it does not accept mail, and `reason` says why.\n- `typo`: the address looks misspelled, and `did_you_mean` contains\n  the correction.\n\nOpen enum: further verdicts may be added over time, so treat an unrecognized\nvalue as a future one rather than an error. Branch on the values you know and\nfall back on `delivery_confidence`, which is always present and always\ncomparable.\n","example":"risky"},"EmailLookupFlag":{"type":"string","minLength":1,"x-extensible-enum":["role","disposable","free_provider"],"description":"A notable characteristic of an email address.\n\n- `role`: it addresses a function rather than a person (`support@`,\n  `info@`). Replies and consent are therefore ambiguous, and complaints are\n  more likely.\n- `disposable`: it belongs to a throwaway-address provider and\n  typically stops existing.\n- `free_provider`: it belongs to a consumer mailbox provider such as\n  Gmail or Outlook.com. This is ordinary for consumer mail and a signal when\n  you expected a business address.\n\nOpen enum: more flags may be added over time, so treat an unrecognized value\nas a future flag rather than an error.\n","example":"role"},"EmailLookupReason":{"type":"string","minLength":1,"x-extensible-enum":["invalid_syntax","invalid_domain","invalid_recipient"],"description":"Why an address cannot receive mail.\n\n- `invalid_syntax`: the address is malformed.\n- `invalid_domain`: the domain does not accept mail.\n- `invalid_recipient`: the domain accepts mail but this mailbox does\n  not exist.\n\nOpen enum: further reasons may be added over time, so treat an unrecognized\nvalue as a future one rather than an error. `result` is what to branch on; this\nfield explains it.\n","example":"invalid_recipient"},"EmailLookup":{"type":"object","additionalProperties":false,"description":"Assessment of whether an email address accepts mail, the confidence and reason for that\nassessment, and a suggested correction when the address appears misspelled.\n\n`result` is the field to decide on; `delivery_confidence` grades it, and\n`flags` describes the address itself rather than its deliverability, so a\nperfectly valid address can still have `role` or `disposable`.\n\nFields without resolved values are omitted rather than sent as null. Every\nfield present in the response was resolved.\n","required":["email","valid","result","delivery_confidence","flags"],"properties":{"email":{"type":"string","minLength":3,"maxLength":254,"readOnly":true,"description":"The address that was looked up, exactly as you sent it."},"valid":{"type":"boolean","readOnly":true,"description":"Whether the address is well-formed and its domain is set up to receive mail at all. It says nothing about the mailbox itself, so a `valid` domain with no such mailbox is `true` here and `undeliverable` in `result`."},"result":{"allOf":[{"$ref":"#/components/schemas/EmailLookupResult"}],"readOnly":true},"delivery_confidence":{"type":"integer","minimum":0,"maximum":100,"readOnly":true,"description":"How likely mail to this address is to be delivered, from 0 (certain not to be) to 100 (certain to be). Read it alongside `result` rather than instead of it, because the same score can sit under `neutral` or `risky` for different reasons."},"flags":{"type":"array","readOnly":true,"description":"Notable characteristics of the address. Empty when none apply.","items":{"$ref":"#/components/schemas/EmailLookupFlag"}},"reason":{"allOf":[{"$ref":"#/components/schemas/EmailLookupReason"}],"readOnly":true,"description":"Why the address cannot receive mail. Absent unless `result` is `undeliverable`."},"did_you_mean":{"type":"string","minLength":3,"maxLength":254,"readOnly":true,"description":"The address this one looks like a misspelling of. Absent unless a correction was found, which in practice means `result` is `typo`. Offer it to whoever typed the original rather than sending to it unasked, because it is a guess and the address they meant may be neither one."}},"example":{"email":"aisha.khan@example.com","valid":true,"result":"risky","delivery_confidence":42,"flags":["role","free_provider"]}},"KycAddress":{"type":"object","additionalProperties":false,"description":"The address you believe the number's owner lives at. Send whichever parts you hold; each is checked on its own, so a partial address still returns an answer for the parts you sent.\n","properties":{"street_name":{"type":"string","minLength":1,"maxLength":255,"description":"The street, without the house number."},"street_number":{"type":"string","minLength":1,"maxLength":32,"description":"The house or building number."},"unit_number":{"type":"string","minLength":1,"maxLength":32,"description":"The apartment, suite, or unit within the building."},"locality":{"type":"string","minLength":1,"maxLength":255,"description":"The city, town, or village."},"region":{"type":"string","minLength":1,"maxLength":255,"description":"The state, province, or region."},"postal_code":{"type":"string","minLength":1,"maxLength":32,"description":"The postal or ZIP code."},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"description":"The country the address is in."}},"example":{"street_name":"Keizersgracht","street_number":"117","locality":"Amsterdam","postal_code":"1015CJ","country_code":"NL"}},"KycIdentity":{"type":"object","additionalProperties":false,"description":"What you believe about the person who holds the number. Send only the\nattributes you want checked. Each is compared independently, and an omitted\nattribute is not checked.\n\nAt least one attribute is required. A request carrying only a phone number has\nnothing to compare.\n","properties":{"given_name":{"type":"string","minLength":1,"maxLength":255,"description":"The person's first name."},"middle_name":{"type":"string","minLength":1,"maxLength":255,"description":"The person's middle name or names."},"family_name":{"type":"string","minLength":1,"maxLength":255,"description":"The person's last name."},"birthdate":{"type":"string","format":"date","description":"The person's date of birth, as `YYYY-MM-DD`."},"email":{"type":"string","format":"email","minLength":3,"maxLength":254,"description":"The person's email address."},"address":{"$ref":"#/components/schemas/KycAddress"}},"example":{"given_name":"Aisha","family_name":"Khan","birthdate":"1985-11-02","address":{"postal_code":"1015CJ","country_code":"NL"}}},"KycMatchRequest":{"type":"object","additionalProperties":false,"required":["phone_number","identity"],"properties":{"phone_number":{"type":"string","minLength":1,"description":"The phone number whose owner you want to check, in E.164 format, which is a leading `+`, the country calling code, then the national number."},"identity":{"allOf":[{"$ref":"#/components/schemas/KycIdentity"}],"x-sensitive":true}},"example":{"phone_number":"+31612345678","identity":{"given_name":"Aisha","family_name":"Khan","birthdate":"1985-11-02","address":{"postal_code":"1015CJ","country_code":"NL"}}}},"KycMatchResult":{"type":"string","minLength":1,"enum":["true","false","not_available"],"description":"Whether one attribute matched the operator's record.\n\n- `true`: a full match.\n- `false`: a match the operator rejected, and it carries a `score` saying\n  how close it was.\n- `not_available`: the operator holds nothing to compare that\n  attribute against. This is a different answer from a rejection and\n  should not be treated as one.\n\nA number outside supported coverage causes the request to fail instead of\nreturning this value.\n","example":"true"},"KycAttributeMatch":{"type":"object","additionalProperties":false,"description":"How one attribute you sent compared with the operator's record.\n","required":["result"],"properties":{"result":{"allOf":[{"$ref":"#/components/schemas/KycMatchResult"}],"readOnly":true},"score":{"type":"integer","minimum":0,"maximum":99,"readOnly":true,"description":"How close the value you sent came to the operator's, from 0 (nothing in common) to 99 (a near miss). Present only when `result` is `false`, which is what lets you tell a misspelling from a different person entirely. A `true` result is a full match and needs no score.\n"}},"example":{"result":"false","score":80}},"KycAddressMatch":{"type":"object","additionalProperties":false,"description":"How the address you sent compared with the operator's record.\n\nOperators differ in how finely they answer. Most compare each part separately\nand you get `street_name`, `locality` and the rest; some compare the address as\na whole and you get `overall` instead. Exactly one of the two forms is present,\nso check for `overall` first and fall back to the parts.\n","properties":{"overall":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true,"description":"The address judged as a whole. Present only when the operator does not break it down."},"street_name":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true},"street_number":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true},"unit_number":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true},"locality":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true},"region":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true},"postal_code":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true},"country_code":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true}},"example":{"postal_code":{"result":"true"},"country_code":{"result":"true"}}},"KycIdentityMatch":{"type":"object","additionalProperties":false,"description":"One block per attribute you sent, and nothing for attributes you did not. An attribute you sent that the operator cannot answer about comes back as `not_available` rather than being left out, so you can always tell `not asked` from `asked, no answer`.\n","properties":{"given_name":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true},"middle_name":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true},"family_name":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true},"full_name":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true,"description":"The name judged as a whole. Present instead of the individual name parts when the operator does not break them down."},"birthdate":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true},"email":{"allOf":[{"$ref":"#/components/schemas/KycAttributeMatch"}],"readOnly":true},"address":{"allOf":[{"$ref":"#/components/schemas/KycAddressMatch"}],"readOnly":true}},"example":{"given_name":{"result":"true"},"family_name":{"result":"true"},"birthdate":{"result":"false","score":50}}},"KycMatch":{"type":"object","additionalProperties":false,"description":"How the identity you sent compares with what the number's mobile operator holds\non file for its subscriber.\n\nRead each attribute independently. The response has no overall verdict because\nyou decide how many matching attributes your use case requires.\n","required":["phone_number","identity"],"properties":{"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"The number that was checked, exactly as you sent it."},"identity":{"allOf":[{"$ref":"#/components/schemas/KycIdentityMatch"}],"readOnly":true,"x-sensitive":true}},"example":{"phone_number":"+31612345678","identity":{"given_name":{"result":"true"},"family_name":{"result":"true"},"birthdate":{"result":"false","score":50},"address":{"postal_code":{"result":"true"},"country_code":{"result":"true"}}}}},"VerificationChannel":{"type":"string","minLength":1,"x-extensible-enum":["email","sms","whatsapp","telegram","voice"],"description":"The channel a passcode is delivered over. Open enum: new channels may be added over time, so treat any unrecognized value as a future channel rather than an error."},"VerificationID":{"type":"string","minLength":1,"pattern":"^vrf_[0-9a-hjkmnp-tv-z]{26}$","example":"vrf_01krdgeqcxet5s7t44vh8rt9mg"},"VerificationTerminalReason":{"type":"string","minLength":1,"x-extensible-enum":["attempts_exhausted","ttl_elapsed","undeliverable"],"description":"Why a verification session reached its final state without succeeding: `attempts_exhausted` (too many incorrect passcodes), `ttl_elapsed` (the time window elapsed before a correct passcode), or `undeliverable` (no planned channel could deliver a passcode, so the recipient never had one to submit). Open enum: new reasons may be added over time, so treat any unrecognized value as a future reason rather than an error."},"VerificationTo":{"type":"object","additionalProperties":false,"minProperties":1,"description":"The recipient to verify. Provide an `email`, a `phone_number`, or both; at least one is required. The addresses also identify the verification: a check must supply exactly the set used on the create call, so a verification created with both addresses is not found by either one alone.\n","properties":{"email":{"type":"string","minLength":1,"format":"email","description":"The recipient's email address. Case does not matter; the address is lowercased before use.","example":"user@example.com"},"phone_number":{"type":"string","minLength":1,"description":"The recipient's phone number in E.164 format, with the leading `+` and country code (for example `+15551234567`). A number in any other format is rejected as an invalid recipient (`422`).","example":"+15551234567"}}},"VerificationChannelEntry":{"type":"object","additionalProperties":false,"required":["channel"],"properties":{"channel":{"$ref":"#/components/schemas/VerificationChannel"}}},"Money":{"type":"object","additionalProperties":false,"required":["amount","currency_code"],"properties":{"amount":{"type":"string","minLength":1,"description":"Decimal amount as a string, in major currency units.","example":"0.00995"},"currency_code":{"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code.","example":"USD"}}},"VerificationAttemptFailureReason":{"type":"string","minLength":1,"x-extensible-enum":["carrier_rejected","hard_bounce","soft_bounce","undelivered","channel_unavailable","channel_disabled","channel_restricted","delivery_timeout","not_billable"],"description":"Why a passcode send did not deliver:\n\n- `carrier_rejected`: The SMS carrier rejected the send.\n- `hard_bounce`: The email permanently bounced.\n- `soft_bounce`: The email temporarily bounced, such as when a mailbox is full.\n- `undelivered`: The channel reported a generic delivery failure.\n- `channel_unavailable`: The channel could not be used, so the verification\n  moved to the next channel.\n- `channel_disabled`: Sending on the channel is temporarily disabled, so the\n  verification moved to the next channel.\n- `channel_restricted`: The channel does not carry passcodes to this\n  destination country. The verification moves to the next channel enabled\n  there, or fails when the country has no other.\n- `delivery_timeout`: No delivery confirmation arrived before the channel's\n  timeout, so the verification moved to the next channel.\n- `not_billable`: The send could not be charged, so it was never handed to the\n  channel. Usually the workspace balance is too low to cover it. Topping up\n  the balance is what clears this.\n\nNew reasons may be added over time. Treat unrecognized values as reasons added\nlater rather than errors."},"VerificationAttemptSenderClass":{"type":"string","minLength":1,"x-extensible-enum":["shared","custom_domain","system","owned","free_form","managed"],"description":"How the sender was provisioned. Open enum: treat any unrecognized value as a future class. For email: `shared` (the shared sender) or `custom_domain`; for SMS: `system`, `owned`, or `free_form`; for WhatsApp: `managed` or `owned`."},"WhatsAppTemplateCategory":{"type":"string","minLength":1,"x-extensible-enum":["authentication","utility","marketing"],"description":"Meta's content classification for a template.\n\n- `authentication`: delivers one-time passcodes.\n- `utility`: delivers transaction-triggered updates (receipts, order status).\n- `marketing`: carries promotional content.\n\nThe category determines the sender number and price. This is an open enum.\nAccept unrecognized values.\n"},"VerificationAttempt":{"type":"object","additionalProperties":false,"required":["channel","delivery_status","created_at","updated_at"],"properties":{"channel":{"$ref":"#/components/schemas/VerificationChannel"},"delivery_status":{"type":"string","minLength":1,"enum":["queued","sent","delivered","undelivered","unknown"],"x-enum-varnames":["VerificationAttemptDeliveryStatusQueued","VerificationAttemptDeliveryStatusSent","VerificationAttemptDeliveryStatusDelivered","VerificationAttemptDeliveryStatusUndelivered","VerificationAttemptDeliveryStatusUnknown"],"readOnly":true,"description":"The delivery state of this send. Closed enum. v1 emits `sent` (accepted by the channel), `delivered` (confirmed delivered), and `undelivered` (a code was sent but a delivery failure was recorded); `queued` and `unknown` are reserved and not emitted in v1."},"reason":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VerificationAttemptFailureReason"},{"type":"null"}],"description":"Why the send did not deliver, or `null` when it was accepted/delivered. See the enum for the emitted values."},"origin":{"type":["string","null"],"x-extensible-enum":["create","resend","resolution_failover","delivery_failover","manual"],"readOnly":true,"description":"Why this send exists:\n\n- `create`: First send for a verification.\n- `resend`: Fresh passcode for a re-created verification.\n- `resolution_failover`: The previous channel could not send.\n- `delivery_failover`: The previous send failed to deliver.\n- `manual`: The recipient reported not receiving the passcode and asked\n  to move to the next channel.\n\n`null` on sends recorded before this field existed. Treat unrecognized\nvalues as origins added later."},"origin_from_channel":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VerificationChannel"},{"type":"null"}],"description":"The channel this send moved on from. For `resolution_failover` and `delivery_failover`, this is the channel that failed. For `manual`, this is the channel on which the passcode was reported as not received. The value is `null` for a first send, a resend, or a send recorded before this field existed. See the enum for the values it can take."},"error":{"type":["string","null"],"readOnly":true,"description":"The channel/provider's own diagnostic text for a failed send (for example, a carrier's `absent subscriber`), or `null`. Free-form and provider-specific; display this text without parsing it."},"resolved_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the delivery outcome (delivered or failed) was recorded, or `null` while the send's delivery is still undetermined."},"verified_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the passcode from this send matched a successful check and completed the verification. Set on the successful send only; `null` on every other send."},"sender":{"type":["string","null"],"readOnly":true,"description":"The originator this send was dispatched from: the sender ID, phone number, or email address the recipient saw. `null` when the send was not composed (a failed failover rung) or predates its capture."},"sender_class":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VerificationAttemptSenderClass"},{"type":"null"}],"description":"How this send's sender was provisioned, or `null` when the send was not composed or predates its capture."},"segments":{"readOnly":true,"description":"For an SMS send, the segment breakdown of the message body (segment count drives billing). Omitted for email, WhatsApp, and Telegram sends.","allOf":[{"$ref":"#/components/schemas/SMSSegments"}]},"destination_country":{"readOnly":true,"description":"For an SMS send, the country the destination number resolved to. Omitted for email, WhatsApp, and Telegram sends.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"template_language":{"type":["string","null"],"readOnly":true,"description":"The language the message rendered in, on the channels that send a template. Not always the language requested: a tag with no translation falls back to its base language, and then to English, so this is where you confirm what a verification actually sent. Null on a channel that sends no template."},"template_category":{"readOnly":true,"description":"For a WhatsApp send, the category of the template used.","allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateCategory"}]},"cost":{"readOnly":true,"description":"The amount charged for this send. Omitted when no charge was recorded: a free channel, a zero-price rate, or a send rejected before it was billed.","allOf":[{"$ref":"#/components/schemas/Money"}]},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this send was made."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this send was last updated. This can change for reasons other than delivery: use `resolved_at` for when the delivery outcome was recorded and `verified_at` for when the send converted the verification."}}},"Verification":{"allOf":[{"type":"object","required":["id","status","to","channels","expires_at","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VerificationID"},"status":{"type":"string","minLength":1,"enum":["pending","verified","failed","expired","canceled","blocked"],"readOnly":true,"description":"The verification's current state:\n\n- `pending`: Awaiting a correct passcode.\n- `verified`: A correct passcode was submitted.\n- `failed`: The verification cannot be completed. Either too many\n  incorrect passcodes were submitted, or no planned channel could\n  deliver one. Read `reason` to tell those apart.\n- `expired`: The validity window elapsed before a correct passcode.\n- `canceled`: The verification was canceled before completion.\n- `blocked`: A fraud or abuse control stopped the verification."},"reason":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VerificationTerminalReason"},{"type":"null"}],"description":"Why the verification reached its final state, or `null` while `pending` and once `verified`. See the enum for the values it can take."},"to":{"readOnly":true,"$ref":"#/components/schemas/VerificationTo"},"channels":{"type":"array","readOnly":true,"minItems":1,"description":"The channels this verification uses to deliver the passcode, in attempt order: the first entry is tried first and later entries are fallbacks. An email recipient is verified over email; a phone recipient is verified over the phone channels enabled for its destination country, in the order that country's configuration sets.","items":{"$ref":"#/components/schemas/VerificationChannelEntry"}},"last_channel":{"type":["string","null"],"x-extensible-enum":["email","sms","whatsapp","telegram","voice"],"readOnly":true,"description":"The channel the most recent passcode was sent on, or `null` before the first send. Open enum; new channels may be added over time, so treat any unrecognized value as a future channel rather than an error."},"metadata":{"type":"object","additionalProperties":true,"readOnly":true,"description":"The key/value pairs attached when the verification was created."},"expires_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the verification expires if no correct passcode is submitted first. After this time its status reports `expired`."},"verified_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the verification was completed, or `null` if it is not yet verified."}}},{"$ref":"#/components/schemas/Timestamps"}]},"VerificationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of verification objects.","items":{"$ref":"#/components/schemas/Verification"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"VerificationOptions":{"type":"object","additionalProperties":false,"description":"Per-request overrides applied to this verification only.","properties":{"code_length":{"type":"integer","minimum":4,"maximum":8,"description":"Passcode length for this verification. Omit to use the configured length."},"channels":{"type":"array","description":"Reorder or narrow the delivery channels for this request. List channel names in the order to try them; a channel you omit is not used for this request, and a channel not already enabled for the recipient is ignored. A list that leaves no usable channel fails the request with `422`. Omit the field to use the configured order.","items":{"$ref":"#/components/schemas/VerificationChannel"}},"language":{"$ref":"#/components/schemas/LanguageTag","description":"Which of the built-in message translations to send. It applies to SMS, email and WhatsApp, and has no effect on a Telegram verification. A tag with no translation of its own falls back to its base language, and then to English. The attempt's `template_language` reports which translation was used. The tag is fixed for the verification, so a resend arrives in the same language as the first message.\n\nOmit it and the language is read from the recipient phone number's country, so a French number gets French without you asking. Set it to override that. A verification with no phone number, and one whose country has no translation here, sends English.\n\nSupported: en, ar, bg, cs, da, de, el, es, fi, fr, he, hi, hr, hu, id, it, ja, ko, lt, lv, mk, mn, ms, nb-NO, nl, no, pl, pt, ro, ru, sk, sl, sr, sv, th, tr, uk, vi, zh, zh-TW. Every one of them is available on SMS and email. Mongolian (mn) is the one WhatsApp cannot carry, so a WhatsApp passcode falls back to English for it."}}},"VerificationCreateRequest":{"type":"object","additionalProperties":false,"required":["to"],"properties":{"to":{"$ref":"#/components/schemas/VerificationTo"},"options":{"$ref":"#/components/schemas/VerificationOptions"},"metadata":{"type":"object","additionalProperties":true,"description":"Optional key/value pairs to attach to the verification, for example a correlation id. Returned on the verification."}},"example":{"to":{"phone_number":"+15551234567"},"metadata":{"correlation_id":"signup-7f3a"}}},"VerificationCheckRequest":{"type":"object","additionalProperties":false,"required":["to","code"],"properties":{"to":{"$ref":"#/components/schemas/VerificationTo"},"code":{"type":"string","minLength":4,"maxLength":12,"description":"The passcode the recipient received. Passcodes are numeric; submit the digits exactly as delivered. An incorrect value is a normal `200` outcome with `success: false`. It does not return an error.","example":"123456"}},"example":{"to":{"phone_number":"+15551234567"},"code":"123456"}},"VerificationCheckResult":{"type":"object","additionalProperties":false,"required":["success","verification"],"properties":{"success":{"type":"boolean","readOnly":true,"description":"Whether the submitted passcode verified this verification. `true` means the passcode was correct and the verification is now complete; `false` means it did not verify, and `reason` says why. A verification that has already reached a final state is no longer checkable and returns `404`."},"reason":{"type":["string","null"],"x-extensible-enum":["incorrect_code","expired","attempts_exhausted"],"readOnly":true,"description":"Why the check did not succeed:\n\n- `incorrect_code`: The passcode was wrong and attempts remain.\n- `expired`: The validity window elapsed.\n- `attempts_exhausted`: Too many incorrect attempts were submitted.\n\n`null` when `success` is `true`. Treat unrecognized values as reasons added\nlater."},"verification":{"$ref":"#/components/schemas/Verification"},"attempts_remaining":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"The number of check attempts left while the verification is still pending, or `null` once it has reached a final state."}}},"VerificationNextChannelRequest":{"type":"object","additionalProperties":false,"required":["to"],"properties":{"to":{"$ref":"#/components/schemas/VerificationTo"}},"example":{"to":{"phone_number":"+15551234567"}}},"VerificationResolvedSettings":{"type":"object","additionalProperties":false,"description":"The passcode policy currently in effect for a verification.","required":["code_length","code_type","ttl_seconds","max_attempts","resend_cooldown_seconds"],"properties":{"code_length":{"type":"integer","minimum":4,"maximum":8,"description":"Number of characters in a generated passcode."},"code_type":{"type":"string","minLength":1,"enum":["numeric","alphanumeric"],"x-enum-varnames":["VerificationResolvedSettingsCodeTypeNumeric","VerificationResolvedSettingsCodeTypeAlphanumeric"],"description":"Character set of a generated passcode. Only numeric is supported today."},"ttl_seconds":{"type":"integer","minimum":1,"maximum":59940,"description":"How long a verification stays valid, in seconds."},"max_attempts":{"type":"integer","minimum":1,"maximum":10,"description":"How many incorrect passcode submissions are allowed before the verification fails."},"resend_cooldown_seconds":{"type":"integer","minimum":0,"maximum":3600,"description":"Minimum wait between passcode sends to the same recipient, in seconds."}}},"VerificationCountryChannel":{"type":"object","additionalProperties":false,"required":["channel","enabled"],"description":"A channel available for this country, in attempt order.","properties":{"channel":{"$ref":"#/components/schemas/VerificationChannel"},"enabled":{"type":"boolean","readOnly":true,"description":"Whether this channel is currently used for this country."},"overridden":{"type":"boolean","readOnly":true,"description":"`true` when this configuration sets the channel's enabled state. `false` when it follows the default."}}},"VerificationDefaultsCountry":{"type":"object","additionalProperties":false,"required":["country","channels"],"description":"The default delivery plan for one country, before any of your changes.","properties":{"country":{"$ref":"#/components/schemas/CountryCode"},"channels":{"type":"array","description":"The channels available for this country, in default attempt order; a channel that is not enabled is off by default.","items":{"$ref":"#/components/schemas/VerificationCountryChannel"}}}},"VerificationDefaults":{"type":"object","additionalProperties":false,"required":["settings","fallback","countries"],"properties":{"settings":{"$ref":"#/components/schemas/VerificationResolvedSettings","description":"The platform default passcode policy, inherited wherever a configuration does not override it."},"fallback":{"type":"array","description":"The ordered channels tried for a phone number in a country that has no per-country entry below.","items":{"$ref":"#/components/schemas/VerificationChannel"}},"countries":{"type":"array","description":"Per-country channel defaults, ordered by country code. A country not listed here uses the fallback order.","items":{"$ref":"#/components/schemas/VerificationDefaultsCountry"}}}},"VerificationConfigurationID":{"type":"string","minLength":1,"pattern":"^vfc_[0-9a-hjkmnp-tv-z]{26}$","example":"vfc_01krdgeqcxet5s7t44vh8rt9mg"},"VerifySharedSender":{"type":"string","minLength":1,"x-extensible-enum":["bird_verify","authifly"],"description":"A Bird-managed shared sender identity. The choice decides what each channel\nsends as: the email from-address, the SMS sender ID, and the WhatsApp\nbusiness number.\n\n- `bird_verify`: the identity a channel uses when it has no sender choice.\n- `authifly`: a verification identity that names no platform vendor.\n\nWhere a destination country requires a short code or a local number, SMS\nshows that number rather than a branded sender ID, and both identities use\nthe same one. Only the email body names the identity; the SMS and WhatsApp\nbodies read the same under either.\n\nThe set is open because new identities can appear without a client update:\nwhen reading, accept values beyond those listed. Writes accept only the\nidentities listed here.\n"},"DomainID":{"type":"string","minLength":1,"pattern":"^dom_[0-9a-hjkmnp-tv-z]{26}$","example":"dom_01krdgeqcxet5s7t44vh8rt9mg"},"VerificationEmailSenderForm":{"type":"object","description":"The two forms an email sender can take, shared by every tier that stores or reports one. Name a Bird-managed identity in `shared_sender` and nothing else, or give both halves of an address of your own.","properties":{"shared_sender":{"$ref":"#/components/schemas/VerifySharedSender"},"domain":{"$ref":"#/components/schemas/DomainID","description":"A verified sending domain in your workspace."},"local_part":{"type":"string","minLength":1,"description":"The part of the from-address before the @ sign."}},"minProperties":1,"dependentRequired":{"domain":["local_part"],"local_part":["domain"]},"dependentSchemas":{"shared_sender":{"propertyNames":{"enum":["shared_sender"]}}}},"VerificationResolvedEmailSender":{"unevaluatedProperties":false,"description":"The email sender identity in effect. `shared_sender` names the Bird-managed identity when one applies, and the custom-sender fields are then absent; `domain` and `local_part` give the configuration's own sender instead.","allOf":[{"$ref":"#/components/schemas/VerificationEmailSenderForm"}]},"VerificationSharedSenderPin":{"type":"object","additionalProperties":false,"required":["shared_sender"],"description":"The sender choice for a channel that offers Bird-managed identities only: name the shared sender the channel sends as.","properties":{"shared_sender":{"$ref":"#/components/schemas/VerifySharedSender"}}},"VerificationResolvedSenders":{"type":"object","additionalProperties":false,"required":["email","sms","whatsapp"],"description":"The sender identity in effect for each channel that carries one. Each reports whether it uses a Bird-managed shared sender or one you configured. Two channels are absent: telegram, whose codes come from Telegram's own verified account, and voice, whose calls present a number chosen for the destination country rather than one you select.","properties":{"email":{"$ref":"#/components/schemas/VerificationResolvedEmailSender"},"sms":{"$ref":"#/components/schemas/VerificationSharedSenderPin","description":"The shared sender in effect for verification texts."},"whatsapp":{"$ref":"#/components/schemas/VerificationSharedSenderPin","description":"The shared sender in effect for WhatsApp verification messages."}}},"VerificationConfigurationSettings":{"type":"object","additionalProperties":false,"description":"Overrides for the verification passcode policy. Every field is optional; a field you do not set inherits the platform default.","properties":{"code_length":{"type":"integer","minimum":4,"maximum":8,"description":"Number of characters in the passcode."},"code_type":{"type":"string","enum":["numeric","alphanumeric"],"x-enum-varnames":["VerificationConfigurationSettingsCodeTypeNumeric","VerificationConfigurationSettingsCodeTypeAlphanumeric"],"description":"Passcode character set. Only numeric passcodes are currently issued."},"ttl_seconds":{"type":"integer","minimum":1,"maximum":59940,"description":"How long a verification stays open before it expires, in seconds."},"max_attempts":{"type":"integer","minimum":1,"maximum":10,"description":"How many incorrect passcode submissions are allowed before the verification fails."},"resend_cooldown_seconds":{"type":"integer","minimum":0,"maximum":3600,"description":"Minimum wait between passcode sends to the same recipient, in seconds."}}},"VerificationConfigurationOverrides":{"type":"object","additionalProperties":false,"readOnly":true,"required":["settings"],"description":"Exactly what this configuration overrides. A field absent here follows the platform default, including future changes to it; a field present is pinned to your value.","properties":{"settings":{"$ref":"#/components/schemas/VerificationConfigurationSettings"}}},"VerificationConfiguration":{"allOf":[{"type":"object","required":["id","name","settings","senders","overrides"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VerificationConfigurationID"},"name":{"type":"string","minLength":1,"description":"Human-readable label for the configuration, for example `signup` or `login`."},"settings":{"$ref":"#/components/schemas/VerificationResolvedSettings","description":"The passcode policy in effect for this configuration, whether a value was set here or inherited from the platform default."},"senders":{"$ref":"#/components/schemas/VerificationResolvedSenders"},"overrides":{"$ref":"#/components/schemas/VerificationConfigurationOverrides"},"archived_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the configuration was archived, or `null` while it is active. An archived configuration cannot be used for new verifications and is excluded from the configuration list, but its past verifications remain readable."}}},{"$ref":"#/components/schemas/Timestamps"}]},"VerificationConfigurationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of verification configuration objects.","items":{"$ref":"#/components/schemas/VerificationConfiguration"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"VerificationEmailSender":{"unevaluatedProperties":false,"description":"The sending identity for verification emails, in exactly one of two forms: name a Bird-managed shared sender in `shared_sender`, or compose a `from` address of your own as `{local_part}@{domain}`, for example `verify@acme.com`.","allOf":[{"$ref":"#/components/schemas/VerificationEmailSenderForm"}]},"VerificationSenders":{"type":"object","additionalProperties":false,"description":"Sender identities used for verification messages, per channel. A channel you do not set uses the default shared sender.","properties":{"email":{"$ref":"#/components/schemas/VerificationEmailSender"},"sms":{"$ref":"#/components/schemas/VerificationSharedSenderPin"},"whatsapp":{"$ref":"#/components/schemas/VerificationSharedSenderPin"}}},"VerificationConfigurationCreateRequest":{"type":"object","additionalProperties":false,"required":["name"],"description":"Fields for a new verification configuration.","properties":{"name":{"type":"string","minLength":1,"description":"Human-readable label for the configuration, for example `signup` or `login`."},"settings":{"$ref":"#/components/schemas/VerificationConfigurationSettings"},"senders":{"$ref":"#/components/schemas/VerificationSenders"}}},"VerificationConfigurationSettingsUpdate":{"type":"object","additionalProperties":false,"description":"Changes to the passcode policy. A field you omit is left as it is; a field you set to `null` returns to the platform default; a value pins that field.","properties":{"code_length":{"type":["integer","null"],"minimum":4,"maximum":8,"description":"Number of characters in the passcode, or `null` to return to the platform default."},"code_type":{"type":["string","null"],"enum":["numeric","alphanumeric",null],"x-enum-varnames":["VerificationConfigurationSettingsUpdateCodeTypeNumeric","VerificationConfigurationSettingsUpdateCodeTypeAlphanumeric"],"description":"Passcode character set, or `null` to return to the platform default. Only numeric passcodes are currently issued."},"ttl_seconds":{"type":["integer","null"],"minimum":1,"maximum":59940,"description":"How long a verification stays open before it expires, in seconds, or `null` to return to the platform default."},"max_attempts":{"type":["integer","null"],"minimum":1,"maximum":10,"description":"How many incorrect passcode submissions are allowed, or `null` to return to the platform default."},"resend_cooldown_seconds":{"type":["integer","null"],"minimum":0,"maximum":3600,"description":"Minimum wait between passcode sends to the same recipient, in seconds, or `null` to return to the platform default."}}},"VerificationSendersUpdate":{"type":"object","additionalProperties":false,"description":"Changes to the default senders. A channel you omit is left as it is; a channel you set to `null` returns to the default shared sender.","properties":{"email":{"oneOf":[{"$ref":"#/components/schemas/VerificationEmailSender"},{"type":"null"}],"description":"The sending identity for verification emails, or `null` to return to the default shared sender."},"sms":{"oneOf":[{"$ref":"#/components/schemas/VerificationSharedSenderPin"},{"type":"null"}],"description":"The shared sender for verification texts, or `null` to return to the default shared sender."},"whatsapp":{"oneOf":[{"$ref":"#/components/schemas/VerificationSharedSenderPin"},{"type":"null"}],"description":"The shared sender for WhatsApp verification messages, or `null` to return to the default shared sender."}}},"VerificationConfigurationUpdateRequest":{"type":"object","additionalProperties":false,"description":"Fields to update. A field you omit is left unchanged. Inside settings and senders, setting a field to `null` returns it to the platform default. A value pins the field. Each field changes independently of the others.","properties":{"name":{"type":"string","minLength":1,"description":"New label for the configuration."},"settings":{"$ref":"#/components/schemas/VerificationConfigurationSettingsUpdate"},"senders":{"$ref":"#/components/schemas/VerificationSendersUpdate"},"archived":{"type":"boolean","description":"Set `true` to archive the configuration (it stops governing new verifications), or `false` to restore it."}}},"VerificationCountrySenderChoice":{"unevaluatedProperties":false,"description":"The email sender choice stored for this country: either a Bird-managed shared sender, or a sender of your own.","allOf":[{"$ref":"#/components/schemas/VerificationEmailSenderForm"}]},"VerificationCountryPlanSenders":{"type":"object","additionalProperties":false,"readOnly":true,"description":"This country's stored sender choices, verbatim: per channel, either a Bird-managed shared sender or your own. A channel absent here inherits the configuration's default sender; the whole key is absent when the country stores no sender choices at all.","properties":{"email":{"$ref":"#/components/schemas/VerificationCountrySenderChoice"},"sms":{"$ref":"#/components/schemas/VerificationSharedSenderPin"},"whatsapp":{"$ref":"#/components/schemas/VerificationSharedSenderPin"}}},"VerificationCountryPlan":{"type":"object","additionalProperties":false,"required":["country","enabled","enabled_overridden","channels","order_overridden"],"description":"The delivery plan in effect for one country, whether it comes from your settings or the default.","properties":{"country":{"$ref":"#/components/schemas/CountryCode","description":"ISO 3166-1 alpha-2 country code this plan applies to."},"enabled":{"type":"boolean","readOnly":true,"description":"Whether verification is on for this country. `false` when the country is off or no channel is enabled."},"enabled_overridden":{"type":"boolean","readOnly":true,"description":"`true` when this configuration sets whether the country is enabled. `false` when it follows the default, which enables the country whenever a channel is enabled."},"channels":{"type":"array","readOnly":true,"description":"Every channel available for this country, in the order they are tried (the first is tried first). A channel that is not enabled keeps its place in the order but is skipped.","items":{"$ref":"#/components/schemas/VerificationCountryChannel"}},"order_overridden":{"type":"boolean","readOnly":true,"description":"`true` when this configuration sets the channel order. `false` when the order follows the default."},"senders":{"$ref":"#/components/schemas/VerificationCountryPlanSenders","description":"This country's stored sender choices, present only when it stores at least one."}}},"VerificationCountryPlanList":{"type":"object","additionalProperties":false,"required":["data","fallback"],"properties":{"data":{"type":"array","description":"Plans ordered by country code for every country with a shared configuration or one of your routes.","items":{"$ref":"#/components/schemas/VerificationCountryPlan"}},"fallback":{"type":"array","readOnly":true,"description":"The ordered channels used for any country not listed above.","items":{"$ref":"#/components/schemas/VerificationChannel"}}}},"VerificationCountryChannelUpdate":{"type":["object","null"],"additionalProperties":false,"required":["enabled"],"description":"Whether to use this channel for this country, or `null` to return it to the default.","properties":{"enabled":{"type":"boolean","description":"Whether this channel is used to deliver passcodes in this country."}}},"VerificationCountryChannelsUpdate":{"type":"object","additionalProperties":false,"description":"Per-channel settings, keyed by channel. A channel you omit is left unchanged; set a channel to `null` to return it to the default.","properties":{"email":{"$ref":"#/components/schemas/VerificationCountryChannelUpdate"},"sms":{"$ref":"#/components/schemas/VerificationCountryChannelUpdate"},"whatsapp":{"$ref":"#/components/schemas/VerificationCountryChannelUpdate"},"telegram":{"$ref":"#/components/schemas/VerificationCountryChannelUpdate"},"voice":{"$ref":"#/components/schemas/VerificationCountryChannelUpdate"}}},"VerificationCountryEmailSenderUpdate":{"unevaluatedProperties":false,"description":"The email sender choice for one country. Name a Bird-managed identity in `shared_sender` with no other fields, or provide your own domain and `local_part`. Set the entry to `null` to inherit the configuration's default sender; omitting it leaves the current choice unchanged.","allOf":[{"$ref":"#/components/schemas/VerificationEmailSenderForm"}]},"VerificationCountrySendersUpdate":{"type":"object","additionalProperties":false,"description":"Sender choices for this country, per channel. For each channel: `null` inherits your configuration default, a Bird-managed identity named in `shared_sender` pins the shared sender for this country, or (email only) provide the channel's own sender fields. Each entry replaces that channel's choice entirely.","properties":{"email":{"oneOf":[{"$ref":"#/components/schemas/VerificationCountryEmailSenderUpdate"},{"type":"null"}],"description":"The email sender choice for this country, or `null` to inherit your configuration's default sender."},"sms":{"oneOf":[{"$ref":"#/components/schemas/VerificationSharedSenderPin"},{"type":"null"}],"description":"The shared sender choice for verification texts in this country, or `null` to inherit your configuration's default sender."},"whatsapp":{"oneOf":[{"$ref":"#/components/schemas/VerificationSharedSenderPin"},{"type":"null"}],"description":"The shared sender choice for WhatsApp verification messages in this country, or `null` to inherit your configuration's default sender."}}},"VerificationCountryUpdateRequest":{"type":"object","additionalProperties":false,"description":"The fields to change for this country. A field you omit is left as it is. Whether the country is on, each channel's on/off, the channel order, and the per-channel senders each change independently.","properties":{"enabled":{"type":["boolean","null"],"description":"Turn verification on or off for this country. `null` returns it to the default (on whenever at least one channel is enabled)."},"channels":{"$ref":"#/components/schemas/VerificationCountryChannelsUpdate"},"order":{"type":["array","null"],"uniqueItems":true,"description":"The order channels are tried in, most-preferred first. List every channel available in the country exactly once (a permutation); an empty array is rejected. `null` returns it to the default order.","items":{"$ref":"#/components/schemas/VerificationChannel"}},"senders":{"$ref":"#/components/schemas/VerificationCountrySendersUpdate"}}},"VerifyStatsPeriod":{"type":"object","additionalProperties":false,"description":"The window the server actually computed against, echoed back from the request.","required":["from","to"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window the response covers, a calendar day (`YYYY-MM-DD`) or an RFC 3339 instant, matching the form of the request's `from`.\n","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window the response covers, a calendar day (`YYYY-MM-DD`) or an RFC 3339 instant, matching the form of the request's `to`.\n","example":"2026-05-25"},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The approximate time through which this response includes events. More recent events may not be reflected yet. `null` when the freshness boundary cannot be read, or when the operation has none to report; the operation's own description says which applies to it.","example":"2026-05-25T14:03:10Z"}}},"VerifyStatsFunnel":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Verification-session lifecycle counts and derived rates for the containing row. Counts use distinct verification sessions and are attributed by event time. `created_sessions` is the starting point, `verified` is successful passcode entry, and `failed` and `expired` are terminal unsuccessful outcomes. Each rate is a fraction from 0 to 1 and is `null` when its denominator is zero.","required":["created_sessions","verified","failed","expired","conversion_rate","drop_off_rate"],"properties":{"created_sessions":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions created in scope. This is the denominator for `conversion_rate` and `drop_off_rate`.","example":5230},"verified":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions where the recipient entered a valid passcode.","example":4780},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions that ended because the recipient exhausted their passcode attempts without success.","example":210},"expired":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions that ended because their validity window elapsed before the recipient verified.","example":240},"conversion_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of created sessions that were verified, computed as `verified / created_sessions`. `null` when no sessions were created in scope.\n","example":0.9139},"drop_off_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of created sessions that ended without success, computed as `(failed + expired) / created_sessions`. `null` when no sessions were created in scope.\n","example":0.0861}}},"VerifyDeliveryStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Passcode send-attempt counts and derived rates for the containing row. Counts use distinct send attempts. `sent` was handed to the channel, `delivered` was confirmed delivered, `failed` could not be delivered, and `rejected` was refused before a send attempt. Each rate is `null` when its denominator is zero.","required":["sent","rejected","delivered","failed","delivery_rate","avg_send_attempts"],"properties":{"sent":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts handed to a delivery channel. This is the denominator for `delivery_rate`.","example":5410},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts refused before a send attempt was made, for example by a channel configuration or destination restriction.","example":35},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts confirmed as delivered.","example":5280},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts that could not be delivered.","example":130},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of send attempts that were delivered, computed as `delivered / sent`. `rejected` counts in neither term. `null` when nothing was sent in scope.\n","example":0.976},"avg_send_attempts":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Average number of passcode send attempts made per verification session, computed as `attempts / verifications`. `null` when no verification session in scope had a send attempt.\n","example":1.14}}},"VerifyStatsQuantiles":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Approximate latency percentiles in milliseconds. Each value is `null` when no qualifying event contributed a measurement in scope.","required":["p50","p95","p99"],"properties":{"p50":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"50th percentile latency in milliseconds. `null` when no qualifying event contributed a measurement in scope.","example":4200},"p95":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"95th percentile latency in milliseconds. `null` when no qualifying event contributed a measurement in scope.","example":11800},"p99":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"99th percentile latency in milliseconds. `null` when no qualifying event contributed a measurement in scope.","example":25600}}},"VerifyStatsLatency":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Latency percentiles (p50, p95, p99) in milliseconds, computed across the whole scope. Two families are reported:\n\n- `time_to_verify`: Time from a verification session being created to the recipient successfully entering the passcode.\n- `otp_delivery`: Time from a passcode send attempt to delivery confirmation.\n\nMeasurements belong to the window in which they completed. Their start window\ndoes not determine attribution. A percentile can therefore include a session\nor send attempt that started before the window and finished within it. Each\nfamily is `null` when no qualifying event contributed a measurement.\n","required":["time_to_verify","otp_delivery"],"properties":{"time_to_verify":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsQuantiles"}]},"otp_delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsQuantiles"}]}}},"VerifyStatsCost":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Average spend per verification session over the scope, in your workspace's billing currency.","properties":{"avg_cost_per_verification":{"readOnly":true,"description":"Average amount charged per verification session, computed as `sum(cost) / count(verification sessions)`. Omitted when no verification session occurred in scope.\n","allOf":[{"$ref":"#/components/schemas/Money"}]}}},"VerifyStatsComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Change in each headline metric from the preceding period to the requested period. A `*_pct_change` is the signed relative change in a count, computed as `(current - previous) / previous`. For example, `0.5` means 50% higher and `-0.2` means 20% lower. It is `null` when the preceding count was zero. A `*_rate_pp` is the signed difference between two rates expressed as fractions from 0 to 1. For example, `0.012` is an increase of 1.2 percentage points. It is `null` when either rate is undefined and ranges from `-1` to `1`.","required":["created_sessions_pct_change","verified_pct_change","failed_verifications_pct_change","expired_pct_change","sent_pct_change","delivered_pct_change","failed_deliveries_pct_change","rejected_pct_change","conversion_rate_pp","drop_off_rate_pp","delivery_rate_pp"],"properties":{"created_sessions_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in verification sessions created (`funnel.created_sessions`) versus the previous period, as a signed fraction. `null` when the previous period created none.","example":0.121},"verified_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in verified sessions (`funnel.verified`) versus the previous period, as a signed fraction. `null` when the previous period verified none.","example":0.144},"failed_verifications_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in failed verification sessions (`funnel.failed`, sessions that exhausted their passcode attempts) versus the previous period, as a signed fraction. `null` when the previous period had none.","example":-0.052},"expired_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in expired sessions (`funnel.expired`) versus the previous period, as a signed fraction. `null` when the previous period had none.","example":-0.018},"sent_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in passcode send attempts (`delivery.sent`) versus the previous period, as a signed fraction. `null` when the previous period sent none.","example":0.119},"delivered_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in delivered send attempts (`delivery.delivered`) versus the previous period, as a signed fraction. `null` when the previous period delivered none.","example":0.122},"failed_deliveries_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in failed send attempts (`delivery.failed`, attempts that could not be delivered) versus the previous period, as a signed fraction. `null` when the previous period had none.","example":-0.031},"rejected_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in rejected send attempts (`delivery.rejected`) versus the previous period, as a signed fraction. `null` when the previous period had none.","example":0.004},"conversion_rate_pp":{"type":["number","null"],"minimum":-1,"maximum":1,"readOnly":true,"description":"Signed difference between this period's and the previous period's conversion rate (`funnel.conversion_rate`), both fractions in [0,1] (multiply by 100 for percentage points). `null` when either period's conversion rate is undefined.","example":0.006},"drop_off_rate_pp":{"type":["number","null"],"minimum":-1,"maximum":1,"readOnly":true,"description":"Signed difference between this period's and the previous period's drop-off rate (`funnel.drop_off_rate`), both fractions in [0,1] (multiply by 100 for percentage points). `null` when either period's drop-off rate is undefined.","example":-0.006},"delivery_rate_pp":{"type":["number","null"],"minimum":-1,"maximum":1,"readOnly":true,"description":"Signed difference between this period's and the previous period's delivery rate (`delivery.delivery_rate`), both fractions in [0,1] (multiply by 100 for percentage points). `null` when either period's delivery rate is undefined.","example":0.004}}},"VerifyStatsComparison":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The same funnel and delivery statistics for the equal-length period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request.\n","required":["period","funnel","delivery","delta"],"properties":{"period":{"$ref":"#/components/schemas/VerifyStatsPeriod","description":"The preceding window these comparison figures cover, the equal-length window ending immediately before the requested start."},"funnel":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsFunnel"}]},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyDeliveryStats"}]},"delta":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsComparisonDelta"}]}}},"VerifyStatsSummary":{"type":"object","additionalProperties":false,"description":"Single-row aggregate across the full requested period, covering verification funnel counts, delivery counts, latency percentiles, and spend. Use this endpoint for KPI tiles and reporting; the daily and hourly endpoints carry the same counts per bucket.\n","required":["period","funnel","delivery","latency","cost"],"properties":{"period":{"$ref":"#/components/schemas/VerifyStatsPeriod","description":"The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"funnel":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsFunnel"}]},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyDeliveryStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsLatency"}]},"cost":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsCost"}]},"comparison":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsComparison"}]}}},"VerifyStatsSeriesPeriod":{"type":"object","additionalProperties":false,"description":"The window and bucket grain the response covers, echoed back from the request.","required":["from","to","grain"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window. A calendar day (`YYYY-MM-DD`) on the day grain, an RFC 3339 instant rounded to the hour on the hour grain.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window. A calendar day (`YYYY-MM-DD`) on the day grain, an RFC 3339 instant rounded to the hour on the hour grain.","example":"2026-05-31"},"grain":{"$ref":"#/components/schemas/StatsGrain","readOnly":true},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The approximate time through which this response includes events. More recent events may not be reflected yet. `null` when the freshness boundary is unavailable.","example":"2026-05-25T14:03:10Z"}}},"VerifyStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Verification lifecycle counts for one calendar-day or hourly bucket, attributed by event time. Rates, latency, and spend are whole-window aggregates reported by the summary and breakdown responses; they are not included per bucket.","required":["bucket","created_sessions","verified","failed","expired","sent","delivered"],"properties":{"bucket":{"type":"string","minLength":1,"readOnly":true,"description":"The day (`YYYY-MM-DD`) or hour (RFC 3339, on the hour) this point covers, matching the period's grain.","example":"2026-05-25"},"created_sessions":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions created in this bucket.","example":210},"verified":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions verified in this bucket.","example":192},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions that ended in this bucket because the recipient exhausted their passcode attempts.","example":8},"expired":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions that ended in this bucket because their validity window elapsed.","example":10},"sent":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts handed to a delivery channel in this bucket.","example":225},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts confirmed as delivered in this bucket.","example":219}}},"VerifyStatsSeries":{"type":"object","additionalProperties":false,"description":"Time-series stats payload. `period` echoes the range and bucket grain the server computed against; `data` is one row per bucket in chronological order.\n","required":["period","data"],"properties":{"period":{"$ref":"#/components/schemas/VerifyStatsSeriesPeriod","description":"The window and bucket grain the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"One row per bucket (day or hour, per the grain) in the period, in chronological order. Zero-filled, buckets with no activity are included with zero counts, so the series charts continuously without client-side gap handling.","items":{"$ref":"#/components/schemas/VerifyStatsPoint"}}}},"VerifyStatsCountryRowComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Change in this country's lifecycle metrics from the preceding period to the requested period. A `*_pct_change` is the signed relative change in a count, computed as `(current - previous) / previous`. It is `null` when the preceding count was zero. `conversion_rate_pp` is the signed difference between the two conversion rates expressed as fractions from 0 to 1. Multiply it by 100 for percentage points. It is `null` when either rate is undefined.","required":["created_sessions_pct_change","verified_pct_change","failed_pct_change","expired_pct_change","conversion_rate_pp"],"properties":{"created_sessions_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in this country's created verification sessions versus the previous period, as a signed fraction. `null` when the previous period created none for this country.","example":0.121},"verified_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in this country's verified sessions versus the previous period, as a signed fraction. `null` when the previous period verified none for this country.","example":0.144},"failed_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in this country's failed sessions (recipient exhausted their passcode attempts) versus the previous period, as a signed fraction. `null` when the previous period had none.","example":-0.052},"expired_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in this country's expired sessions versus the previous period, as a signed fraction. `null` when the previous period had none.","example":-0.018},"conversion_rate_pp":{"type":["number","null"],"minimum":-1,"maximum":1,"readOnly":true,"description":"Signed difference between this period's and the previous period's conversion rate for this country, both fractions in [0,1] (multiply by 100 for percentage points). `null` when either period's conversion rate is undefined.","example":0.006}}},"VerifyStatsCountryRowComparison":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Lifecycle counts and conversion rate for this country over the equal-length period immediately before the requested period, with the change between the two periods. Present only when `compare=previous_period` is requested and the country ranks among the preceding period's `200` highest-volume countries by created sessions. Also omitted when that country had no created sessions in the preceding period.","required":["created_sessions","verified","failed","expired","conversion_rate","delta"],"properties":{"created_sessions":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions created for this country in the preceding period.","example":180},"verified":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions verified for this country in the preceding period.","example":160},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions for this country that ended in the preceding period because the recipient exhausted their passcode attempts.","example":9},"expired":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions for this country that ended in the preceding period because their validity window elapsed.","example":11},"conversion_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of this country's created sessions that were verified in the preceding period, computed as `verified / created_sessions`. `null` when no sessions were created for this country in that period.\n","example":0.8889},"delta":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsCountryRowComparisonDelta"}]}}},"VerifyStatsCountryRow":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Verification funnel counts for a single destination country over the requested period.","required":["country","created_sessions","verified","failed","expired","conversion_rate"],"properties":{"country":{"readOnly":true,"description":"The destination country this row aggregates, as an ISO 3166-1 alpha-2 code.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"created_sessions":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions created for this country. This is the denominator for `conversion_rate`.","example":1820},"verified":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions verified for this country.","example":1690},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions for this country that ended because the recipient exhausted their passcode attempts.","example":65},"expired":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions for this country that ended because their validity window elapsed.","example":65},"conversion_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of created sessions for this country that were verified, computed as `verified / created_sessions`. `null` when no sessions were created for this country in scope.\n","example":0.9286},"comparison":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsCountryRowComparison"}]}}},"VerifyStatsCountriesResponse":{"type":"object","additionalProperties":false,"description":"Per-country funnel breakdown for the requested period, ranked by `created_sessions` descending and capped at the requested `limit` (default 50, max `200`).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/VerifyStatsPeriod","description":"The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Country breakdown rows, ranked by `created_sessions` descending. Empty when no verification sessions were created in the period.","items":{"$ref":"#/components/schemas/VerifyStatsCountryRow"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct destination countries with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to `200`) or narrow the window to see more.\n","example":34}}},"VerifyStatsChannelRowComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The change in this channel's delivery metrics from the preceding period to the requested one. A `*_pct_change` is a signed relative change in a count, computed as `(current - previous) / previous`. It is `null` when the previous period's count was zero. The `delivery_rate_pp` field is the signed arithmetic difference between the two periods' delivery rates. Each rate is a fraction in `[0,1]`; multiply by 100 for percentage points. The field is `null` when either period's rate is undefined.\n","required":["sent_pct_change","rejected_pct_change","delivered_pct_change","failed_pct_change","delivery_rate_pp"],"properties":{"sent_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in this channel's send attempts versus the previous period, as a signed fraction. `null` when the previous period sent none on this channel.","example":0.119},"rejected_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in this channel's rejected send attempts versus the previous period, as a signed fraction. `null` when the previous period had none.","example":0.004},"delivered_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in this channel's delivered send attempts versus the previous period, as a signed fraction. `null` when the previous period delivered none on this channel.","example":0.122},"failed_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in this channel's failed send attempts versus the previous period, as a signed fraction. `null` when the previous period had none.","example":-0.031},"delivery_rate_pp":{"type":["number","null"],"minimum":-1,"maximum":1,"readOnly":true,"description":"Signed difference between this period's and the previous period's delivery rate for this channel, both fractions in [0,1] (multiply by 100 for percentage points). `null` when either period's delivery rate is undefined.","example":0.004}}},"VerifyStatsChannelRowComparison":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The same delivery counts and rate for this channel over the equal-length period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested.\n","required":["sent","rejected","delivered","failed","delivery_rate","delta"],"properties":{"sent":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts handed to this channel in the preceding period.","example":2980},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts on this channel refused before a send attempt was made in the preceding period.","example":18},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts on this channel confirmed as delivered in the preceding period.","example":2910},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts on this channel that could not be delivered in the preceding period.","example":52},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of this channel's send attempts that were delivered in the preceding period, computed as `delivered / sent`. `null` when nothing was sent on this channel in that period.\n","example":0.9765},"delta":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsChannelRowComparisonDelta"}]}}},"VerifyStatsChannelRow":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Delivery and conversion counts for a single passcode delivery channel over the requested period.","required":["channel","sent","rejected","delivered","failed","delivery_rate","usage_share","conversion_rate"],"properties":{"channel":{"readOnly":true,"description":"The delivery channel this row aggregates.","allOf":[{"$ref":"#/components/schemas/VerificationChannel"}]},"sent":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts handed to this channel. This is the denominator for `delivery_rate`.","example":3210},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts on this channel refused before a send attempt was made.","example":20},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts on this channel confirmed as delivered.","example":3140},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts on this channel that could not be delivered.","example":50},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of this channel's send attempts that were delivered, computed as `delivered / sent`. `null` when nothing was sent on this channel in scope.\n","example":0.9782},"usage_share":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of the period's total send volume attributed to this channel, computed as `sent / total sent across all channels`. `null` when nothing was sent on any channel in scope.\n","example":0.62},"conversion_rate":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Share of verification sessions sent on this channel that were verified, computed as `verified_verifications / sent_verifications`. Both values use distinct verifications and attribute success to the channel of the winning passcode attempt. `null` when the denominator is zero. The value can exceed 1 when a channel has few sends, and is independent of `delivery_rate`.","example":1.02},"comparison":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyStatsChannelRowComparison"}]}}},"VerifyStatsChannelsResponse":{"type":"object","additionalProperties":false,"description":"Per-channel delivery and conversion breakdown for the requested period, ranked by `sent` descending.","required":["period","data"],"properties":{"period":{"$ref":"#/components/schemas/VerifyStatsPeriod","description":"The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Channel breakdown rows, ranked by `sent` descending. Empty when no passcode was sent in the period.","items":{"$ref":"#/components/schemas/VerifyStatsChannelRow"}}}},"VerifyStatsReasonRow":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The number of verification sessions that ended in a given terminal reason over the requested period.","required":["reason","sessions"],"properties":{"reason":{"readOnly":true,"description":"The terminal reason a verification session did not succeed.","example":"ttl_elapsed","allOf":[{"$ref":"#/components/schemas/VerificationTerminalReason"}]},"sessions":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct verification sessions that ended for this reason.","example":240}}},"VerifyStatsReasonsResponse":{"type":"object","additionalProperties":false,"description":"Session terminal-reason breakdown for the requested period, one row per reason with activity, ordered by session count descending.","required":["period","data"],"properties":{"period":{"$ref":"#/components/schemas/VerifyStatsPeriod","description":"The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Reason breakdown rows, ordered by session count descending. Empty when every session in the period was verified.","items":{"$ref":"#/components/schemas/VerifyStatsReasonRow"}}}},"VerifyStatsDeliveryChannelRow":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Delivery counts for a single passcode delivery channel over the requested period.","required":["channel","sent","delivered","failed","rejected","delivery_rate"],"properties":{"channel":{"readOnly":true,"description":"The delivery channel this row aggregates.","allOf":[{"$ref":"#/components/schemas/VerificationChannel"}]},"sent":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts handed to this channel. This is the denominator for `delivery_rate`.","example":3210},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts on this channel confirmed as delivered.","example":3140},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts on this channel that could not be delivered.","example":50},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts on this channel refused before a send attempt was made.","example":20},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of this channel's send attempts that were delivered, computed as `delivered / sent`. `null` when nothing was sent on this channel in scope.\n","example":0.9782}}},"VerifyStatsDeliveryFailureReasonRow":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The number of passcode send attempts on a channel that failed for a given reason over the requested period.","required":["reason","channel","failed"],"properties":{"reason":{"readOnly":true,"description":"Why the send attempts counted in this row failed, for example a carrier rejection or a terminal delivery failure.","example":"undelivered","allOf":[{"$ref":"#/components/schemas/VerificationAttemptFailureReason"}]},"channel":{"readOnly":true,"description":"The delivery channel the failed send attempt was made on.","allOf":[{"$ref":"#/components/schemas/VerificationChannel"}]},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts on this channel that failed for this reason.","example":18}}},"VerifyStatsDeliveryResponse":{"type":"object","additionalProperties":false,"description":"Deliverability breakdown for the requested period, covering whole-window delivery counts, a per-channel breakdown, a per-channel failure-reason breakdown, and delivery latency.\n","required":["period","delivery","by_channel","failure_reasons","latency"],"properties":{"period":{"$ref":"#/components/schemas/VerifyStatsPeriod","description":"The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VerifyDeliveryStats"}]},"by_channel":{"type":"array","readOnly":true,"description":"Delivery counts broken down by channel, ranked by `sent` descending. Empty when no passcode was sent in the period.","items":{"$ref":"#/components/schemas/VerifyStatsDeliveryChannelRow"}},"failure_reasons":{"type":"array","readOnly":true,"description":"Failed send attempts broken down by channel and failure reason, ranked by `failed` descending. Empty when no send attempt failed in the period. Per-reason detail covers the trailing 30 days; in a longer period, earlier failures appear in the aggregate `delivery` and `by_channel` counts but not in this breakdown.","items":{"$ref":"#/components/schemas/VerifyStatsDeliveryFailureReasonRow"}},"latency":{"readOnly":true,"description":"Delivery latency percentiles, computed as quantiles of `resolved_at - created_at` over charged delivered attempts. `null` within a percentile when no delivered attempt in scope was charged.\n","allOf":[{"$ref":"#/components/schemas/VerifyStatsQuantiles"}]}}},"VerifyStatsPricingRow":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The blended cost per passcode send attempt for a single destination country over the requested period.","required":["country","blended_rate","cost_amount","attempts"],"properties":{"country":{"readOnly":true,"description":"The destination country this row aggregates, as an ISO 3166-1 alpha-2 code.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"blended_rate":{"readOnly":true,"description":"Blended cost per passcode send attempt to this country, computed as `sum(cost) / uniq(attempt)` for this country.\n","allOf":[{"$ref":"#/components/schemas/Money"}]},"cost_amount":{"readOnly":true,"description":"Total amount charged for passcode send attempts to this country in scope.","allOf":[{"$ref":"#/components/schemas/Money"}]},"attempts":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct passcode send attempts to this country in scope. This is the denominator for `blended_rate`.","example":37},"previous_period_rate":{"readOnly":true,"description":"This country's blended rate over the equal-length period immediately before the requested period. Present only when `compare=previous_period` is requested and the country ranks among the preceding period's `200` highest-cost countries. Also omitted when the country had no charged send attempts in the preceding period.","allOf":[{"$ref":"#/components/schemas/Money"}]}}},"VerifyStatsPricingResponse":{"type":"object","additionalProperties":false,"description":"Per-country blended cost breakdown for the requested period, ranked by `cost_amount` descending and capped at the requested `limit` (default 50, max `200`).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/VerifyStatsPeriod","description":"The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Per-country cost breakdown rows, ranked by `cost_amount` descending. Empty when no passcode send attempt was charged in the period.","items":{"$ref":"#/components/schemas/VerifyStatsPricingRow"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct destination countries with a charged send attempt in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to `200`) or narrow the window to see more.\n","example":27},"avg_cost_per_verification":{"readOnly":true,"description":"Average amount charged per verification session across every country in scope, computed as `sum(cost) / count(verification sessions)`. Omitted when no verification session occurred in scope.\n","allOf":[{"$ref":"#/components/schemas/Money"}]}}},"PreviewFeatureCheck":{"type":"object","additionalProperties":false,"required":["enabled"],"properties":{"enabled":{"type":"boolean","readOnly":true,"description":"Whether preview surfaces are available to your current organization."}}},"PreviewOrgID":{"type":"string","minLength":1,"pattern":"^pog_[0-9a-hjkmnp-tv-z]{26}$","example":"pog_01krdgeqcxet5s7t44vh8rt9mg"},"AdminPreviewOrg":{"type":"object","additionalProperties":false,"required":["id","organization_id","added_at","added_by"],"properties":{"id":{"$ref":"#/components/schemas/PreviewOrgID","readOnly":true,"description":"The grant's id."},"organization_id":{"$ref":"#/components/schemas/OrganizationID","description":"The organization granted preview access."},"added_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this organization was added to the preview allowlist."},"added_by":{"$ref":"#/components/schemas/UserID","readOnly":true,"description":"The staff member who added this organization."}}},"AdminPreviewOrgList":{"type":"object","additionalProperties":false,"required":["organizations"],"properties":{"organizations":{"type":"array","description":"Every organization on the preview allowlist, oldest grant first.","items":{"$ref":"#/components/schemas/AdminPreviewOrg"}}}},"AdminPreviewOrgCreate":{"type":"object","additionalProperties":false,"required":["organization_id"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID","description":"The organization to grant preview access."}}},"PreviewFeatureRegistryID":{"type":"string","minLength":1,"pattern":"^pfr_[0-9a-hjkmnp-tv-z]{26}$","example":"pfr_01krdgeqcxet5s7t44vh8rt9mg"},"AdminPreviewFeatureRegistryEntry":{"type":"object","additionalProperties":false,"required":["id","key","owner","first_seen_at","last_seen_at"],"properties":{"id":{"$ref":"#/components/schemas/PreviewFeatureRegistryID","readOnly":true,"description":"The registry row's id."},"key":{"type":"string","minLength":1,"readOnly":true,"description":"The key call sites pass to the preview check. This row exists purely because a call site invoked the check with this key; there is no admin creation step."},"owner":{"type":"string","minLength":1,"format":"email","readOnly":true,"description":"Who to ask about this feature, from the most recent call. Overwritten on every call, so it reflects who owns this today."},"first_seen_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this key was first seen, i.e. how long it has been gated."},"last_seen_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this key was last seen."}}},"AdminPreviewFeatureRegistryList":{"type":"object","additionalProperties":false,"required":["features"],"properties":{"features":{"type":"array","description":"Every tracked preview-gated feature, longest-outstanding first.","items":{"$ref":"#/components/schemas/AdminPreviewFeatureRegistryEntry"}}}},"AdminVerificationConfigSettings":{"type":"object","additionalProperties":false,"description":"Default code settings every workspace inherits unless it overrides them.","required":["code_length","code_type","ttl_seconds","max_attempts","resend_cooldown_seconds","sms_validity_seconds","email_delivery_timeout_seconds"],"properties":{"code_length":{"type":"integer","minimum":4,"maximum":8,"description":"Number of characters in a generated passcode."},"code_type":{"type":"string","minLength":1,"enum":["numeric","alphanumeric"],"description":"Character set of a generated passcode. Only numeric is supported today."},"ttl_seconds":{"type":"integer","minimum":1,"maximum":59940,"description":"How long a verification stays valid, in seconds."},"max_attempts":{"type":"integer","minimum":1,"maximum":10,"description":"How many incorrect passcode submissions are allowed before the verification fails."},"resend_cooldown_seconds":{"type":"integer","minimum":0,"maximum":3600,"description":"Minimum wait between passcode sends to the same recipient, in seconds."},"sms_validity_seconds":{"type":"integer","minimum":60,"maximum":3600,"description":"How long, in seconds, a carrier keeps trying to deliver a verification SMS before it expires undelivered (sent to the carrier as the message's validity period). An expired message triggers failover to the next channel."},"email_delivery_timeout_seconds":{"type":"integer","minimum":60,"maximum":3600,"description":"The delivery deadline, in seconds, for verification emails: how long an email attempt may remain unresolved before it is considered undelivered. A Bird-side deadline, since email has no carrier validity period. Stored and editable, but not yet enforced on the send path."}}},"AdminVerificationChannelsList":{"type":"array","description":"An ordered list of verification channel names. Open enum — new channels may be added over time, so treat any unrecognized value as a future channel.","items":{"$ref":"#/components/schemas/VerificationChannel"}},"AdminVerificationConfig":{"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","additionalProperties":false,"required":["id","status","settings","fallback"],"properties":{"id":{"type":"string","format":"uuid","minLength":1,"readOnly":true,"description":"The configuration version's id."},"status":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["draft","active","archived"],"description":"The version's lifecycle state. `draft` is editable and not live, `active` is the single live version, `archived` is superseded history."},"settings":{"$ref":"#/components/schemas/AdminVerificationConfigSettings"},"fallback":{"$ref":"#/components/schemas/AdminVerificationChannelsList","description":"The ordered channels tried for a phone number whose country has no specific route. Email-only recipients always use email and never use this list."}}}]},"AdminVerificationConfigList":{"type":"object","additionalProperties":false,"required":["configs"],"properties":{"configs":{"type":"array","description":"All configuration versions, newest first.","items":{"$ref":"#/components/schemas/AdminVerificationConfig"}}}},"AdminVerificationConfigSettingsUpdate":{"type":"object","additionalProperties":false,"description":"Code settings to change. Omit a field to leave it unchanged.","properties":{"code_length":{"type":"integer","minimum":4,"maximum":8,"description":"Number of characters in a generated passcode."},"code_type":{"type":"string","minLength":1,"enum":["numeric","alphanumeric"],"description":"Character set of a generated passcode. Only numeric is supported today."},"ttl_seconds":{"type":"integer","minimum":1,"maximum":59940,"description":"How long a verification stays valid, in seconds."},"max_attempts":{"type":"integer","minimum":1,"maximum":10,"description":"How many incorrect passcode submissions are allowed before the verification fails."},"resend_cooldown_seconds":{"type":"integer","minimum":0,"maximum":3600,"description":"Minimum wait between passcode sends to the same recipient, in seconds."},"sms_validity_seconds":{"type":"integer","minimum":60,"maximum":3600,"description":"How long, in seconds, a carrier keeps trying to deliver a verification SMS before it expires undelivered (sent to the carrier as the message's validity period). An expired message triggers failover to the next channel."},"email_delivery_timeout_seconds":{"type":"integer","minimum":60,"maximum":3600,"description":"The delivery deadline, in seconds, for verification emails: how long an email attempt may remain unresolved before it is considered undelivered. A Bird-side deadline, since email has no carrier validity period. Stored and editable, but not yet enforced on the send path."}}},"AdminVerificationConfigUpdate":{"type":"object","additionalProperties":false,"description":"Fields to change on the admin verification config. Omit a field to leave it unchanged.","properties":{"settings":{"$ref":"#/components/schemas/AdminVerificationConfigSettingsUpdate"},"fallback":{"$ref":"#/components/schemas/AdminVerificationChannelsList","description":"Replace the ordered fallback channel list used when a phone number's country has no specific route."}}},"AdminVerificationConfigCountry":{"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","additionalProperties":false,"required":["country","available","default"],"properties":{"country":{"type":"string","minLength":2,"description":"ISO 3166-1 alpha-2 country code (uppercase)."},"available":{"$ref":"#/components/schemas/AdminVerificationChannelsList","description":"The channels Bird offers in this country. A workspace may enable only these."},"default":{"$ref":"#/components/schemas/AdminVerificationChannelsList","description":"The channels enabled by default in this country, in attempt order. A subset of available."},"sms_validity_seconds":{"type":"integer","minimum":60,"maximum":3600,"description":"Overrides the config's SMS validity period for this country. Omitted means the country inherits the config-wide value."}}}]},"AdminVerificationConfigCountryList":{"type":"object","additionalProperties":false,"required":["countries"],"properties":{"countries":{"type":"array","description":"The per-country routes configured for the admin verification config, ordered by country code.","items":{"$ref":"#/components/schemas/AdminVerificationConfigCountry"}}}},"AdminVerificationConfigCountryUpsert":{"type":"object","additionalProperties":false,"required":["available"],"properties":{"available":{"$ref":"#/components/schemas/AdminVerificationChannelsList","description":"The channels Bird offers in this country. A workspace may enable only these."},"default":{"$ref":"#/components/schemas/AdminVerificationChannelsList","description":"The channels enabled by default in this country, in attempt order. Must be a subset of available. Defaults to an empty list."},"sms_validity_seconds":{"type":"integer","minimum":60,"maximum":3600,"description":"Overrides the config's SMS validity period for this country. Because this request replaces the whole route, omitting this field clears any override the route already had and returns the country to the config-wide value."}}},"VerificationChannelDisableID":{"type":"string","minLength":1,"pattern":"^vcd_[0-9a-hjkmnp-tv-z]{26}$","example":"vcd_01krdgeqcxet5s7t44vh8rt9mg"},"AdminVerificationChannelDisable":{"type":"object","additionalProperties":false,"required":["id","channel","reason","created_by","created_at"],"properties":{"id":{"readOnly":true,"description":"The block's id. Pass it to the delete operation to unblock the channel.","$ref":"#/components/schemas/VerificationChannelDisableID"},"channel":{"$ref":"#/components/schemas/VerificationChannel"},"country":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"description":"The country this block applies to, or null when it applies to every country."},"reason":{"type":"string","minLength":1,"description":"Why the channel was blocked. Shown in the admin panel and kept in the block history."},"created_by":{"readOnly":true,"description":"Staff user who set the block. Stays resolvable after the account is deleted, which `created_by_email` does not.","$ref":"#/components/schemas/UserID"},"created_by_email":{"type":["string","null"],"readOnly":true,"description":"Email of the staff member who set the block, resolved for display on list reads. Always null in the create response, and null when that account no longer exists."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the block was set."},"cleared_by":{"readOnly":true,"description":"Staff user who cleared the block, or null while it is still in force.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"cleared_by_email":{"type":["string","null"],"readOnly":true,"description":"Email of the staff member who cleared the block, resolved for display. Null while the block is in force or when that account no longer exists."},"cleared_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the block was cleared, or null while it is still in force."}}},"AdminVerificationChannelDisableList":{"type":"object","additionalProperties":false,"required":["disables"],"properties":{"disables":{"type":"array","description":"Channel blocks, newest first. The blocks currently in force by default; cleared blocks too when the request asks for history.","items":{"$ref":"#/components/schemas/AdminVerificationChannelDisable"}}}},"AdminVerificationChannelDisableCreate":{"type":"object","additionalProperties":false,"required":["channel","reason"],"properties":{"channel":{"$ref":"#/components/schemas/VerificationChannel"},"country":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"description":"Block the channel only for verifications routed to this country. Omit it to block the channel everywhere. A channel cannot hold a country block and a global block at the same time, so clear the existing block first."},"reason":{"type":"string","minLength":1,"maxLength":500,"description":"Why the channel is being blocked, such as the provider incident it responds to."}}},"WhatsAppServiceWindow":{"type":"object","additionalProperties":false,"required":["open"],"properties":{"open":{"type":"boolean","description":"Whether the recipient has an open service window with this sender."},"expires_at":{"type":"string","format":"date-time","description":"When the open service window expires. Absent when the window is closed."},"matched_recipient":{"type":"string","readOnly":true,"description":"The recipient recovered from a matching inbound test message: its phone number in E.164 format when disclosed, otherwise its BSUID. Use it as the send recipient. Returned only when the matched recipient has an open window and the requested recipient is a phone number or the same matched BSUID.\n"}}},"WhatsAppMessageStatus":{"type":"string","minLength":1,"enum":["scheduled","accepted","sent","delivered","failed","rejected","canceled","received"],"description":"Delivery status:\n\n- `accepted`: Accepted and queued for sending.\n- `sent`: Handed to the WhatsApp network.\n- `delivered`: Confirmed as delivered to the recipient's device.\n- `failed`: Permanently failed.\n- `rejected`: Refused before sending and not charged.\n- `received`: Received as an inbound message.\n- `scheduled`: Reserved and not returned.\n- `canceled`: Reserved and not returned.\n\nRead receipts appear in `read_at` and `whatsapp.read` events, in both\ndirections: the recipient opening an outbound message, and the business\nacknowledging an inbound one.\n"},"WhatsAppGroupID":{"type":"string","minLength":1,"pattern":"^wag_[0-9a-hjkmnp-tv-z]{26}$","example":"wag_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppMessageID":{"type":"string","minLength":1,"pattern":"^wam_[0-9a-hjkmnp-tv-z]{26}$","example":"wam_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppAddress":{"type":"object","additionalProperties":false,"description":"Sender or recipient of a WhatsApp message: a phone number, a business-scoped user ID, or both. A message received from a WhatsApp user carries whatever profile they publish, which may be neither.","properties":{"phone_number":{"type":"string","minLength":1,"description":"Phone number in E.164 format, when known.","example":"+15550001111"},"bsuid":{"type":"string","minLength":1,"description":"Business-scoped user ID, Meta's identifier for the WhatsApp user. Present only on the WhatsApp-user side of the message.\n","example":"NL.xxxx"},"group_id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroupID"}],"description":"The group this address was addressed as, or reached through. It appears on a message's `to` and nowhere else: never on `from`, and never on an event's `recipient`. Outbound, it stands in for the recipient, because a group send names no single phone number. Inbound, it qualifies one: `to` carries the business `phone_number` that received the message and the group it arrived through, while `from` stays the participant who wrote it. Its presence on `to` is what tells a group message from a one-to-one one, in either direction.\n"},"username":{"type":"string","minLength":1,"description":"Present only on a message received from a WhatsApp user, on `from`; never on an outbound send's `to`, where the profile is not known. Absent when the contact has not adopted one, and on a message received before this workspace started recording them. Same form as a number's own username (`WhatsAppNumberProfile.username`), without a leading `@`; a message cannot be addressed by it.\n"},"display_name":{"type":"string","minLength":1,"description":"Present only on a message received from a WhatsApp user, on `from`; never on an outbound send's `to`, where the profile is not known. Absent when the message carries no profile, and on a message received before this workspace started recording them.\n"}}},"WhatsAppTemplateParameterType":{"type":"string","minLength":1,"x-extensible-enum":["text","image","video","gif","document","location"],"description":"The kind of value a template parameter carries, which follows the block it fills. The `text` type is a plain string substituted into a placeholder. This includes a coupon button's code, which the recipient copies from the button. The `image`, `video`, `gif`, and `document` types carry a media header's file in `url`. Each matches its header's `format`. The `location` type fills a location header and carries a point on the map. Open enum: more kinds may be added over time.\n"},"WhatsAppLocationSend":{"type":"object","additionalProperties":false,"required":["latitude","longitude"],"description":"A free-form location to send: a point on the map the recipient can open in their maps app. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead.\n","properties":{"latitude":{"type":"number","format":"double","minimum":-90,"maximum":90,"description":"Latitude in decimal degrees.","example":52.3702},"longitude":{"type":"number","format":"double","minimum":-180,"maximum":180,"description":"Longitude in decimal degrees.","example":4.8952},"name":{"type":"string","maxLength":1000,"description":"Name of the place, shown above the address.","example":"Bird HQ"},"address":{"type":"string","maxLength":1000,"description":"Street address of the place. Shown only when `name` is also set.","example":"Keizersgracht 117, Amsterdam"}},"example":{"latitude":52.3702,"longitude":4.8952,"name":"Bird HQ","address":"Keizersgracht 117, Amsterdam"}},"WhatsAppMessageTemplateComponentParameter":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateParameterType"}],"description":"The kind of value this parameter carries, which decides which of the fields below to send."},"text":{"type":"string","minLength":1,"description":"The value substituted into the placeholder, as a plain string. Send it on a `text` parameter."},"url":{"type":"string","format":"uri","minLength":1,"description":"Public `https` URL of the file a media header shows. Send it on an `image`, `video`, `gif` or `document` parameter. WhatsApp fetches it at send time, so it must still be reachable then, the same way a free-form media message's `url` must.\n","example":"https://cdn.example.com/receipts/a1b2c3.png"},"location":{"allOf":[{"$ref":"#/components/schemas/WhatsAppLocationSend"}],"description":"The point on the map a location header opens. Send it on a `location` parameter."},"name":{"type":"string","minLength":1,"description":"Required when the template declares named parameters: the placeholder this value fills (for example `first_name`), matching exactly one of the names the template declares. Name every parameter in that case; order does not matter once names are supplied. Omit this field for a positional template, which takes its values in `{{n}}` order instead. Sending the wrong set of names, or leaving one out that the template requires, returns a `422` `WhatsAppTemplateParameterMismatch`.\n"}}},"WhatsAppMessageTemplateCardComponent":{"type":"object","additionalProperties":false,"required":["type"],"description":"The values that fill one block of one carousel card.","properties":{"type":{"type":"string","minLength":1,"x-extensible-enum":["header","body","button"],"description":"Which part of the card this fills in.\n\n- `header`: the card's image or video.\n- `body`: its text.\n- `button`: a button's variable.\n","example":"header"},"parameters":{"type":"array","description":"The values that fill this part's placeholders, in placeholder order.","items":{"$ref":"#/components/schemas/WhatsAppMessageTemplateComponentParameter"}}}},"WhatsAppMessageTemplateCard":{"type":"object","additionalProperties":false,"required":["components"],"description":"The values that fill one card of a carousel. Cards fill in the order the template was approved with, so send one entry per card and keep them in that order.\n","properties":{"components":{"type":"array","description":"The values that fill this card's blocks.","items":{"$ref":"#/components/schemas/WhatsAppMessageTemplateCardComponent"}}}},"WhatsAppMessageTemplateComponent":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","minLength":1,"x-extensible-enum":["header","body","button","carousel"],"description":"Which part of the template this fills in.\n\n- `body`: the main text.\n- `button`: a button's variable.\n- `header`: the header's text, media or location.\n- `carousel`: the cards.\n"},"parameters":{"type":"array","description":"The values that fill this part's placeholders. A positional template takes them in `{{n}}` placeholder order; a template with named parameters requires each parameter's `name` to match one the template declares, and order then carries no meaning. Send it on every part except `carousel`, which carries its values on `cards`. Send no `button` part at all for a button that takes no value, such as a `quick_reply` or `request_contact_info` button, or a `url` button whose address has no placeholder: a part the template has no slot for is refused here, before the message is sent and charged.\n","items":{"$ref":"#/components/schemas/WhatsAppMessageTemplateComponentParameter"}},"cards":{"type":"array","minItems":2,"maxItems":10,"description":"The values that fill each card of a carousel. Send it only on a `carousel` part. A carousel sends exactly the number of cards its template was approved with, so every card needs an entry.\n","items":{"$ref":"#/components/schemas/WhatsAppMessageTemplateCard"}}}},"WhatsAppMessageTemplate":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The template a message was sent from. On reads `slug`, `language`, `category`, and `components` are always present; `components` is an empty array for an authentication template (the filled-in values, for example a verification code, are never returned).\n","required":["slug","language","category","components"],"properties":{"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"readOnly":true,"description":"The template's stable handle (for example `bird_otp`).","example":"bird_otp"},"category":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateCategory"}],"readOnly":true,"description":"The category this message was priced at, recorded as it stood when the message was sent. For a template you authored this is the category Meta applies to the language the send resolved to, which can differ from the category declared on the template: Meta categorizes each language separately and may move one. A built-in `bird_` template is priced at the single category the built-in declares, the same in every language.\n"},"language":{"allOf":[{"$ref":"#/components/schemas/LanguageTag"}],"readOnly":true,"description":"The canonical BCP-47 tag of the template variant that was sent.","example":"pt-BR"},"components":{"type":"array","readOnly":true,"description":"The values that filled the template's placeholders. Empty for an authentication template, whose content is never returned.\n","items":{"$ref":"#/components/schemas/WhatsAppMessageTemplateComponent"}}}},"WhatsAppText":{"type":"object","additionalProperties":false,"description":"Text content of a WhatsApp message.","required":["body"],"properties":{"body":{"type":"string","minLength":1,"description":"The message text.","example":"Does it come in another color?"}},"example":{"body":"Does it come in another color?"}},"WhatsAppFileID":{"type":"string","minLength":1,"pattern":"^waf_[0-9a-hjkmnp-tv-z]{26}$","example":"waf_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppMedia":{"type":"object","description":"Fields shared by every media content object on a WhatsApp message.","properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppFileID"}],"description":"ID of the stored file, to pass as `media_id` when fetching it. Absent on an outbound message, whose file we never stored.\n"},"url":{"type":"string","format":"uri","description":"Where to fetch the media. On an inbound message this is a Bird URL you fetch with your API key; it is absent when the file could not be retrieved from WhatsApp. It stays populated after the stored bytes expire, and the link returns `410` from then on. On an outbound message it is the URL the sender supplied, whose availability is the sender's to guarantee.\n","example":"https://platform.bird.com/v1/whatsapp/messages/wam_01kya19eknftrs2s6p82asmvnh/media/waf_01kyb2m4xq7whs0d8n3prv6tez"},"mime_type":{"type":"string","description":"Media type WhatsApp reported for the file, for example `image/jpeg`. Absent on outbound messages.\n","example":"image/jpeg"}}},"WhatsAppImage":{"type":"object","description":"Image content of a WhatsApp message.\n","allOf":[{"$ref":"#/components/schemas/WhatsAppMedia"},{"type":"object","properties":{"caption":{"type":"string","description":"Text shown beneath the image. Absent when the sender wrote none.","example":"Your receipt for order A1B2C3"}}}]},"WhatsAppVideo":{"type":"object","description":"Video content of a WhatsApp message.\n","allOf":[{"$ref":"#/components/schemas/WhatsAppMedia"},{"type":"object","properties":{"caption":{"type":"string","description":"Text shown beneath the video. Absent when the sender wrote none.","example":"How to set it up"}}}]},"WhatsAppAudio":{"type":"object","description":"Audio content of a WhatsApp message.\n","allOf":[{"$ref":"#/components/schemas/WhatsAppMedia"},{"type":"object","properties":{"voice":{"type":"boolean","description":"Whether this is a voice note rather than an attached audio file. A voice note auto-downloads in the WhatsApp client and can be transcribed for the recipient.\n","example":true}}}]},"WhatsAppSticker":{"type":"object","description":"Sticker content of a WhatsApp message.\n","allOf":[{"$ref":"#/components/schemas/WhatsAppMedia"},{"type":"object","properties":{"animated":{"type":"boolean","description":"Whether the sticker is animated. Absent on an outbound message.\n","example":false}}}]},"WhatsAppDocument":{"type":"object","description":"Document content of a WhatsApp message.\n","allOf":[{"$ref":"#/components/schemas/WhatsAppMedia"},{"type":"object","properties":{"caption":{"type":"string","description":"Text shown beneath the document. Absent when the sender wrote none.","example":"Signed contract"},"filename":{"type":"string","description":"The sender's own name for the file.","example":"contract-a1b2c3.pdf"}}}]},"WhatsAppLocation":{"type":"object","additionalProperties":false,"description":"Location content of a WhatsApp message: a point on the map the recipient can open in their maps app.\n","properties":{"latitude":{"type":"number","format":"double","description":"Latitude in decimal degrees.","example":52.3702},"longitude":{"type":"number","format":"double","description":"Longitude in decimal degrees.","example":4.8952},"name":{"type":"string","description":"Name of the place. Absent when the sender shared a plain pin.","example":"Bird HQ"},"address":{"type":"string","description":"Street address of the place. Shown only when `name` is also set.","example":"Keizersgracht 117, Amsterdam"},"url":{"type":"string","format":"uri","description":"Link to the place, which WhatsApp includes mainly for business locations. Present on an inbound message when the sender's client supplied one, and absent on a message you sent, since sending a location does not support this field.\n","example":"https://www.google.com/maps/place/Statue+of+Liberty/@40.6246301,-74.5291919,124716m/"}}},"WhatsAppContactName":{"type":"object","additionalProperties":false,"description":"The contact's name, in the parts their device supplied. Every part is optional: WhatsApp sends what the card holds and omits the rest.\n","properties":{"formatted_name":{"type":"string","description":"The whole name as the contact's device renders it.","example":"Barbara J. Johnson"},"first_name":{"type":"string","example":"Barbara"},"middle_name":{"type":"string","example":"Joana"},"last_name":{"type":"string","example":"Johnson"},"prefix":{"type":"string","example":"Dr."},"suffix":{"type":"string","example":"Esq."}}},"WhatsAppContactOrg":{"type":"object","additionalProperties":false,"description":"Where the contact works, as their card records it.","properties":{"company":{"type":"string","example":"Lucky Shrub"},"department":{"type":"string","example":"Engineering"},"title":{"type":"string","example":"Software Engineer"}}},"WhatsAppContactPhone":{"type":"object","additionalProperties":false,"description":"One phone number on a shared contact card.","properties":{"phone_number":{"type":"string","description":"The number as the card holds it, normalized to E.164 where we can parse it. A card is whatever the contact's device stored, so a number that no country's numbering plan accepts, an extension among them, is passed through exactly as it arrived rather than dropped. Parse defensively: most values are E.164 and none is guaranteed to be.\n","example":"+16505551234"},"type":{"type":"string","description":"The label attached to this value, for example `CELL`, `Home` or `iPhone`. Free text: WhatsApp defines no vocabulary. A label on a received card is lowercased; one this workspace sent reads back exactly as sent.\n","example":"CELL"}}},"WhatsAppContactEmail":{"type":"object","additionalProperties":false,"description":"One email address on a shared contact card.","properties":{"email":{"type":"string","example":"barbara@example.com"},"type":{"type":"string","description":"The label attached to this value, for example `CELL`, `Home` or `iPhone`. Free text: WhatsApp defines no vocabulary. A label on a received card is lowercased; one this workspace sent reads back exactly as sent.\n","example":"Personal"}}},"WhatsAppContactUrl":{"type":"object","additionalProperties":false,"description":"One website on a shared contact card.","properties":{"url":{"type":"string","description":"The address as the card holds it, which is often bare rather than a full URL, so it is passed through as text rather than validated.\n","example":"luckyshrub.example.com"},"type":{"type":"string","description":"The label attached to this value, for example `CELL`, `Home` or `iPhone`. Free text: WhatsApp defines no vocabulary. A label on a received card is lowercased; one this workspace sent reads back exactly as sent.\n","example":"Company"}}},"WhatsAppContactAddress":{"type":"object","additionalProperties":false,"description":"One postal address on a shared contact card.","properties":{"street":{"type":"string","example":"1 Hacker Way"},"city":{"type":"string","example":"Menlo Park"},"state":{"type":"string","example":"CA"},"zip":{"type":"string","example":"94025"},"country":{"type":"string","example":"United States"},"country_code":{"type":"string","description":"The country as the card holds it, left exactly as WhatsApp sent it: it describes a postal address rather than a routing destination.\n","example":"US"},"type":{"type":"string","description":"The label attached to this value, for example `CELL`, `Home` or `iPhone`. Free text: WhatsApp defines no vocabulary. A label on a received card is lowercased; one this workspace sent reads back exactly as sent.\n","example":"Home"}}},"WhatsAppContactCard":{"type":"object","additionalProperties":false,"description":"A contact card on this message: one the contact shared, or one this workspace sent.\nNothing here is required. WhatsApp sends the parts the card holds and omits the rest, and a card that arrives with only an `origin` is still meaningful, so an empty card reads back empty rather than being dropped.\n","properties":{"origin":{"type":"string","minLength":1,"x-extensible-enum":["contact_request","other"],"description":"Why the card arrived. `contact_request` means the contact tapped a button this workspace sent asking for their number, which is the only signal that the message answers that ask; `other` means they shared a card in the chat. Open enum: treat an unrecognized value as a way of sharing added since. Set on a card the contact shared; absent on one this workspace sent.\n","example":"contact_request"},"vcard":{"type":"string","description":"The contact's card in vCard format. WhatsApp sends it on a card shared in the chat and omits it on a button tap, which carries the number alone. Set on a card the contact shared; absent on one this workspace sent.\n","example":"BEGIN:VCARD\nVERSION:3.0\nN:Johnson;Barbara;;;\nTEL;type=CELL:+16505551234\nEND:VCARD\n"},"name":{"allOf":[{"$ref":"#/components/schemas/WhatsAppContactName"}],"description":"The contact's name, when the card carries one."},"org":{"allOf":[{"$ref":"#/components/schemas/WhatsAppContactOrg"}],"description":"Where the contact works, when the card carries it."},"birthday":{"type":"string","description":"The contact's birthday, which WhatsApp sends as `YYYY-MM-DD`. Passed through as text rather than typed as a date: the value comes off the contact's own device unvalidated, and a card we could not parse would otherwise have to lose the field or fail the whole read.\n","example":"1999-01-23"},"phone_numbers":{"type":"array","description":"The numbers on the card. A button tap carries the contact's own number here, which is the point of asking.\n","items":{"$ref":"#/components/schemas/WhatsAppContactPhone"}},"emails":{"type":"array","items":{"$ref":"#/components/schemas/WhatsAppContactEmail"}},"urls":{"type":"array","items":{"$ref":"#/components/schemas/WhatsAppContactUrl"}},"addresses":{"type":"array","items":{"$ref":"#/components/schemas/WhatsAppContactAddress"}}},"example":{"origin":"contact_request","phone_numbers":[{"phone_number":"+16505551234","type":"CELL"}]}},"WhatsAppInteractiveType":{"type":"string","minLength":1,"x-extensible-enum":["button","list","cta_url","carousel","location_request_message","request_contact_info"],"description":"Which kind of interactive message this is.\n\n- `button`: up to three tappable buttons, each sending its own identifier\n  back as an inbound message. Carried in `buttons`.\n- `list`: a single button that opens a menu of rows to choose one from.\n- `cta_url`: a single button that opens a link.\n- `carousel`: 2 to 10 media cards the recipient scrolls through sideways,\n  each with its own buttons. Carried in `cards`.\n- `location_request_message`: a single button that asks the recipient to\n  share where they are. A tap arrives as an inbound `location` message.\n- `request_contact_info`: a single button that asks the recipient to share\n  their phone number. A tap arrives as an inbound message carrying the number\n  on `contact_cards`, with `origin` set to `contact_request`.\n\nThe last two name no field of their own: the ask is the whole message, and\n`body_text` is all they carry.\n\nOpen enum: WhatsApp adds interactive kinds over time, so treat an\nunrecognized value as a future kind rather than an error.\n","example":"button"},"WhatsAppInteractiveHeaderType":{"type":"string","minLength":1,"x-extensible-enum":["text","image","video","document"],"description":"A header's kind, and which field carries it. Open enum: WhatsApp adds header kinds over time, so treat an unrecognized value as a future kind rather than an error.\n","example":"image"},"WhatsAppInteractiveHeader":{"type":"object","additionalProperties":false,"required":["type"],"description":"What the interactive message showed above its body. `type` names the kind and the field that carries it: `text` for a line of copy, `url` for the file every other kind shows. As on the message itself, the read vocabulary is open: dispatch on `type` and treat an unrecognized value as a header kind added since.\n","properties":{"type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveHeaderType"}],"description":"Which kind of header this is, and which field carries it."},"text":{"type":"string","description":"The line of text shown above the body.","example":"New workshop dates announced"},"url":{"type":"string","format":"uri","description":"The URL of the file shown above the body, as the send supplied it. Interactive content is outbound only, so Bird neither stores nor proxies the file.\n","example":"https://cdn.example.com/banners/workshop.png"}}},"WhatsAppInteractiveButtonType":{"type":"string","minLength":1,"x-extensible-enum":["quick_reply","cta_url"],"description":"Which kind of button this is, and which field carries it.\n\n- `quick_reply`: sends its own identifier back as an inbound message.\n- `cta_url`: opens a link in the recipient's browser.\n\nOpen enum: WhatsApp adds button kinds over time, so treat an unrecognized\nvalue as a future kind rather than an error.\n","example":"quick_reply"},"WhatsAppInteractiveQuickReplyButton":{"type":"object","additionalProperties":false,"required":["slug","text"],"description":"A reply button's label and the handle it carries back. On the echo of a message Bird sent, the pair the send declared; on an inbound `interactive_reply`, the pair the contact tapped. No length is declared here, because a tap can echo a template's quick-reply button, whose label runs longer than an interactive message's own allows.\n","properties":{"slug":{"type":"string","minLength":1,"description":"The handle the button carries back, never shown to the recipient. On a tap on a template's quick-reply button, it is the payload that template declared.\n","example":"change-booking"},"text":{"type":"string","minLength":1,"description":"The label the recipient saw.","example":"Change"}}},"WhatsAppInteractiveCtaUrl":{"type":"object","additionalProperties":false,"required":["text","url"],"description":"The link button the message offered: its label and the address it opens.\n","properties":{"text":{"type":"string","minLength":1,"description":"The button's label.","example":"See dates"},"url":{"type":"string","format":"uri","minLength":1,"description":"The address the button opens, as the send supplied it.","example":"https://example.com/workshops?click_id=a1b2c3"}}},"WhatsAppInteractiveButton":{"type":"object","additionalProperties":false,"required":["type"],"description":"One button the message offered. The field named by `type` is the one that is present. As on the message itself, the read vocabulary is open: dispatch on `type` and treat an unrecognized value as a button kind added since.\n","properties":{"type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveButtonType"}],"description":"Which kind of button this is, and which field carries it."},"quick_reply":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveQuickReplyButton"}],"description":"The button's label and the handle it sends back."},"cta_url":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveCtaUrl"}],"description":"The button's label and the address it opens."}}},"WhatsAppInteractiveListRow":{"type":"object","additionalProperties":false,"required":["slug","text"],"description":"One option in a list's menu. On the echo of a message Bird sent, the row as declared; on an inbound `interactive_reply`, the row the contact chose. No length is declared here: this is what WhatsApp reported, not what a send is held to.\n","properties":{"slug":{"type":"string","minLength":1,"description":"The handle the row carries back, never shown to the recipient.","example":"priority_express"},"text":{"type":"string","minLength":1,"description":"The row's label, shown as its title in the menu.","example":"Priority Mail Express"},"description":{"type":"string","description":"The second line under the label. Absent when the row carried none.","example":"Next day to 2 days"}}},"WhatsAppInteractiveListSection":{"type":"object","additionalProperties":false,"required":["title","rows"],"description":"One group of options in the menu the message showed.","properties":{"title":{"type":"string","minLength":1,"description":"The group's heading, shown above its rows.","example":"As soon as possible"},"rows":{"type":"array","description":"The options in this group, in the order shown.","items":{"$ref":"#/components/schemas/WhatsAppInteractiveListRow"}}}},"WhatsAppInteractiveList":{"type":"object","additionalProperties":false,"required":["button_text","sections"],"description":"The menu the message offered: a button that opens it, and the groups of options behind it.\n","properties":{"button_text":{"type":"string","minLength":1,"description":"The label of the button that opens the menu.","example":"Shipping options"},"sections":{"type":"array","description":"The groups of options in the menu, in the order shown.","items":{"$ref":"#/components/schemas/WhatsAppInteractiveListSection"}}}},"WhatsAppInteractiveCard":{"type":"object","additionalProperties":false,"required":["header","buttons"],"description":"One card the carousel showed, in the position it appeared in.","properties":{"header":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveHeader"}],"description":"The image or video shown at the top of the card."},"body_text":{"type":"string","description":"The card's own text. Absent when the card carried none.","example":"*Blue Echeveria*"},"buttons":{"type":"array","description":"The buttons the card offered, in the order shown.","items":{"$ref":"#/components/schemas/WhatsAppInteractiveButton"}}}},"WhatsAppInteractive":{"type":"object","additionalProperties":false,"description":"Interactive content of a WhatsApp message: body text plus something the recipient can tap. The field named by `type` is the one that is present, except on `location_request_message` and `request_contact_info`, which name no field: each is a single button asking the recipient for something, so `body_text` is the whole message. Outbound only, and so an echo of what the send asked for: a contact cannot send interactive content, and a tap on it reads as `interactive_reply`, or on a location or contact request as the message the recipient shared in answer. Unlike the send schema, this one does not pin each `type` to its field. The vocabulary in `type` is open, so dispatch on it and treat an unrecognized value as a kind added since. Only the discriminator is open: this schema declares no additional properties, so a new kind's own payload field arrives here in the same change that introduces the kind, which is additive.\n","required":["type","body_text"],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveType"}],"description":"Which kind of interactive message this is, and which field carries it."},"header":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveHeader"}],"description":"What was shown above the body. Absent when the message carried no header."},"body_text":{"type":"string","minLength":1,"description":"The message's main text.","example":"Your workshop is scheduled for 9am tomorrow."},"footer_text":{"type":"string","description":"The small print below the body. Absent when the message carried none.","example":"Lucky Shrub, your gateway to succulents"},"buttons":{"type":"array","description":"The buttons the message offered, in the order shown.","items":{"$ref":"#/components/schemas/WhatsAppInteractiveButton"}},"list":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveList"}],"description":"The menu the message offered."},"cta_url":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveCtaUrl"}],"description":"The link button the message offered."},"cards":{"type":"array","description":"The cards the message offered, in the order they appeared, left to right.\n","items":{"$ref":"#/components/schemas/WhatsAppInteractiveCard"}}}},"WhatsAppInteractiveReplyType":{"type":"string","minLength":1,"x-extensible-enum":["button","list"],"description":"Which kind of tap the reply came from.\n\n- `button`: a reply button on an interactive message, or a quick-reply\n  button on a template. Both carry an identifier and a label, so they read\n  the same way.\n- `list`: a row chosen from a list's menu. Only this kind carries a\n  `description`.\n\nOpen enum: WhatsApp adds interactive kinds over time, so treat an\nunrecognized value as a future kind rather than an error.\n","example":"button"},"WhatsAppInteractiveReply":{"type":"object","additionalProperties":false,"description":"What the contact tapped, on an inbound message answering an interactive message or a template's quick-reply button. `type` names the kind and the field it names carries it, as everywhere else in this arm. Inbound only: a message that offers something to tap reads as `interactive` instead, and the two never appear together.\n","required":["type"],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveReplyType"}],"description":"Which kind of tap this reply came from, and which field carries it."},"button":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveQuickReplyButton"}],"description":"The button the contact tapped, as you declared it. On a reply to a template's quick-reply button, `slug` is the button's payload, which WhatsApp sets to the button's own label.\n"},"list":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveListRow"}],"description":"The row the contact chose, as you declared it. `description` is present only when the row carried one.\n"}},"example":{"type":"list","list":{"slug":"priority_express","text":"Priority Mail Express","description":"Next day to 2 days"}}},"WhatsAppUnsupported":{"type":"object","additionalProperties":false,"description":"A message whose content we do not model, named so it is visible in the message log rather than arriving empty. Inbound only.\n","required":["type"],"properties":{"type":{"type":"string","minLength":1,"x-extensible-enum":["reaction","interactive","button","order","system","unsupported"],"description":"The WhatsApp content type we did not model. `unsupported` is not a placeholder here: WhatsApp reports its own `unsupported` type for a message its own clients cannot render, and that arrives as this value. Open enum: WhatsApp adds content types over time, so treat an unrecognized value as a future type rather than an error.\n","example":"reaction"}},"example":{"type":"reaction"}},"WhatsAppReaction":{"type":"object","additionalProperties":false,"required":["emoji","from"],"description":"An emoji reaction standing on a message. One entry per sender: reacting again replaces that sender's entry rather than adding one, and removing a reaction drops it from the list. A one-to-one message therefore carries at most two, one for the contact and one for your business number. This is the folded current state, so it names no single change; the message's reaction log is what records how each one arrived.\n","properties":{"emoji":{"type":"string","minLength":1,"readOnly":true,"description":"The emoji, as WhatsApp sent it. It is not normalized, so two emoji that render identically can differ byte for byte and compare unequal.\n","example":"👍"},"from":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"description":"Who reacted. On a group message this is what tells one participant's reaction from another's. On a one-to-one message it is your business number on a reaction you placed and the contact on one they placed, which is why it is here rather than inferred from the message's `direction`.\n"}}},"WhatsAppAgentController":{"type":"string","minLength":1,"x-extensible-enum":["agent","app"],"description":"Who is answering a contact's conversation right now.\n\n- `agent` means the agent holds it and replies to the contact.\n- `app` means your own application holds it, and the agent stays silent until control goes back.\n\nWhatsApp decides which parties can hold a conversation, so treat an unrecognized value as a party it has since added rather than an error.\n","example":"agent"},"WhatsAppErrorCode":{"type":"string","minLength":1,"x-extensible-enum":["insufficient_balance","price_not_found","internal_error","undeliverable","service_window_expired","rate_limited","recipient_suppressed","media_rejected"],"description":"Standardized failure reason:\n\n- `insufficient_balance`: The workspace wallet could not fund the send.\n- `price_not_found`: No price was configured for the destination and template.\n- `internal_error`: An unexpected service failure occurred.\n- `undeliverable`: The recipient could not be reached.\n- `service_window_expired`: The 24-hour service window closed; send a template.\n- `rate_limited`: The send was throttled.\n- `recipient_suppressed`: The recipient is on the workspace suppression list.\n- `media_rejected`: WhatsApp could not fetch the media URL, or refused the file it found there; `description` carries its reason.\n\nThis is an open enum. Accept unrecognized values.\n"},"WhatsAppError":{"type":["object","null"],"additionalProperties":false,"readOnly":true,"required":["code","description","occurred_at"],"description":"Failure detail for a message that could not be delivered or was rejected.","properties":{"code":{"$ref":"#/components/schemas/WhatsAppErrorCode"},"description":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable explanation of the failure.","example":"Message could not be delivered."},"meta_error_code":{"type":["string","null"],"readOnly":true,"description":"Raw error code from the WhatsApp Cloud API, when available, for low-level debugging.","example":"131026"},"occurred_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the failure occurred."}}},"WhatsAppMessage":{"type":"object","additionalProperties":false,"required":["id","direction","from","to","status","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppMessageID","description":"ID of the message, assigned when the send is accepted. Pass it as `message_id` to the get-message and list-events endpoints.\n"},"direction":{"type":"string","minLength":1,"readOnly":true,"enum":["outbound","inbound"],"description":"Whether the message was sent by the business (`outbound`) or received from the contact (`inbound`)."},"from":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"description":"Sender of the message. On outbound messages, the business number it was sent from; on inbound, the WhatsApp contact."},"to":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"description":"Recipient of the message. On outbound messages, the WhatsApp contact; on inbound, the business number."},"template":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppMessageTemplate"}],"description":"The template the message was sent from. For authentication templates the filled-in values are not returned."},"text":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppText"}],"description":"Text the message carried."},"image":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppImage"}],"description":"Image the message carried."},"video":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppVideo"}],"description":"Video the message carried."},"audio":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppAudio"}],"description":"Audio the message carried."},"sticker":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppSticker"}],"description":"Sticker the message carried."},"document":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppDocument"}],"description":"Document the message carried."},"location":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppLocation"}],"description":"Location the message carried."},"contact_cards":{"readOnly":true,"type":"array","description":"Contact cards on this message: cards the contact shared, either by tapping a button that asked for their number or by sending one from their address book, or the cards this workspace sent.\n","items":{"$ref":"#/components/schemas/WhatsAppContactCard"}},"interactive":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractive"}],"description":"Interactive content the message carried. Outbound only: a contact cannot send one. A tap on a reply button or a list row reads back as `interactive_reply` on the contact's inbound message; a `cta_url` link sends nothing back, and the two request kinds are answered by an inbound `location` or `contact_cards` message.\n"},"in_reply_to_message_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppMessageID"}],"description":"The message this one answers. On an inbound message it is what WhatsApp reports as the reply's target: a tap on a button or a list row, and equally a text or media message the contact sent as a quoted reply. An outbound message echoes the `in_reply_to_message_id` it was sent with. Absent when the message answers nothing, and absent on an inbound message whose target we cannot match to a message we hold, which is the case for one sent before this workspace started recording them or one already past the 15-day window we keep provider ids for.\n"},"interactive_reply":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveReply"}],"description":"What the contact tapped, on a message answering an interactive message or a template's quick-reply button. Inbound only.\n"},"unsupported":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppUnsupported"}],"description":"Set when the contact sent content we do not model, naming the WhatsApp content type so the message is not silently empty. Inbound only.\n"},"reactions":{"readOnly":true,"type":"array","description":"Emoji reactions standing on this message right now, one per sender. Absent when the message has none. A reaction that was replaced by a different emoji, or taken back, is not listed; the message's reaction log keeps that history. WhatsApp accepts a reaction on a message up to 30 days old, and we keep provider ids for 15, so a reaction placed on a message older than that cannot be matched to it and does not appear here.\n","items":{"$ref":"#/components/schemas/WhatsAppReaction"}},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppMessageStatus"}]},"recipient_count":{"type":"integer","minimum":1,"readOnly":true,"description":"How many recipients a group send was addressed to, taken when the send\nwas accepted. It is the group's membership at that moment, not its\nmembership now: someone joining through the invite link while the message\nis in flight does not receive it and does not change this count.\n\nAbsent on a one-to-one message, along with `delivered_count` and\n`read_count`. A message with one recipient has no fan-out to report, and\nits delivery is what `status`, `delivered_at` and `read_at` already say.\nAbsent for the same reason on a group message sent before Bird recorded\nthe count, and on a send to a group nobody had joined yet: there is no\ndenominator to report, and none can be recovered after the fact, since\nmembership has moved on. `to.group_id` is what tells a group message from\na one-to-one one in every case, including those two. With no denominator\nto resolve against, `status` is read as stored, the way a one-to-one\nmessage's is: it reaches `sent` when the message is handed to WhatsApp and\nstops there, because delivery is confirmed per participant and a send with\nno participants collects no confirmations.\n\nIt is also the denominator `status` is resolved against: on a group\nmessage `status` reports the furthest point *every* recipient has\nreached, so it turns `delivered` only once `delivered_count` equals this\nnumber, and stays `sent` while some have confirmed and others have not.\n`failed` and `rejected` are never per recipient: there is one hand-off to\nthe WhatsApp network and one way for that to be refused. `delivered_at`\nand `read_at` are the first recipient's, not the last.\n"},"delivered_count":{"type":"integer","minimum":0,"readOnly":true,"description":"How many of the `recipient_count` recipients WhatsApp has confirmed the\nmessage reached. A recipient who reported only a read counts here too:\nWhatsApp skips the delivery receipt when someone is already looking at\nthe chat, so waiting for one would leave that person uncounted for ever.\n\nAbsent on a one-to-one message, which has no fan-out to count, and on a\ngroup message with no `recipient_count` to count against.\n"},"read_count":{"type":"integer","minimum":0,"readOnly":true,"description":"How many of the `recipient_count` recipients have opened the message.\nRead receipts do not move `status`, which has no `read` value; they\nsurface here and in `read_at`.\n\nAbsent on a one-to-one message, which has no fan-out to count, and on a\ngroup message with no `recipient_count` to count against.\n"},"last_error":{"$ref":"#/components/schemas/WhatsAppError","description":"Failure detail for a message that did not reach the recipient. Present only when the message failed or was rejected."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the message was accepted for delivery."},"sent_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the message was handed to the WhatsApp network. Null until then."},"delivered_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When delivery was confirmed. Null until then."},"read_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the message was read. On an outbound message this is the recipient opening it. On an inbound one it is when Bird acknowledged the message to WhatsApp for the business, which a read receipt sets. Null until then.\n"},"cost":{"readOnly":true,"$ref":"#/components/schemas/MessageCost","description":"What the message cost, split into Bird's charge and any third-party fees passed through. Null on an inbound message, which is never priced, on an outbound message that has not been priced yet, and on one rejected before pricing. The rate depends on the message category and the recipient's country."},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"description":"Structured `{name, value}` filter labels applied to this message."},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary JSON metadata stored on the message."}}},"WhatsAppMessageList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of WhatsApp messages, newest first.","items":{"$ref":"#/components/schemas/WhatsAppMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppTemplateID":{"type":"string","minLength":1,"pattern":"^wat_[0-9a-hjkmnp-tv-z]{26}$","example":"wat_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppTemplateSend":{"type":"object","additionalProperties":false,"description":"A send-by-template reference. Identify the template by its `id` or its `slug` (supply exactly one), optionally name a language, and fill its placeholders through `components`.\n","oneOf":[{"required":["id"]},{"required":["slug"]}],"properties":{"id":{"description":"The template to send, by its id.","$ref":"#/components/schemas/WhatsAppTemplateID"},"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"description":"The template to send, by its slug handle (for example `bird_otp`).","example":"bird_otp"},"language":{"allOf":[{"$ref":"#/components/schemas/LanguageTag"}],"description":"Which of the template's languages to send, as a BCP-47 tag (for example `en` or `pt-BR`); Meta's underscore form (`pt_BR`) is accepted and normalized. Omit it to send the template's default language, unless the template sets `language_source_required`, in which case a send naming no language is rejected. When the template does not carry the language you ask for, its own `on_missing_language` setting decides whether the closest available language is sent instead or the send is rejected. The accepted message echoes the canonical BCP-47 form of the language it resolved to, which is the language it is priced at: Meta categorizes each language separately, so a send served by a different language than the one you asked for is priced at that language's category.\n","example":"pt-BR"},"components":{"type":"array","description":"The values that fill the template's placeholders: one entry per content block that has placeholders, each carrying its `parameters`. A positional template takes its parameters in `{{n}}` order; a template with named parameters requires each parameter's `name` to match one the template declares. Either way, sending parameters that do not match what the template declares returns a `422` `WhatsAppTemplateParameterMismatch`.\n","items":{"$ref":"#/components/schemas/WhatsAppMessageTemplateComponent"}}},"examples":[{"id":"wat_01ky4x8e4genzb7way45txfkm1","language":"en","components":[{"type":"body","parameters":[{"type":"text","text":"1234"}]},{"type":"button","parameters":[{"type":"text","text":"1234"}]}]},{"slug":"bird_order_confirmation","language":"en","components":[{"type":"body","parameters":[{"type":"text","name":"ref","text":"A1B2C3D4"},{"type":"text","name":"amount","text":"EUR 49.99"}]}]},{"slug":"bird_otp","language":"en","components":[{"type":"body","parameters":[{"type":"text","text":"1234"}]},{"type":"button","parameters":[{"type":"text","text":"1234"}]}]}]},"WhatsAppTextSend":{"type":"object","additionalProperties":false,"required":["body"],"description":"Free-form text to send. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead.\n","properties":{"body":{"type":"string","minLength":1,"maxLength":4096,"description":"The message text. The WhatsApp client turns any URL it contains into a clickable link.\n","example":"Your driver is 2 minutes away."},"preview_url":{"type":"boolean","default":false,"description":"Whether the WhatsApp client renders a preview of the first URL in `body`. A URL must begin with `http://` or `https://`, only the first one is previewed, and the client falls back to a plain link when it cannot fetch a preview. Not returned when the message is read back, because WhatsApp does not report whether a preview rendered.\n"}},"example":{"body":"Your order shipped: https://example.com/track/A1B2C3","preview_url":true}},"WhatsAppImageSend":{"type":"object","additionalProperties":false,"required":["url"],"description":"A free-form image to send, with an optional caption. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead.\n","properties":{"url":{"type":"string","format":"uri","minLength":1,"description":"Public `https` URL of the image. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. JPEG and PNG only, up to 5 MB.\n","example":"https://cdn.example.com/receipts/a1b2c3.png"},"caption":{"type":"string","maxLength":1024,"description":"Text shown beneath the image.","example":"Your receipt for order A1B2C3"}},"example":{"url":"https://cdn.example.com/receipts/a1b2c3.png","caption":"Your receipt for order A1B2C3"}},"WhatsAppVideoSend":{"type":"object","additionalProperties":false,"required":["url"],"description":"A free-form video to send, with an optional caption. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead.\n","properties":{"url":{"type":"string","format":"uri","minLength":1,"description":"Public `https` URL of the video. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. MP4 with H.264 video and AAC audio, up to 16 MB.\n","example":"https://cdn.example.com/unboxing.mp4"},"caption":{"type":"string","maxLength":1024,"description":"Text shown beneath the video.","example":"How to set it up"}},"example":{"url":"https://cdn.example.com/unboxing.mp4","caption":"How to set it up"}},"WhatsAppAudioSend":{"type":"object","additionalProperties":false,"required":["url"],"description":"Free-form audio to send, either as a voice note or as a basic audio file. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead.\n","properties":{"url":{"type":"string","format":"uri","minLength":1,"description":"Public `https` URL of the audio file. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. AAC, AMR, MP3, M4A and OGG (OPUS codec, mono) are supported, up to 16 MB.\n","example":"https://cdn.example.com/voice/9f2e4a.ogg"},"voice":{"type":"boolean","default":false,"description":"Whether to send this as a voice note rather than a basic audio message. A voice note auto-downloads, shows the sender's profile picture, and can be transcribed for the recipient. It requires an `.ogg` file encoded with the OPUS codec; any other format makes transcription fail. Leave it false for an ordinary audio attachment.\n"}},"example":{"url":"https://cdn.example.com/voice/9f2e4a.ogg","voice":true}},"WhatsAppStickerSend":{"type":"object","additionalProperties":false,"required":["url"],"description":"A free-form sticker to send. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead.\n","properties":{"url":{"type":"string","format":"uri","minLength":1,"description":"Public `https` URL of the sticker. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. WebP only: up to 100 KB for a static sticker and 500 KB for an animated one. A sticker carries no caption.\n","example":"https://cdn.example.com/stickers/thumbs-up.webp"}},"example":{"url":"https://cdn.example.com/stickers/thumbs-up.webp"}},"WhatsAppDocumentSend":{"type":"object","additionalProperties":false,"required":["url"],"description":"A free-form document to send, with an optional caption and filename. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead.\n","properties":{"url":{"type":"string","format":"uri","minLength":1,"description":"Public `https` URL of the document. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. Up to 100 MB. PDF, Word, Excel, PowerPoint and plain text render reliably in the WhatsApp client; other file types are transmitted but WhatsApp does not support them.\n","example":"https://cdn.example.com/invoices/a1b2c3.pdf"},"caption":{"type":"string","maxLength":1024,"description":"Text shown beneath the document.","example":"Your invoice for order A1B2C3"},"filename":{"type":"string","minLength":1,"maxLength":100,"description":"Name the recipient sees, including the extension. WhatsApp derives one from the URL when you omit it.\n","example":"invoice-a1b2c3.pdf"}},"example":{"url":"https://cdn.example.com/invoices/a1b2c3.pdf","caption":"Your invoice for order A1B2C3","filename":"invoice-a1b2c3.pdf"}},"WhatsAppInteractiveTypeWrite":{"type":"string","minLength":1,"enum":["button","list","cta_url","carousel","location_request_message","request_contact_info"],"x-enum-varnames":["WhatsAppInteractiveTypeWriteButton","WhatsAppInteractiveTypeWriteList","WhatsAppInteractiveTypeWriteCtaUrl","WhatsAppInteractiveTypeWriteCarousel","WhatsAppInteractiveTypeWriteLocationRequestMessage","WhatsAppInteractiveTypeWriteRequestContactInfo"],"description":"Which kind of interactive message to send.\n\n- `button`: up to three tappable buttons, each sending its own identifier\n  back as an inbound message. Carried in `buttons`.\n- `list`: a single button that opens a menu of rows to choose one from.\n- `cta_url`: a single button that opens a link, so the address stays out of\n  the message body.\n- `carousel`: 2 to 10 media cards the recipient scrolls through sideways,\n  each with its own buttons. Carried in `cards`.\n- `location_request_message`: a single button that asks the recipient to\n  share where they are. A tap arrives as an inbound `location` message.\n- `request_contact_info`: a single button that asks the recipient to share\n  their phone number. A tap arrives as an inbound message carrying the number\n  on `contact_cards`, with `origin` set to `contact_request`.\n\nThe last two name no field of their own: the ask is the whole message, and\n`body_text` is all they carry.\n\nClosed on the write side: a kind Bird cannot send to Meta is rejected rather\nthan accepted and then failed asynchronously.\n","example":"button"},"WhatsAppInteractiveHeaderTypeWrite":{"type":"string","minLength":1,"enum":["text","image","video","document"],"x-enum-varnames":["WhatsAppInteractiveHeaderTypeWriteText","WhatsAppInteractiveHeaderTypeWriteImage","WhatsAppInteractiveHeaderTypeWriteVideo","WhatsAppInteractiveHeaderTypeWriteDocument"],"description":"A header's kind, and which field carries it. `text` is a line of copy; the rest each show a file whose address you give in `url`. A `list` accepts `text` only, and a carousel card accepts `image` or `video` only. Closed on the write side: a kind Bird cannot send to Meta is rejected rather than accepted and then failed asynchronously.\n","example":"image"},"WhatsAppInteractiveHeaderSend":{"type":"object","additionalProperties":false,"required":["type"],"description":"What to show above an interactive message's body. `type` names the kind and the field that carries it: `text` for a line of copy, `url` for the file every other kind shows, the same pairing a template's `parameters` use. A `list` accepts a `text` header only, and any other kind on it is refused.\n","oneOf":[{"properties":{"type":{"const":"text"},"url":{"not":{}}},"required":["type","text"]},{"properties":{"type":{"enum":["image","video","document"]},"text":{"not":{}}},"required":["type","url"]}],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveHeaderTypeWrite"}],"description":"Which kind of header this is, and which field carries it."},"text":{"type":"string","minLength":1,"maxLength":60,"description":"A single line of text above the body. Send it on a `text` header.","example":"New workshop dates announced"},"url":{"type":"string","format":"uri","minLength":1,"description":"Public `https` URL of the file to show. Send it on an `image`, `video` or `document` header. An image must be JPEG or PNG, up to 5 MB; a video, MP4 with H.264 video and AAC audio, up to 16 MB; a document, up to 100 MB, and PDF, Word, Excel, PowerPoint and plain text render reliably in the WhatsApp client while other file types are transmitted but unsupported. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch.\n","example":"https://cdn.example.com/banners/workshop.png"}},"example":{"type":"image","url":"https://cdn.example.com/banners/workshop.png"}},"WhatsAppInteractiveButtonTypeWrite":{"type":"string","minLength":1,"enum":["quick_reply","cta_url"],"x-enum-varnames":["WhatsAppInteractiveButtonTypeWriteQuickReply","WhatsAppInteractiveButtonTypeWriteCtaUrl"],"description":"Which kind of button this is, and which field carries it.\n\n- `quick_reply`: sends its own identifier back as an inbound message. The\n  name `WhatsAppTemplateButtonTypeWrite` already uses for the same control.\n- `cta_url`: opens a link in the recipient's browser.\n\nClosed on the write side: a kind Bird cannot send to Meta is rejected rather\nthan accepted and then failed asynchronously.\n","example":"quick_reply"},"WhatsAppInteractiveQuickReplyButtonSend":{"type":"object","additionalProperties":false,"required":["slug","text"],"description":"One tappable reply button. Tapping it sends `slug` back as an inbound message, which reads as an `interactive_reply`.\n","properties":{"slug":{"type":"string","minLength":1,"maxLength":256,"description":"Your own handle for this button, echoed back on the reply. You choose the value and it is never shown to the recipient, so it can carry whatever your application needs to route the answer. Any characters, up to 256.\n","example":"change-booking"},"text":{"type":"string","minLength":1,"maxLength":20,"description":"The button's label. It must differ from every other button's label in the same message, because the recipient's reply is identified to them by the label they tapped.\n","example":"Change"}},"example":{"slug":"change-booking","text":"Change"}},"WhatsAppInteractiveCtaUrlSend":{"type":"object","additionalProperties":false,"required":["text","url"],"description":"A button that opens a link in the recipient's browser, so a long or opaque address never has to appear in the message body.\n","properties":{"text":{"type":"string","minLength":1,"maxLength":20,"description":"The button's label.","example":"See dates"},"url":{"type":"string","format":"uri","minLength":1,"maxLength":2000,"description":"The address the button opens. It is fixed for every recipient, so per recipient tracking belongs in the address you supply, for example as a query parameter you generate per send.\n","example":"https://example.com/workshops?click_id=a1b2c3"}},"example":{"text":"See dates","url":"https://example.com/workshops?click_id=a1b2c3"}},"WhatsAppInteractiveButtonSend":{"type":"object","additionalProperties":false,"required":["type"],"description":"One button on an interactive message or on a carousel card. `type` names the kind and the field that carries it, so a button kind WhatsApp adds later arrives as another `type` here rather than as a new shape somewhere else.\n","oneOf":[{"properties":{"type":{"const":"quick_reply"},"cta_url":{"not":{}}},"required":["type","quick_reply"]},{"properties":{"type":{"const":"cta_url"},"quick_reply":{"not":{}}},"required":["type","cta_url"]}],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveButtonTypeWrite"}],"description":"Which kind of button this is, and which field carries it."},"quick_reply":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveQuickReplyButtonSend"}],"description":"The button's label and the handle it sends back. Send this on a `quick_reply` button."},"cta_url":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveCtaUrlSend"}],"description":"The button's label and the address it opens. Send this on a `cta_url` button."}},"example":{"type":"quick_reply","quick_reply":{"slug":"change-booking","text":"Change"}}},"WhatsAppInteractiveListRowSend":{"type":"object","additionalProperties":false,"required":["slug","text"],"description":"One option in a list's menu. Choosing it sends `slug` back as an inbound message, which reads as an `interactive_reply`.\n","properties":{"slug":{"type":"string","minLength":1,"maxLength":200,"description":"Your own handle for this option, echoed back on the reply. You choose the value and it is never shown to the recipient. Any characters, up to 200.\n","example":"priority_express"},"text":{"type":"string","minLength":1,"maxLength":24,"description":"The option's label, shown as the row's title in the menu. It must differ from every other row's label and from every button's label in the same message, not merely within its own group; a repeat returns a `422` `WhatsAppInteractiveDuplicateLabel`.\n","example":"Priority Mail Express"},"description":{"type":"string","maxLength":72,"description":"A second line under the label, for detail that will not fit in it.","example":"Next day to 2 days"}},"example":{"slug":"priority_express","text":"Priority Mail Express","description":"Next day to 2 days"}},"WhatsAppInteractiveListSectionSend":{"type":"object","additionalProperties":false,"required":["title","rows"],"description":"One group of options in a list's menu. A menu with a single group still carries a title, which WhatsApp shows above its rows.\n","properties":{"title":{"type":"string","minLength":1,"maxLength":24,"description":"The group's heading, shown above its rows.","example":"As soon as possible"},"rows":{"type":"array","minItems":1,"maxItems":10,"description":"The options in this group. A message carries at most 10 rows across all its groups combined, so this per-group maximum is not additive: more than 10 in total returns a `422` `WhatsAppInteractiveLimitExceeded`. Row labels must be unique across the whole message too, not just within a group.\n","items":{"$ref":"#/components/schemas/WhatsAppInteractiveListRowSend"}}},"example":{"title":"As soon as possible","rows":[{"slug":"priority_express","text":"Priority Mail Express","description":"Next day to 2 days"}]}},"WhatsAppInteractiveListSend":{"type":"object","additionalProperties":false,"required":["button_text","sections"],"description":"A menu of options behind a single button. The recipient taps the button, WhatsApp opens the menu, and choosing one option sends it back as a reply.\n","properties":{"button_text":{"type":"string","minLength":1,"maxLength":20,"description":"The label of the button that opens the menu.","example":"Shipping options"},"sections":{"type":"array","minItems":1,"maxItems":10,"description":"The groups of options in the menu, in the order shown. At most 10 rows across all groups combined, each carrying a label unique across the whole message.\n","items":{"$ref":"#/components/schemas/WhatsAppInteractiveListSectionSend"}}},"example":{"button_text":"Shipping options","sections":[{"title":"As soon as possible","rows":[{"slug":"priority_express","text":"Priority Mail Express","description":"Next day to 2 days"}]},{"title":"I can wait a bit","rows":[{"slug":"ground_advantage","text":"Ground Advantage","description":"2 to 5 days"}]}]}},"WhatsAppInteractiveCardHeaderSend":{"type":"object","additionalProperties":false,"required":["type","url"],"description":"The media at the top of a carousel card. Every card must carry one, and it must be an image or a video: a card takes no text or document header.\n","oneOf":[{"properties":{"type":{"const":"image"}}},{"properties":{"type":{"const":"video"}}}],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveHeaderTypeWrite"}],"description":"Which kind of media this is. A card accepts `image` or `video` only.\n"},"url":{"type":"string","format":"uri","minLength":1,"description":"Public `https` URL of the file to show at the top of the card. An image must be JPEG or PNG, up to 5 MB; a video, MP4 with H.264 video and AAC audio, up to 16 MB. WhatsApp fetches it at send time, on the same terms as a message header's `url`.\n","example":"https://cdn.example.com/plants/blue-echeveria.jpeg"}},"example":{"type":"image","url":"https://cdn.example.com/plants/blue-echeveria.jpeg"}},"WhatsAppInteractiveCardSend":{"type":"object","additionalProperties":false,"required":["header","buttons"],"description":"One card in a carousel: media at the top, optional text of its own, and the buttons under it. A card has no footer, and its position in `cards` is the position it appears in.\n","oneOf":[{"properties":{"buttons":{"maxItems":1,"items":{"properties":{"type":{"const":"cta_url"}}}}}},{"properties":{"buttons":{"items":{"properties":{"type":{"const":"quick_reply"}}}}}}],"properties":{"header":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveCardHeaderSend"}],"description":"The image or video at the top of the card."},"body_text":{"type":"string","minLength":1,"maxLength":160,"pattern":"^[^\\n]*(\\n[^\\n]*){0,2}$","description":"The card's own text, below its media, with at most two line breaks. Optional: a card can carry media and buttons alone.\n","example":"*Blue Echeveria*\n\nA rosette-shaped succulent with powdery blue leaves."},"buttons":{"type":"array","minItems":1,"maxItems":3,"description":"The buttons under the card, in the order given. Either one `cta_url` button or up to three `quick_reply` buttons: the two kinds cannot be mixed on one card. Every card in the carousel must carry the same kinds in the same number, and a carousel whose cards disagree returns a `422` `WhatsAppInteractiveCarouselButtonsMismatch`.\n","items":{"$ref":"#/components/schemas/WhatsAppInteractiveButtonSend"}}}},"WhatsAppInteractiveSend":{"type":"object","additionalProperties":false,"required":["type","body_text"],"description":"An interactive message to send: body text plus something for the recipient to tap. Name the kind in `type` and carry that kind's field alongside it: `buttons`, `list`, `cta_url` or `cards`. The schema pins each `type` to its own field and bars the other kinds', so a request carrying a second kind's field alongside the right one is refused. `location_request_message` and `request_contact_info` name no field: each is a single button asking the recipient for something, so `body_text` is the whole message and every other kind's field is barred. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead.\n","oneOf":[{"properties":{"type":{"const":"button"},"body_text":{"maxLength":1024},"buttons":{"items":{"properties":{"type":{"const":"quick_reply"}}}},"list":{"not":{}},"cta_url":{"not":{}},"cards":{"not":{}}},"required":["type","buttons"]},{"properties":{"type":{"const":"list"},"header":{"properties":{"type":{"const":"text"},"url":{"not":{}}}},"buttons":{"not":{}},"cta_url":{"not":{}},"cards":{"not":{}}},"required":["type","list"]},{"properties":{"type":{"const":"cta_url"},"body_text":{"maxLength":1024},"buttons":{"not":{}},"list":{"not":{}},"cards":{"not":{}}},"required":["type","cta_url"]},{"properties":{"type":{"const":"carousel"},"body_text":{"maxLength":1024},"header":{"not":{}},"footer_text":{"not":{}},"buttons":{"not":{}},"list":{"not":{}},"cta_url":{"not":{}}},"required":["type","cards"]},{"properties":{"type":{"const":"location_request_message"},"body_text":{"maxLength":1024},"header":{"not":{}},"footer_text":{"not":{}},"buttons":{"not":{}},"list":{"not":{}},"cta_url":{"not":{}},"cards":{"not":{}}},"required":["type"]},{"properties":{"type":{"const":"request_contact_info"},"body_text":{"maxLength":1024},"header":{"not":{}},"footer_text":{"not":{}},"buttons":{"not":{}},"list":{"not":{}},"cta_url":{"not":{}},"cards":{"not":{}}},"required":["type"]}],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveTypeWrite"}],"description":"Which kind of interactive message this is, and which field carries it."},"header":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveHeaderSend"}],"description":"Optional content above the body. A `list` accepts a `text` header only; `button` and `cta_url` also accept an image, video or document. A `carousel` accepts none: its cards carry their own media. Neither request kind accepts one.\n"},"body_text":{"type":"string","minLength":1,"maxLength":4096,"description":"The message's main text, required on every kind, and the whole message on `location_request_message` and `request_contact_info`. The WhatsApp client turns any URL it contains into a clickable link. Only a `list` may use the full length; the other kinds cap it at 1024 characters.\n","example":"Your workshop is scheduled for 9am tomorrow."},"footer_text":{"type":"string","minLength":1,"maxLength":60,"description":"Optional small print below the body and above the buttons. A `carousel` and both request kinds take no footer.\n","example":"Dates are subject to change."},"buttons":{"type":"array","minItems":1,"maxItems":3,"description":"The buttons to show, in the order given. Send this on a `button` message, where every button is a `quick_reply`. Every label must be unique within the message; a repeat returns a `422` `WhatsAppInteractiveDuplicateLabel`.\n","items":{"$ref":"#/components/schemas/WhatsAppInteractiveButtonSend"}},"list":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveListSend"}],"description":"The menu to show. Send this on a `list` message."},"cta_url":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveCtaUrlSend"}],"description":"The link button to show. Send this on a `cta_url` message."},"cards":{"type":"array","minItems":2,"maxItems":10,"description":"The cards to show, in the order they appear, left to right. Send this on a `carousel` message, with between 2 and 10 cards. The message's own `body_text` introduces them; a carousel carries no header and no footer of its own.\n","items":{"$ref":"#/components/schemas/WhatsAppInteractiveCardSend"}}},"examples":[{"type":"button","header":{"type":"image","url":"https://cdn.example.com/banners/workshop.png"},"body_text":"Your gardening workshop is scheduled for 9am tomorrow. Use the buttons if you need to reschedule.","footer_text":"Lucky Shrub, your gateway to succulents","buttons":[{"type":"quick_reply","quick_reply":{"slug":"change-booking","text":"Change"}},{"type":"quick_reply","quick_reply":{"slug":"cancel-booking","text":"Cancel"}}]},{"type":"list","header":{"type":"text","text":"Choose a shipping option"},"body_text":"Which shipping option do you prefer?","list":{"button_text":"Shipping options","sections":[{"title":"As soon as possible","rows":[{"slug":"priority_express","text":"Priority Mail Express","description":"Next day to 2 days"}]},{"title":"I can wait a bit","rows":[{"slug":"ground_advantage","text":"Ground Advantage","description":"2 to 5 days"}]}]}},{"type":"cta_url","body_text":"Tap the button below to see the available dates.","footer_text":"Dates are subject to change.","cta_url":{"text":"See dates","url":"https://example.com/workshops?click_id=a1b2c3"}},{"type":"carousel","body_text":"Here are two of our latest arrivals, each under $25:","cards":[{"header":{"type":"image","url":"https://cdn.example.com/plants/blue-echeveria.jpeg"},"body_text":"Blue Echeveria. A rosette-shaped succulent with powdery blue leaves.","buttons":[{"type":"cta_url","cta_url":{"text":"Buy now","url":"https://shop.example.com/blue-echeveria"}}]},{"header":{"type":"image","url":"https://cdn.example.com/plants/zebra-haworthia.jpeg"},"body_text":"Zebra Haworthia. Striking white stripes on deep green leaves.","buttons":[{"type":"cta_url","cta_url":{"text":"Buy now","url":"https://shop.example.com/zebra-haworthia"}}]}]},{"type":"location_request_message","body_text":"Let's start with your pickup. Share your current location, or type an address instead."},{"type":"request_contact_info","body_text":"To confirm your booking we need a number to reach you on. Tap below to share yours."}]},"WhatsAppContactNameSend":{"type":"object","additionalProperties":false,"description":"The contact's name. `formatted_name` is what the card shows, and WhatsApp additionally requires at least one of the parts below it, so a card carrying only a formatted name is rejected.\n","required":["formatted_name"],"properties":{"formatted_name":{"type":"string","minLength":1,"maxLength":256,"description":"The whole name, as the card should render it.","example":"Barbara J. Johnson"},"first_name":{"type":"string","maxLength":256,"example":"Barbara"},"middle_name":{"type":"string","maxLength":256,"example":"Joana"},"last_name":{"type":"string","maxLength":256,"example":"Johnson"},"prefix":{"type":"string","maxLength":64,"example":"Dr."},"suffix":{"type":"string","maxLength":64,"example":"Esq."}}},"WhatsAppContactOrgSend":{"type":"object","additionalProperties":false,"description":"Where the contact works, as the card should record it.","properties":{"company":{"type":"string","maxLength":128,"example":"Lucky Shrub"},"department":{"type":"string","maxLength":128,"example":"Legal"},"title":{"type":"string","maxLength":128,"example":"Lead Counsel"}}},"WhatsAppContactPhoneSend":{"type":"object","additionalProperties":false,"description":"One phone number to put on a contact card.","required":["phone_number"],"properties":{"phone_number":{"type":"string","minLength":1,"maxLength":32,"description":"The number to show. Send it in E.164 to get a card the recipient can message from; any other form still renders, with an invite button.\n","example":"+16505551234"},"type":{"type":"string","maxLength":64,"description":"A label for the number, shown beside it. Free text: WhatsApp defines no vocabulary, and the label is sent exactly as written.\n","example":"Mobile"}}},"WhatsAppContactEmailSend":{"type":"object","additionalProperties":false,"description":"One email address to put on a contact card.","required":["email"],"properties":{"email":{"type":"string","minLength":1,"maxLength":254,"example":"barbara@example.com"},"type":{"type":"string","maxLength":64,"description":"A label for the address, shown beside it. Free text, sent exactly as written.\n","example":"Work"}}},"WhatsAppContactUrlSend":{"type":"object","additionalProperties":false,"description":"One website to put on a contact card.","required":["url"],"properties":{"url":{"type":"string","minLength":1,"maxLength":2048,"description":"The address to show. Not validated as a URL, because a card commonly carries a bare domain.\n","example":"https://luckyshrub.example.com"},"type":{"type":"string","maxLength":64,"description":"A label for the website, shown beside it. Free text, sent exactly as written.\n","example":"Company"}}},"WhatsAppContactAddressSend":{"type":"object","additionalProperties":false,"description":"One postal address to put on a contact card.","properties":{"street":{"type":"string","maxLength":128,"example":"1 Lucky Shrub Way"},"city":{"type":"string","maxLength":128,"example":"Menlo Park"},"state":{"type":"string","maxLength":128,"example":"CA"},"zip":{"type":"string","maxLength":128,"example":"94025"},"country":{"type":"string","maxLength":128,"example":"United States"},"country_code":{"type":"string","maxLength":128,"description":"The country as it should appear on the address, commonly the ISO two-letter code.","example":"US"},"type":{"type":"string","maxLength":64,"description":"A label for the address, shown beside it. Free text, sent exactly as written.\n","example":"Office"}}},"WhatsAppContactCardSend":{"type":"object","additionalProperties":false,"description":"A contact card to send. WhatsApp shows the name on the card and the rest in a profile view the recipient opens from it.\nA card carrying a phone number renders buttons that message or save the contact; a card without one can only be added to an address book.\n","required":["name"],"properties":{"name":{"allOf":[{"$ref":"#/components/schemas/WhatsAppContactNameSend"}]},"org":{"allOf":[{"$ref":"#/components/schemas/WhatsAppContactOrgSend"}],"description":"Where the contact works."},"birthday":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"The contact's birthday, as `YYYY-MM-DD`. WhatsApp rejects any other shape, and a date no calendar holds is rejected too.\n","example":"1999-01-23"},"phone_numbers":{"type":"array","maxItems":10,"description":"The numbers on the card. A number in E.164 renders a button that opens a WhatsApp chat with it; one that is not renders an invite instead.\n","items":{"$ref":"#/components/schemas/WhatsAppContactPhoneSend"}},"emails":{"type":"array","maxItems":10,"items":{"$ref":"#/components/schemas/WhatsAppContactEmailSend"}},"urls":{"type":"array","maxItems":10,"items":{"$ref":"#/components/schemas/WhatsAppContactUrlSend"}},"addresses":{"type":"array","maxItems":10,"items":{"$ref":"#/components/schemas/WhatsAppContactAddressSend"}}},"example":{"name":{"formatted_name":"Barbara J. Johnson","first_name":"Barbara","last_name":"Johnson"},"phone_numbers":[{"phone_number":"+16505551234","type":"Mobile"}]}},"WhatsAppMessageSendRequest":{"type":"object","additionalProperties":false,"description":"A WhatsApp message to send. Carry exactly one kind of content: a request with none returns a `422` `WhatsAppContentRequired`, and one carrying more than one returns a `422` `WhatsAppContentAmbiguous`. The schema does not express that constraint, because which combinations are available depends on the content types your workspace can send.\n","required":["to"],"properties":{"to":{"type":"string","minLength":1,"description":"The message recipient: a phone number in E.164 format (for example `+31612345678`), the recipient's business-scoped user ID (for example `US.13491208655302741918`), which addresses a WhatsApp user whose phone number you do not have, or a WhatsApp group ID (for example `wag_01krdgeqcxet5s7t44vh8rt9mg`), which sends to every participant of that group. A value that is none of these returns a `422` `WhatsAppInvalidRecipient`. One-time-passcode templates require a phone number and return a `422` `WhatsAppRecipientNotSupportedForTemplate` when sent to a business-scoped user ID. A group ID naming no group this workspace holds returns a `404` `WhatsAppGroupNotFound`, and one whose group is not active returns a `409` `WhatsAppGroupNotActive`. Content a group cannot take is refused ahead of both, so a group ID paired with interactive content returns the `422` below whether or not the group exists.\n","example":"+31612345678"},"from":{"type":"string","minLength":1,"description":"The business phone number to send from, in E.164 format. Omit it for a Bird-managed template, which selects its own number from its category: setting it there returns a `422` `WhatsAppSenderNotAllowed`. Every other send, whether free-form content of any kind or a template your workspace authored, requires it, and the number must be one this workspace owns. Omitting it returns a `422` `WhatsAppSenderRequired`, and naming a number this workspace cannot send from returns a `422` `WhatsAppSenderNotFound`. Naming a number this workspace owns but that sits on a different WhatsApp Business Account than an authored template returns a `422` `WhatsAppSenderWABAMismatch`. A number this workspace holds but has not finished connecting returns a `422` `WhatsAppSenderNotConnected`. Omit it for a group send too: the group sends on its own number, so naming one returns a `422` `WhatsAppSenderNotAllowed`.\n","example":"+13124495648"},"template":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateSend"}],"description":"The template to send. A Bird-managed template selects the sender number from the template's category, so `from` must be omitted. A template is the only content deliverable outside a customer service window. A group send takes a template your workspace authored in any category but authentication: WhatsApp does not deliver an authentication template to a group, which returns a `422` `WhatsAppGroupContentNotSupported`. A Bird-managed template sends from a Bird-owned number that no group is scoped to, so addressing one to a group returns a `422` `WhatsAppInvalidRecipient`.\n"},"text":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTextSend"}],"description":"Free-form text to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. A send into a closed window is refused with a `422` `WhatsAppServiceWindowClosed` before anything is created or charged; one whose window closes between accept and dispatch fails asynchronously, with `service_window_expired` on the message's `last_error`.\n"},"image":{"allOf":[{"$ref":"#/components/schemas/WhatsAppImageSend"}],"description":"A free-form image to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. A send into a closed window is refused with a `422` `WhatsAppServiceWindowClosed` before anything is created or charged; one whose window closes between accept and dispatch fails asynchronously, with `service_window_expired` on the message's `last_error`.\n"},"video":{"allOf":[{"$ref":"#/components/schemas/WhatsAppVideoSend"}],"description":"A free-form video to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. A send into a closed window is refused with a `422` `WhatsAppServiceWindowClosed` before anything is created or charged; one whose window closes between accept and dispatch fails asynchronously, with `service_window_expired` on the message's `last_error`.\n"},"audio":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAudioSend"}],"description":"Free-form audio to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. A send into a closed window is refused with a `422` `WhatsAppServiceWindowClosed` before anything is created or charged; one whose window closes between accept and dispatch fails asynchronously, with `service_window_expired` on the message's `last_error`.\n"},"sticker":{"allOf":[{"$ref":"#/components/schemas/WhatsAppStickerSend"}],"description":"A free-form sticker to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. A send into a closed window is refused with a `422` `WhatsAppServiceWindowClosed` before anything is created or charged; one whose window closes between accept and dispatch fails asynchronously, with `service_window_expired` on the message's `last_error`.\n"},"document":{"allOf":[{"$ref":"#/components/schemas/WhatsAppDocumentSend"}],"description":"A free-form document to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. A send into a closed window is refused with a `422` `WhatsAppServiceWindowClosed` before anything is created or charged; one whose window closes between accept and dispatch fails asynchronously, with `service_window_expired` on the message's `last_error`.\n"},"location":{"allOf":[{"$ref":"#/components/schemas/WhatsAppLocationSend"}],"description":"A free-form location to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. A send into a closed window is refused with a `422` `WhatsAppServiceWindowClosed` before anything is created or charged; one whose window closes between accept and dispatch fails asynchronously, with `service_window_expired` on the message's `last_error`.\n"},"interactive":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveSend"}],"description":"Free-form interactive content to send instead of a template: body text plus reply buttons, a menu, a link button, media cards, or a single button asking the recipient to share their location or their phone number. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. A send into a closed window is refused with a `422` `WhatsAppServiceWindowClosed` before anything is created or charged; one whose window closes between accept and dispatch fails asynchronously, with `service_window_expired` on the message's `last_error`. WhatsApp does not deliver interactive content to a group, so a group recipient returns a `422` `WhatsAppGroupContentNotSupported`.\n"},"contact_cards":{"type":"array","minItems":1,"maxItems":5,"description":"Contact cards to send instead of a template. Up to five: WhatsApp accepts far more, and a message that opens as one name plus a count of the rest is not a card the recipient will read.\n","items":{"$ref":"#/components/schemas/WhatsAppContactCardSend"}},"in_reply_to_message_id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppMessageID"}],"description":"Quote a message the contact will see above this one, the way replying in the WhatsApp client does. Name a message from the same conversation: one this workspace sent to this recipient, or received from them. Any content quotes, template or free-form. The quote is resolved before the send is accepted, so a quote WhatsApp cannot render fails this request rather than the message. An id naming no message this workspace holds, or one older than the 15 days we keep provider ids for, answers `404`; a message that never reached WhatsApp, or one from a different conversation than this send's `to` and `from`, answers `422`. Nothing is charged either way.\n","example":"wam_01kya19eknftrs2s6p82asmvnh"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"maxItems":20,"description":"Structured `{name, value}` labels for filtering. Tags become first-class query dimensions: filter the list endpoint by tag name. Maximum 20 tags per send. Use tags for low-cardinality dimensions (`category`, `experiment_variant`). For arbitrary structured context you do not need as a filter dimension, use `metadata` instead.\n"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary JSON object stored on the message and returned on API reads. Maximum 2 KB serialized. Use metadata for per-send context like internal IDs and foreign keys. For low-cardinality filterable labels, use `tags` instead.\n"}},"example":{"to":"+31612345678","template":{"slug":"bird_otp","language":"en","components":[{"type":"body","parameters":[{"type":"text","text":"1234"}]},{"type":"button","parameters":[{"type":"text","text":"1234"}]}]}}},"WhatsAppReadReceiptRequest":{"type":"object","additionalProperties":false,"description":"What to acknowledge on the inbound message. An absent body and `{}` mean the same thing: mark the message read and show nothing.\n","properties":{"typing_indicator":{"type":"boolean","default":false,"example":true,"description":"Show a typing indicator to the contact as well as marking the message read. WhatsApp clears it when you send your next message, or after 25 seconds, whichever comes first. Only ask for one if you are about to reply.\n"}}},"WhatsAppReadReceipt":{"type":"object","additionalProperties":false,"required":["typing_indicator"],"description":"The acknowledgement Bird accepted. There is no status to poll afterwards: WhatsApp reports nothing about a read receipt.\n","properties":{"typing_indicator":{"type":"boolean","description":"Whether a typing indicator was requested alongside the read receipt.","example":true}}},"WhatsAppEventType":{"type":"string","minLength":1,"description":"Message timeline event type:\n\n- `whatsapp.accepted`: The API accepted the request.\n- `whatsapp.sent`: The message reached the WhatsApp network.\n- `whatsapp.delivered`: Delivery to the recipient's device was confirmed.\n- `whatsapp.read`: The message was read. On an outbound message the recipient\n  opened it; on an inbound one Bird acknowledged it to WhatsApp for the\n  business, which is what a read receipt records.\n- `whatsapp.failed`: Delivery failed permanently.\n- `whatsapp.rejected`: The message was refused before sending and not charged.\n- `whatsapp.received`: An inbound message arrived from the contact.\n\nThis is an open enum. Accept unrecognized values.\n","x-extensible-enum":["whatsapp.accepted","whatsapp.delivered","whatsapp.failed","whatsapp.read","whatsapp.received","whatsapp.rejected","whatsapp.sent"],"example":"whatsapp.delivered"},"WhatsAppEventID":{"type":"string","minLength":1,"pattern":"^ev_[0-9a-hjkmnp-tv-z]{26}$","example":"ev_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppEvent":{"type":"object","additionalProperties":false,"required":["id","type","occurred_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppEventID","description":"ID of the event, unique within the message's timeline."},"type":{"$ref":"#/components/schemas/WhatsAppEventType","readOnly":true},"occurred_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this event occurred."},"recipient":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"description":"The participant this confirmation is about, on a group message. Present only on `whatsapp.delivered` and `whatsapp.read`, the two events a group send fans out: one per participant, so a group of eight produces up to eight of each. The rest describe the message as a whole and carry no recipient, because there is one hand-off to the WhatsApp network and one way for that to be refused. Absent on a one-to-one message, whose `to` already names its recipient. Never carries `group_id`: the group belongs to the message's `to`, not to a participant.\n"},"error":{"$ref":"#/components/schemas/WhatsAppError","description":"Failure detail. Present only on `whatsapp.failed` and `whatsapp.rejected` events."}}},"WhatsAppEventList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Timeline events for this WhatsApp message, in chronological order. The timeline is bounded and returned in full; this list is not paginated.","items":{"$ref":"#/components/schemas/WhatsAppEvent"}}}},"WhatsAppReactionUpsert":{"type":"object","additionalProperties":false,"required":["emoji"],"properties":{"emoji":{"type":"string","minLength":1,"maxLength":64,"description":"The emoji to place, as the character itself. Replaces your existing reaction on this message if you have one. To take a reaction back entirely, delete it rather than sending an empty value. WhatsApp takes exactly one emoji, so a value carrying more than one is refused with a `422` rather than sent. The length cap is generous because a single joined emoji is many code points: a couple-kissing one carrying two skin tones is ten, which is why the cap alone cannot express the limit.\n","example":"👍"}}},"WhatsAppReactionEventID":{"type":"string","minLength":1,"pattern":"^war_[0-9a-hjkmnp-tv-z]{26}$","example":"war_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppReactionAccepted":{"type":"object","additionalProperties":false,"readOnly":true,"required":["id","emoji"],"description":"A reaction as accepted, which WhatsApp has not applied yet and may still refuse. It names the reaction-log entry the request created, so a caller that places two changes on one message can tell which entry is which; read the message's `reactions` for what currently stands, or its reaction log for what became of this one.\n","properties":{"id":{"$ref":"#/components/schemas/WhatsAppReactionEventID","description":"ID of the reaction-log entry this request created, matching the `id` that entry carries in [List reaction events for a WhatsApp message](/docs/api/reference/list-whatsapp-message-reaction-events).\n"},"emoji":{"type":"string","minLength":1,"description":"The emoji as accepted, echoing the one the request carried.","example":"👍"}}},"WhatsAppReactionEventStatus":{"type":"string","minLength":1,"enum":["received","sent","failed","rejected"],"description":"What became of one reaction change:\n\n- `received` means the contact placed or removed the reaction and WhatsApp\n  told us about it. Every inbound entry carries this.\n- `sent` means your reaction reached WhatsApp. Reactions have no delivery or\n  read receipt, so this is as far as an outbound entry gets.\n- `failed` means WhatsApp refused it. `error` says why, most often because the\n  contact deleted the message. The grounds we can check for ourselves (a\n  message you sent, one that is itself a reaction, one over 30 days old) are\n  refused when you ask, so they do not reach here.\n- `rejected` means we refused it before it reached WhatsApp, so nothing was\n  sent. `error` says why.\n\nOnly `received` and `sent` entries change what stands on the message, so those\nare the ones the message's `reactions` are folded from.\n","example":"received"},"WhatsAppReactionEvent":{"type":"object","additionalProperties":false,"required":["id","emoji","status","from","occurred_at"],"description":"One change to a reaction on a message: a reaction placed, replaced by a different emoji, or taken back. Entries are never edited, so a sender who reacts twice and then removes it leaves three of them.\n","properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppReactionEventID","description":"ID of this entry, unique within the message's reaction log."},"emoji":{"type":["string","null"],"readOnly":true,"description":"The emoji this entry placed, as WhatsApp sent it and not normalized. Null when the entry took a reaction back rather than placing one. Always present, so null is the removal itself rather than a value we are missing.\n","example":"👍"},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppReactionEventStatus"}]},"from":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"description":"Who made the change. Your business number on a reaction you placed, the contact on one they placed.\n"},"error":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppError","description":"Why the change did not take effect. Always carried by a `failed` or `rejected` entry, and never by any other, so a failure always says what went wrong. Absent rather than null on the entries that did take effect. The schema leaves it optional because that is a conditional the generators do not express. `code` is drawn from the vocabulary a message send shares, less its two billing codes: a reaction is never charged, so neither `insufficient_balance` nor `price_not_found` appears here.\n"},"occurred_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the change was made.","example":"2026-08-28T19:01:10Z"}}},"WhatsAppReactionEventList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Changes to this message's reactions, newest first.","items":{"$ref":"#/components/schemas/WhatsAppReactionEvent"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppGroupStatus":{"type":"string","minLength":1,"enum":["pending","active","suspended","deleted","failed"],"description":"Where the group stands. Values, in lifecycle order:\n\n- `pending` means the request to create the group has been accepted and WhatsApp has not confirmed it yet. The group has no invite link, and it cannot be messaged or changed.\n- `active` means the group exists at WhatsApp and carries an invite link. Only an active group can be messaged.\n- `suspended` means WhatsApp has stopped activity in the group, which it does when a group breaks its policies. Sends fail while it lasts, and WhatsApp can lift it on its own.\n- `deleted` means the group is gone, either because you deleted it or because WhatsApp removed it. Every participant lost access, and the state is terminal.\n- `failed` means WhatsApp refused to create the group; `last_operation.last_error` says why. The state is terminal, so create another group rather than retrying this one.\n","example":"active"},"WhatsAppNumberID":{"type":"string","minLength":1,"pattern":"^wan_[0-9a-hjkmnp-tv-z]{26}$","example":"wan_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppGroupJoinApprovalMode":{"type":"string","minLength":1,"enum":["auto_approve","approval_required"],"description":"How someone opening the invite link gets in:\n\n- `auto_approve` means they join the moment they open the link. This is the default when the group is created.\n- `approval_required` means opening the link raises a join request you approve or reject.\n\nFixed when the group is created.\n","example":"auto_approve"},"WhatsAppGroupOperationType":{"type":"string","minLength":1,"enum":["create","settings_update","delete","remove"],"description":"What was asked of the thing carrying the operation:\n\n- `create` and `delete` act on the group itself.\n- `settings_update` changes the group's subject, description or picture.\n- `remove` takes one participant out of the group.\n","example":"settings_update"},"WhatsAppGroupOperationStatus":{"type":"string","minLength":1,"enum":["pending","success","failed"],"description":"How the operation ended:\n\n- `pending` means WhatsApp accepted the request and has not reported back. Another\n  operation on the same thing is refused while this lasts.\n- `success` means WhatsApp applied everything asked of it. A change whose success\n  removes its own carrier is never seen in this state: a removed participant and an\n  unpinned message leave their lists, and the entry going away is the confirmation.\n- `failed` means WhatsApp applied none of it, or only part: `last_error` says why,\n  and on a `settings_update` the per-field `results` say which fields did apply.\n","example":"pending"},"WhatsAppGroupOperationField":{"type":"string","minLength":1,"enum":["subject","description","profile_picture_url"],"description":"Which of the group's settings a `settings_update` result reports on.","example":"subject"},"WhatsAppGroupError":{"type":"object","additionalProperties":false,"readOnly":true,"required":["description"],"description":"Why a change to a group did not take effect. Meta documents no code vocabulary for a group refusal, since every sample payload carries an undocumented `code` beside its message, so this relays what it said rather than classifying it, the way a template submission failure does.\n","properties":{"description":{"type":"string","minLength":1,"readOnly":true,"description":"WhatsApp's own explanation of the refusal, passed through. Show it to the person who asked for the change; never match on its text. Carries Bird's own words instead when the failure was Bird's verdict, such as a confirmation that never arrived.\n","example":"Group subject contains content that cannot be used."},"meta_error_code":{"type":["string","null"],"readOnly":true,"description":"WhatsApp's most specific code for the refusal: its error subcode where it sent one, otherwise its top-level code. Treat it as an opaque string. Null when the failure was Bird's own verdict rather than a WhatsApp refusal.\n","example":"2388024"}}},"WhatsAppGroupOperationResult":{"type":"object","additionalProperties":false,"required":["field","applied"],"description":"What became of one field in a `settings_update`. WhatsApp applies each field separately, so a single update can leave some applied and others refused.\n","properties":{"field":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroupOperationField"}],"readOnly":true,"description":"The setting this result reports on."},"applied":{"type":"boolean","readOnly":true,"description":"Whether WhatsApp applied this field. False when it refused this one, whatever it did with the others.","example":false},"error":{"$ref":"#/components/schemas/WhatsAppGroupError","readOnly":true,"description":"Why WhatsApp refused this field. Present only when `applied` is false."}}},"WhatsAppGroupOperation":{"type":"object","additionalProperties":false,"readOnly":true,"required":["type","status","requested_at"],"description":"The last change asked of this group or participant, and where it got to. WhatsApp confirms a change on a webhook rather than in its reply, so an operation is `pending` until that arrives. While it is, another change to the same thing is refused with a `409` `WhatsAppGroupUpdateInProgress`; a change to a different participant is not, so several removals can be in flight at once. Absent on something nothing has been asked of yet.\n","properties":{"type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroupOperationType"}],"readOnly":true,"description":"What was asked."},"status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroupOperationStatus"}],"readOnly":true,"description":"Where it got to. `pending` is what a client shows as in-progress, and what refuses the next change."},"requested_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When Bird accepted the request.","example":"2026-08-27T14:02:11Z"},"settled_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When WhatsApp reported the outcome. Null while `pending`.","example":"2026-08-27T14:02:14Z"},"results":{"type":"array","readOnly":true,"maxItems":3,"description":"Per-field outcomes, on a `settings_update` that has settled. One entry per field the update carried, so a client can put a refusal next to the input it came from. Absent on every other operation type, which change one thing and report it on `status`.\n","items":{"$ref":"#/components/schemas/WhatsAppGroupOperationResult"}},"last_error":{"$ref":"#/components/schemas/WhatsAppGroupError","readOnly":true,"description":"Why the operation failed as a whole. Present when `status` is `failed`, including when the confirmation never arrived and Bird gave up waiting. A `settings_update` that failed on some fields and not others carries the per-field detail in `results`.\n"}}},"WhatsAppGroupParticipant":{"type":"object","additionalProperties":false,"required":["bsuid"],"description":"Someone who joined the group. The business number that created the group is its admin and is not listed.","properties":{"bsuid":{"type":"string","minLength":1,"readOnly":true,"description":"Business-scoped user ID, Meta's identifier for this person against your business. The one identifier every participant has: WhatsApp always sends it, and it is stable for as long as they are in the group.\n","example":"BR.1566655121691972"},"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"Phone number in E.164 format. Absent when WhatsApp withholds it, which it does for anyone who has not shared their number with your business, so a group is normally a mix of participants with one and without.\n","example":"+16505551234"},"username":{"type":"string","minLength":1,"readOnly":true,"description":"The WhatsApp username this person chose. Absent when they have none, and not an identifier to address them by: it is theirs to change, so it names them in a list rather than keying anything.\n","example":"jim.almeida"},"last_operation":{"$ref":"#/components/schemas/WhatsAppGroupOperation","readOnly":true,"description":"A removal asked of this participant that has not taken effect: `pending` while WhatsApp has yet to confirm it, or `failed` when WhatsApp refused. Never `success`, because a removal that succeeds takes the participant off this list: the entry disappearing is what says it worked. A `pending` removal refuses a second removal of the same person while leaving other participants free to be removed at the same time.\n"}}},"WhatsAppGroupPinnedMessage":{"type":"object","additionalProperties":false,"description":"A message pinned at the top of a group's chat.","required":["message_id","pinned_until"],"properties":{"message_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppMessageID"}],"description":"The pinned message, as returned in the send response's `id`."},"pinned_until":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the pin is due to lapse, projected from the `duration_days` the pin was asked for. An entry stays listed until it is unpinned, so a time in the past means WhatsApp has already taken the message off the chat.\n","example":"2026-09-01T09:14:52Z"}}},"WhatsAppGroup":{"allOf":[{"type":"object","description":"A WhatsApp group your business created and administers. People join by opening its invite link, not by being added.\n","required":["id","whatsapp_number_id","waba","subject","status","join_approval_mode","participant_count"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroupID"}],"readOnly":true,"description":"Unique identifier for the group. Accepted by every `/v1/whatsapp/groups/{group_id}` operation, and as `to` when sending a message to the group."},"whatsapp_number_id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberID"}],"readOnly":true,"description":"The business number that created the group. It is the group's admin and the number every message to the group is sent from. Fixed when the group is created.\n"},"waba":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Meta's identifier for the WhatsApp Business Account recorded when the group was created. This is a historical snapshot, not a live account directory projection. Null for a number we operate on your behalf, whose account is not yours to see.\n","example":"102290129340398"},"subject":{"type":"string","minLength":1,"maxLength":128,"description":"The group's name, shown to participants and to anyone who opens the invite link.","example":"New Purchase Inquiry"},"description":{"type":["string","null"],"maxLength":2048,"description":"The group's description, shown alongside the subject. Null when the group has none.","example":"Jim would like to learn about new car purchase options for current year models."},"status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroupStatus"}],"readOnly":true,"description":"Where the group stands. A group is messageable only while it is `active`."},"join_approval_mode":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroupJoinApprovalMode"}],"readOnly":true,"description":"Whether opening the invite link joins the group outright or raises a join request to approve."},"invite_link":{"type":["string","null"],"readOnly":true,"description":"The link that lets someone join the group, which is the only way in. A group has one link at a time. Null while the group is `pending`, since WhatsApp issues the link when it confirms the group. Rotating it through `POST /v1/whatsapp/groups/{group_id}/invite-link/rotate` replaces it, and every link the group had before then stops working.\n","example":"https://chat.whatsapp.com/JZm4S9tCkQx2LpVr7Ny8Ab"},"participants":{"type":"array","readOnly":true,"description":"Who is in the group, as of the last update WhatsApp sent, and the whole set rather than a page: WhatsApp holds a group to a handful of people, so there is never a page's worth to return. The business number that created the group is its admin and is not listed.\n","items":{"$ref":"#/components/schemas/WhatsAppGroupParticipant"}},"participant_count":{"type":"integer","readOnly":true,"minimum":0,"description":"How many people are in the group, excluding your business.","example":6},"pinned_messages":{"type":"array","readOnly":true,"description":"The group's pins, newest first. WhatsApp holds a few at once, and pinning past that unpins the oldest rather than refusing. No entry here is merely requested. An entry stays listed until it is unpinned, so one whose `pinned_until` has passed is still listed after WhatsApp has taken it off the chat.\n","items":{"$ref":"#/components/schemas/WhatsAppGroupPinnedMessage"}},"profile_picture_url":{"type":["string","null"],"readOnly":true,"description":"Address of the group's picture, as WhatsApp serves it. Null when the group has none.","example":"https://media.example.com/whatsapp/groups/JZm4S9tCkQx2.jpg"},"last_operation":{"$ref":"#/components/schemas/WhatsAppGroupOperation","readOnly":true,"description":"The last create, settings change or delete asked of the group. `pending` while WhatsApp has yet to confirm it, which is what a client shows as in-progress and what refuses the next change to the group. A settings change carries per-field `results`, since WhatsApp can refuse one field and apply the others. A removal reports on the participant's own entry rather than here, so several can be in flight at once. Pinning is synchronous and reports nothing.\n"},"suspended_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When WhatsApp suspended the group. Present only while the group is `suspended`, and gone once WhatsApp lifts the suspension.","example":"2026-08-20T11:04:00Z"}}},{"$ref":"#/components/schemas/Timestamps"}]},"WhatsAppGroupList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The groups your workspace created, newest first.","items":{"$ref":"#/components/schemas/WhatsAppGroup"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppGroupCreate":{"type":"object","additionalProperties":false,"description":"The group to create. WhatsApp issues the invite link, and people join by opening it, so a create request names no participants.\n","required":["whatsapp_number_id","subject"],"properties":{"whatsapp_number_id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberID"}],"description":"The business number that will own and administer the group, as its id in `GET /v1/whatsapp/numbers`. It must be a number your workspace can send from, and WhatsApp must have granted it Official Business Account status; a number without that status returns a `412` `WhatsAppGroupsNotEligible`. The number cannot be changed afterwards, and every message to the group is sent from it.\n"},"subject":{"type":"string","minLength":1,"maxLength":128,"description":"The group's name, shown to participants and to anyone who opens the invite link. Surrounding whitespace is trimmed.","example":"New Purchase Inquiry"},"description":{"type":"string","maxLength":2048,"description":"The group's description, shown alongside the subject.","example":"Jim would like to learn about new car purchase options for current year models."},"join_approval_mode":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroupJoinApprovalMode"}],"description":"Whether opening the invite link joins the group outright, or raises a join request for you to approve. Defaults to `auto_approve`. It cannot be changed once the group exists.\n"}}},"WhatsAppGroupUpdate":{"type":"object","additionalProperties":false,"description":"The changes to make. Fields you omit are left as they are. WhatsApp applies each field separately, so one can be rejected while the others take effect; the group's `updated_at` moves when a change lands.\n","properties":{"subject":{"type":"string","minLength":1,"maxLength":128,"description":"A new name for the group. Participants see the change in the group's chat.","example":"Watch Enthusiasts"},"description":{"type":["string","null"],"maxLength":2048,"description":"A new description for the group. Send `null` to clear it.","example":"Discuss the latest timepieces and share reviews."},"profile_picture_url":{"type":["string","null"],"description":"A new picture for the group, naming a file in your workspace's media library. WhatsApp takes a square JPEG of at least 192 by 192 pixels and up to 5 MB; anything else returns a `422`. Send `null` to remove the current picture.\n","example":"https://media.example.com/whatsapp/groups/square.jpg"}}},"WhatsAppGroupInviteLink":{"type":"object","additionalProperties":false,"description":"A group's invite link.","required":["invite_link"],"properties":{"invite_link":{"type":"string","minLength":1,"readOnly":true,"description":"The group's one invite link. Every link the group had before this one stops working.","example":"https://chat.whatsapp.com/JZm4S9tCkQx2LpVr7Ny8Ab"}}},"WhatsAppGroupPinnedMessageCreate":{"type":"object","additionalProperties":false,"description":"A message to pin at the top of the group's chat.","required":["message_id"],"properties":{"message_id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppMessageID"}],"description":"The message to pin. It has to be one this group carries: a message in another group, or a one-to-one message, returns a `422` `WhatsAppMessageNotInGroup`.\n"},"duration_days":{"type":"integer","minimum":1,"maximum":30,"default":7,"description":"How many days the message stays pinned before WhatsApp unpins it, from 1 to 30.","example":7}}},"WhatsAppGroupJoinRequestID":{"type":"string","minLength":1,"pattern":"^wgj_[0-9a-hjkmnp-tv-z]{26}$","example":"wgj_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppGroupJoinRequest":{"type":"object","additionalProperties":false,"description":"Someone waiting to be let into a group that requires approval.","required":["id","bsuid","created_at"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroupJoinRequestID"}],"readOnly":true,"description":"Unique identifier for the join request. Pass it to the batch-approve and batch-reject operations."},"bsuid":{"type":"string","minLength":1,"readOnly":true,"description":"Business-scoped user ID, Meta's identifier for this person against your business. The one identifier every request has, and the one that carries over to `participants` if you approve it.\n","example":"BR.1566655121691972"},"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"Phone number in E.164 format. Absent when WhatsApp withholds it, which it does for anyone who has not shared their number with your business.\n","example":"+16505551234"},"username":{"type":"string","minLength":1,"readOnly":true,"description":"The WhatsApp username this person chose. Absent when they have none, and theirs to change, so it names them in a list rather than keying anything.\n","example":"jim.almeida"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the request was made.","example":"2026-08-24T10:07:57Z"}}},"WhatsAppGroupJoinRequestList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The join requests still waiting for a decision, oldest first.","items":{"$ref":"#/components/schemas/WhatsAppGroupJoinRequest"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppGroupJoinRequestDecision":{"type":"object","additionalProperties":false,"description":"The join requests to decide on.","required":["join_request_ids"],"properties":{"join_request_ids":{"type":"array","minItems":1,"maxItems":50,"description":"The join requests to act on, as returned by `GET /v1/whatsapp/groups/{group_id}/join-requests`. Each is decided on its own, so one can fail while the rest succeed. An ID that names no waiting request returns a `422` `WhatsAppGroupJoinRequestNotFound`. The 50 is Bird's own request bound, not a WhatsApp one: how many people the group can hold does not limit how many can queue at its link, so a rejection sweep is not held to the size of the group it is refusing entry to.\n","items":{"$ref":"#/components/schemas/WhatsAppGroupJoinRequestID"}}}},"WhatsAppGroupJoinRequestFailure":{"type":"object","additionalProperties":false,"description":"A join request the decision could not be applied to.","required":["join_request_id","error"],"properties":{"join_request_id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroupJoinRequestID"}],"readOnly":true,"description":"The join request that was not decided."},"error":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroupError"}],"readOnly":true,"description":"Why WhatsApp refused. The common one is a person who has not accepted WhatsApp's current terms, which no retry fixes.\n"}}},"WhatsAppGroupJoinRequestDecisionResult":{"type":"object","additionalProperties":false,"description":"What happened to each join request in the batch. WhatsApp decides them one by one, so a batch can be part-applied: the requests it accepted are in `decided`, and the rest are in `failed` with the reason.\n","required":["decided","failed"],"properties":{"decided":{"type":"array","readOnly":true,"description":"The join requests WhatsApp accepted the decision for. A person approved here can enter the group; a person rejected here sees the join button again.","items":{"$ref":"#/components/schemas/WhatsAppGroupJoinRequestID"}},"failed":{"type":"array","readOnly":true,"description":"The join requests WhatsApp refused, each with its reason. Empty when the whole batch was applied.","items":{"$ref":"#/components/schemas/WhatsAppGroupJoinRequestFailure"}}}},"WhatsAppTemplateExampleParameter":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateParameterType"}],"readOnly":true,"description":"The kind of value this parameter accepts."},"text":{"type":"string","minLength":1,"readOnly":true,"description":"An example value for a text parameter. Present when `type` is `text`.","example":"123456"},"url":{"type":"string","format":"uri","readOnly":true,"description":"The address of the file a media header shows, as it was given when the header was authored rather than WhatsApp's copy of it. Present when `type` is `image`, `video`, `gif` or `document`.\n","example":"https://www.example.com/holiday/banner.jpg"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"The named placeholder this example fills. Present whenever the template declares named parameters, which is what a send must name; absent only for a positional template, whose values go in `{{n}}` order.\n","example":"first_name"}}},"WhatsAppTemplateButton":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["url","quick_reply","phone_number","otp","copy_code","request_contact_info"],"description":"The button's behavior.\n\n- `url`: opens a link.\n- `quick_reply`: sends its own label back to you as an inbound message.\n- `phone_number`: dials the number it carries.\n- `otp`: copies a one-time passcode. It belongs only on an authentication\n  template, and that template takes no other button type.\n- `copy_code`: copies a coupon code to the recipient's clipboard. It\n  belongs only on a marketing template, which takes at most one.\n- `request_contact_info`: asks the recipient to share the phone number\n  their WhatsApp account carries. It belongs only on a utility or\n  marketing template, as that template's only button.\n\nThis is an open enum. Accept unrecognized values.\n","example":"url"},"otp_type":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["copy_code"],"description":"How the recipient receives the one-time passcode. Present on authentication-template OTP buttons.","example":"copy_code"},"text":{"type":"string","minLength":1,"readOnly":true,"description":"The button's label. Absent on an authentication template's passcode button until the language has been submitted, since WhatsApp writes that label itself. Absent on a `request_contact_info` draft for a related reason: WhatsApp fixes that label, so a draft that carried it reads back without it. Once the language is submitted, this carries the label WhatsApp wrote, which is `Share Contact Info` in every language today.\n","example":"Copy code"},"url":{"type":"string","minLength":1,"readOnly":true,"description":"The address the button opens, with any variable placeholder shown inline. Present on link buttons.","example":"https://www.example.com/orders/{{1}}"},"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"The number the button dials. Present on dial buttons.","example":"+14155550100"},"example_parameters":{"type":"array","readOnly":true,"description":"Example values for this button's variables, in placeholder order. Present when the button address has variables, and on a `copy_code` button, where the single value is the sample coupon code WhatsApp reviewed.\n","items":{"$ref":"#/components/schemas/WhatsAppTemplateExampleParameter"}}}},"WhatsAppTemplateCardComponent":{"type":"object","additionalProperties":false,"required":["type"],"description":"One content block inside a carousel card.","properties":{"type":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["header","body","buttons"],"description":"The card block's type.","example":"header"},"format":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["image","video"],"description":"The card header's content type. Present on a card's header block.","example":"image"},"text":{"type":"string","minLength":1,"readOnly":true,"description":"The block's text content, with any variable placeholders shown inline.","example":"Chronograph, brown leather"},"example_parameters":{"type":"array","readOnly":true,"description":"Example values for this block's variables, in placeholder order.","items":{"$ref":"#/components/schemas/WhatsAppTemplateExampleParameter"}},"buttons":{"type":"array","readOnly":true,"description":"The buttons this card carries. Present on a card's buttons block.","items":{"$ref":"#/components/schemas/WhatsAppTemplateButton"}}}},"WhatsAppTemplateCard":{"type":"object","additionalProperties":false,"required":["components"],"description":"One card in a carousel.","properties":{"components":{"type":"array","readOnly":true,"description":"This card's content blocks, in display order.","items":{"$ref":"#/components/schemas/WhatsAppTemplateCardComponent"}}}},"WhatsAppTemplateComponent":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["header","body","footer","buttons","carousel"],"description":"The content block's type within the template.","example":"body"},"format":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["text","image","video","gif","document","location"],"description":"The header block's content type. Present on a header block. A `text` header carries a line of copy. The `image`, `video`, `gif`, and `document` formats each show a file whose address is in the block's `example_parameters`. The `location` format shows a map. It carries no content because the coordinates belong to the message rather than the template.\n","example":"text"},"text":{"type":"string","minLength":1,"readOnly":true,"description":"The block's text content, with any variable placeholders shown inline. Present when the block carries text. An authentication template's body and footer are written by WhatsApp from the two settings below rather than by you, so their text is absent until the language has been submitted and WhatsApp has supplied it.\n","example":"Your verification code is {{1}}."},"add_security_recommendation":{"type":"boolean","readOnly":true,"description":"Whether this authentication template's body ends with WhatsApp's advice not to share the code. Present on an authentication template's body block.\n"},"code_expiration_minutes":{"type":"integer","readOnly":true,"description":"How long the passcode stays valid, which WhatsApp states in this footer. Present on an authentication template's footer block. Omitting it on a write leaves the footer off entirely.\n","example":60},"example_parameters":{"type":"array","readOnly":true,"description":"Example values for this block's variables, in placeholder order (one per `{{n}}`). Use them to see what a filled message looks like. Present when the block has variables.","items":{"$ref":"#/components/schemas/WhatsAppTemplateExampleParameter"}},"buttons":{"type":"array","readOnly":true,"description":"The buttons attached to this block. Present when the block carries buttons.","items":{"$ref":"#/components/schemas/WhatsAppTemplateButton"}},"cards":{"type":"array","readOnly":true,"description":"The cards this block scrolls through, in display order. Present on a `carousel` block.\n","items":{"$ref":"#/components/schemas/WhatsAppTemplateCard"}}}},"WhatsAppTemplatePreview":{"type":"object","additionalProperties":false,"required":["language","components"],"properties":{"language":{"allOf":[{"$ref":"#/components/schemas/LanguageTag"}],"readOnly":true,"description":"The language of the variant that was rendered, in its canonical form."},"components":{"type":"array","readOnly":true,"description":"The rendered content blocks, matching how WhatsApp displays the message, with variable placeholders shown inline.\n","items":{"$ref":"#/components/schemas/WhatsAppTemplateComponent"}}}},"WhatsAppTemplateLanguageStatus":{"type":"string","minLength":1,"x-extensible-enum":["approved","pending","rejected","paused","disabled","in_appeal","pending_deletion","limit_exceeded","archived","deleted","submit_failed","outcome_unknown"],"description":"Language review and health status:\n\n- `approved`: Passed review and can be sent.\n- `pending`: Under review.\n- `rejected`: Failed review.\n- `paused` or `disabled`: Sending is suspended.\n- `in_appeal`: A decision is being appealed.\n- `pending_deletion`: Scheduled for deletion by Meta.\n- `limit_exceeded`: Sending is blocked by a limit.\n- `archived`: Reclaimed after 12 months without use; recoverable for 28 days.\n- `deleted`: Permanently deleted.\n- `submit_failed`: A submission or a deletion did not complete and will not be retried. `error.description` says why, and `error.meta_error_code` is set only where WhatsApp itself refused.\n- `outcome_unknown`: A create or an edit reached WhatsApp but no response came back, so the outcome is still being resolved against WhatsApp. An unanswered deletion is retried instead of landing here. `error.description` says so, and `error.meta_error_code` is absent, since nothing was refused.\n\nThis is an open enum. Accept unrecognized values.\n"},"WhatsAppTemplateRejectionCategory":{"type":"string","minLength":1,"x-extensible-enum":["abusive_content","incorrect_category","invalid_format","scam","tag_content_mismatch"],"description":"Why Meta refused a language's content, in Meta's own vocabulary, lowercased. Read it with `reason`, which carries Meta's human-written detail, and `recommendation`, which carries its suggested fix. This is an open enum. Accept unrecognized values.\n","example":"invalid_format"},"WhatsAppTemplateRejection":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Why Meta refused a language's content, and what it says about fixing it. Present when `status` is `rejected`.\n","properties":{"category":{"$ref":"#/components/schemas/WhatsAppTemplateRejectionCategory","description":"Meta's own classification of the refusal."},"reason":{"type":["string","null"],"readOnly":true,"description":"Meta's detail about the refusal, passed through unmodified.","example":"Parameters are adjacent."},"recommendation":{"type":["string","null"],"readOnly":true,"description":"Meta's suggested fix, the only thing it says about how to make the content acceptable. Meta sends it for some refusals and not others.\n","example":"Add text between the two parameters."}}},"WhatsAppTemplateSubmissionError":{"type":"object","additionalProperties":false,"readOnly":true,"required":["description"],"description":"Why the submission itself did not complete. Distinct from `rejection`, which is Meta refusing the content it was given.\n","properties":{"description":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable explanation of why the submission did not complete.","example":"component of type HEADER is missing expected field(s)"},"meta_error_code":{"type":["string","null"],"readOnly":true,"description":"WhatsApp's most specific code for the refusal: its error subcode when it sent one, otherwise its top-level code. Opaque, treat it as a string. Absent when the failure was Bird's own verdict rather than a WhatsApp refusal.\n","example":"2388043"}}},"WhatsAppTemplateLanguageState":{"type":"object","additionalProperties":false,"properties":{"status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateLanguageStatus"}],"description":"On a template, where this language stands on the version currently in service. On a version, what that version's submission did with this language. Absent on a draft, which has not been submitted.\n"},"submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this language's content was last submitted to Meta. Null on a draft, which has not been submitted, and null for a built-in template's language, shipped already approved rather than submitted on your behalf.\n","example":"2026-07-26T16:40:00Z"},"editable_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The next time you can edit this language, if Meta's one-edit-per-day limit on an approved language is currently spent. Null when an edit is allowed right now, though Meta also caps an approved language at ten edits per rolling 30 days: a null here does not guarantee an edit will succeed if you are close to that limit too.\n","example":"2026-07-27T16:40:00Z"},"rejection":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateRejection"}],"description":"Why Meta refused this content, present when `status` is `rejected`. Absent otherwise.\n"},"error":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateSubmissionError"}],"description":"Why the submission did not complete, present when `status` is `submit_failed` or `outcome_unknown`. Absent otherwise, including on a rejection, whose reason is in `rejection`.\n"}},"description":"Where one language stands, without its content: content lives under a version, read that for it. An object rather than a bare status string, so detail beyond status can arrive later as a sibling property instead of a breaking change.\n"},"WhatsAppTemplateVersionID":{"type":"string","minLength":1,"pattern":"^wav_[0-9a-hjkmnp-tv-z]{26}$","example":"wav_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppTemplate":{"type":"object","additionalProperties":false,"required":["id","slug","slug_editable","name","scope","description","category","status","default_language","on_missing_language","language_source_required","available_languages","languages","draft_version_id","live_version_id","pending_version_id","last_submitted_at","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/WhatsAppTemplateID","readOnly":true,"description":"Stable Bird identifier for the template."},"slug":{"example":"bird_otp","allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"readOnly":true,"description":"The template's handle, editable before the first submission. Address it by this handle, and reference it when sending. Handles beginning with `bird_` are reserved for our built-in templates.\n"},"slug_editable":{"type":"boolean","readOnly":true,"description":"Whether the slug can still be changed. False after the first submission and for built-in templates."},"name":{"type":"string","minLength":1,"maxLength":255,"description":"A display name for the template. Nothing resolves through it, so it is safe to show wherever a human reads the template.\n","example":"Order update"},"description":{"type":["string","null"],"maxLength":1000,"description":"What the template is for. Null when unset.","example":"Sent when an order ships."},"scope":{"$ref":"#/components/schemas/TemplateScope"},"waba":{"type":"string","readOnly":true,"description":"The WhatsApp Business Account that holds this template's languages at Meta. Absent on a built-in template: those live on a WABA that Bird manages centrally rather than on your account, so it is not yours to reconcile against and is not disclosed.\n","example":"102290129340398"},"category":{"$ref":"#/components/schemas/WhatsAppTemplateCategory","description":"The category you declared for the template. It is fixed once the template exists. Meta applies its own category per language and may move one, which is what messages are priced at. Read the language for that.\n"},"status":{"$ref":"#/components/schemas/TemplateStatus","description":"The template's lifecycle, aggregated over its languages."},"default_language":{"$ref":"#/components/schemas/LanguageTag","description":"The language a send is served in when it names none, whichever `on_missing_language` is set. A template that sets `language_source_required` refuses such a send instead. Under `fallback` it is also the last hop for a language that is not in `available_languages`, whether the template holds no copy in it or holds one WhatsApp has not approved. The template is required to hold this default, and it must itself be in `available_languages` for a send to resolve here.\n"},"on_missing_language":{"allOf":[{"$ref":"#/components/schemas/TemplateOnMissingLanguage"}],"readOnly":true,"description":"What a send does when the language it asks for has no approved copy. Defaults to `fail` on WhatsApp, because every language is separately approved and separately priced: falling back silently would send content the recipient did not expect at a rate the sender did not choose.\n"},"language_source_required":{"type":"boolean","description":"When true, a send must name a language explicitly rather than letting the template resolve one.\n","example":false},"available_languages":{"type":"array","readOnly":true,"description":"The languages a send can resolve right now: approved and not held back by Meta. It shrinks for reasons you did not cause: Meta pauses, disables, archives or limits a language and it leaves the set with nobody having edited anything. Read `languages` to see which languages exist and why one is missing.\n","items":{"$ref":"#/components/schemas/LanguageTag"}},"languages":{"type":"object","example":{"en":{"status":"approved"}},"readOnly":true,"description":"Where each of the template's languages stands, keyed by BCP-47 language tag. This is the summary of the version currently in service, so a template reading `active` can still hold a rejected or paused language: the aggregate says something is sendable, and this says which. Content is not here; it lives under a version.\n","propertyNames":{"$ref":"#/components/schemas/LanguageTag"},"additionalProperties":{"$ref":"#/components/schemas/WhatsAppTemplateLanguageState"}},"draft_version_id":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppTemplateVersionID"},{"type":"null"}],"readOnly":true,"description":"The open draft, or null when nobody is editing. Non-null is the answer to whether this template has unsubmitted work: a draft exists only because someone opened one.\n"},"live_version_id":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppTemplateVersionID"},{"type":"null"}],"readOnly":true,"description":"The version Meta is serving. A version goes live as a unit the moment any of its languages is approved, superseding the one before it. Null until a first approval.\n"},"pending_version_id":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppTemplateVersionID"},{"type":"null"}],"readOnly":true,"description":"A submitted version still awaiting verdicts: what to poll. It stays set while any language is unresolved, including after a sibling's approval took the version live. Null when nothing is outstanding.\n"},"last_submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this template was last submitted. Null for a pre-approved built-in template.\n","example":"2026-07-26T16:40:00Z"},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was created. Null for a built-in template, which Bird ships rather than stores."},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was last modified. Null for a built-in template, which Bird ships rather than stores."},"next":{"type":"array","readOnly":true,"description":"What to do next with this template, given the state it is in. Each entry names one\naction and says why it is worth taking, so you can act on this response without\nworking out the order yourself. Present on reads that compute it: an empty list\nmeans there is nothing to do, and the field is absent entirely on responses that\ndo not report next actions.\n\nA `draft` template routes to opening its draft, a `pending` one to the version\nunder review, and a `rejected` or `inactive` one to a fresh draft. The template's\n`status` is the aggregate over its languages, so an entry may send you to the\nversion to see where each language actually stands.\n","items":{"$ref":"#/components/schemas/NextAction"}}},"description":"A message template: one identity holding a copy of the message per language. Each language is reviewed, priced and paused by Meta on its own, so the template's own status is an aggregate and the per-language detail is in `languages`. A version contains the content.\n"},"WhatsAppTemplateList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of templates available to your workspace.","items":{"$ref":"#/components/schemas/WhatsAppTemplate"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppTemplateCategoryWrite":{"type":"string","minLength":1,"enum":["authentication","utility","marketing"],"x-enum-varnames":["WhatsAppTemplateCategoryWriteAuthentication","WhatsAppTemplateCategoryWriteUtility","WhatsAppTemplateCategoryWriteMarketing"],"description":"Meta's content classification for a template.\n\n- `authentication`: delivers one-time passcodes.\n- `utility`: delivers transaction-triggered updates (receipts, order status).\n- `marketing`: carries promotional content.\n\nThe category determines what a send costs. The write-side vocabulary is\ntherefore closed even though reads use an open vocabulary. A template with a\ncategory Meta would refuse can never be submitted. The request reports this\nproblem before a review verdict could arrive hours later.\n","example":"utility"},"WhatsAppTemplateComponentTypeWrite":{"type":"string","minLength":1,"enum":["header","body","footer","buttons","carousel"],"x-enum-varnames":["WhatsAppTemplateComponentTypeWriteHeader","WhatsAppTemplateComponentTypeWriteBody","WhatsAppTemplateComponentTypeWriteFooter","WhatsAppTemplateComponentTypeWriteButtons","WhatsAppTemplateComponentTypeWriteCarousel"],"description":"A content block's type. A `body` block is required on every language; `header`, `footer`, `buttons` and `carousel` are optional and appear at most once each. A `carousel` holds the cards a marketing template scrolls through, and is the one block type that nests: its cards carry blocks of their own. Closed on the write side: a block type Bird cannot submit to Meta is rejected rather than accepted and dropped.\n","example":"body"},"WhatsAppTemplateHeaderFormatWrite":{"type":"string","minLength":1,"enum":["text","image","video","gif","document","location"],"x-enum-varnames":["WhatsAppTemplateHeaderFormatWriteText","WhatsAppTemplateHeaderFormatWriteImage","WhatsAppTemplateHeaderFormatWriteVideo","WhatsAppTemplateHeaderFormatWriteGif","WhatsAppTemplateHeaderFormatWriteDocument","WhatsAppTemplateHeaderFormatWriteLocation"],"description":"A header's kind. The `text` format carries a line of copy. The `image`, `video`, `gif`, and `document` formats each show a file whose address you give in `url`. A `gif` is an MP4 that plays automatically and works only on a marketing template. The `location` format shows a map the recipient can tap to open. It carries no content here because the coordinates belong to the message rather than the template. Closed on the write side: a kind Bird cannot submit to Meta is rejected rather than accepted and dropped.\n","example":"text"},"WhatsAppTemplateParameterTypeWrite":{"type":"string","minLength":1,"enum":["text","image","video","gif","document"],"x-enum-varnames":["Text","Image","Video","Gif","Document"],"description":"The kind of value a template parameter accepts. `text` is an example filling a `{{n}}` placeholder. The other four are the sample file a media header shows, and each matches its header's `format`. Closed on the write side, unlike the vocabulary the reads carry: a kind Bird does not accept cannot be submitted, so naming it here would advertise a value the request is refused for.\n","example":"text"},"WhatsAppTemplateExampleParameterWrite":{"type":"object","additionalProperties":false,"required":["type"],"description":"One example value for one variable, as written to a draft.","properties":{"type":{"$ref":"#/components/schemas/WhatsAppTemplateParameterTypeWrite"},"text":{"type":"string","description":"The example value. Send it when `type` is `text`. May be empty on a draft; required before submission.\n","example":"123456"},"url":{"type":"string","maxLength":2000,"description":"The address of the file a media header shows. Send it when `type` is `image`, `video`, `gif` or `document`. Bird fetches the file when the language is submitted and hands Meta its own copy, so the address only has to be reachable at submission. WhatsApp reviews the template with this file, and the message each recipient receives carries the file named on the send. May be empty on a draft, for a file not yet chosen; a reachable address is required before submission.\n","example":"https://www.example.com/holiday/banner.jpg"},"name":{"type":"string","description":"The named placeholder this example fills. Send it only for a template written against named parameters rather than positional `{{n}}` ones. May be empty on a draft; required before submission.\n","example":"first_name"}}},"WhatsAppTemplateButtonTypeWrite":{"type":"string","minLength":1,"enum":["url","quick_reply","phone_number","otp","copy_code","request_contact_info"],"x-enum-varnames":["WhatsAppTemplateButtonTypeWriteUrl","WhatsAppTemplateButtonTypeWriteQuickReply","WhatsAppTemplateButtonTypeWritePhoneNumber","WhatsAppTemplateButtonTypeWriteOtp","WhatsAppTemplateButtonTypeWriteCopyCode","WhatsAppTemplateButtonTypeWriteRequestContactInfo"],"description":"A button's behavior.\n\n- `url`: opens a link.\n- `quick_reply`: sends its own label back to you as an inbound message.\n- `phone_number`: dials the number it carries.\n- `otp`: copies a one-time passcode. It belongs only on an authentication\n  template, and that template takes no other button type.\n- `copy_code`: copies a coupon code to the recipient's clipboard. It belongs\n  only on a marketing template, which takes at most one.\n- `request_contact_info`: asks the recipient to share the phone number their\n  WhatsApp account carries. It belongs only on a utility or marketing\n  template, as that template's only button.\n\nClosed on the write side: a button type Bird cannot submit to Meta is\nrejected rather than accepted and dropped.\n","example":"url"},"WhatsAppTemplateButtonOtpTypeWrite":{"type":"string","minLength":1,"enum":["copy_code"],"description":"How the recipient receives a one-time passcode. `copy_code` puts it on the clipboard.","example":"copy_code"},"WhatsAppTemplateButtonWrite":{"type":"object","additionalProperties":false,"required":["type"],"description":"One button, as written to a draft.","properties":{"type":{"$ref":"#/components/schemas/WhatsAppTemplateButtonTypeWrite"},"otp_type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateButtonOtpTypeWrite"}],"description":"How the recipient receives the passcode. Send it only on an `otp` button."},"text":{"type":"string","maxLength":25,"description":"The button's label, which each button type treats differently.\n\n- `url`, `quick_reply`, `phone_number`: your own label. Required.\n- `otp`: omit it. WhatsApp writes this label itself and reveals it only\n  once the language has been submitted.\n- `copy_code`: omit it. WhatsApp fixes this label and does not let you\n  change it.\n- `request_contact_info`: omit it. WhatsApp fixes this label too and\n  writes `Share Contact Info`, which is also what a submitted button\n  reads back as, so a read button can be written back unchanged.\n\nA draft takes any label, so this may be empty on a draft and is required\nonly before submission. A label a fixed-label button may not carry is\nrefused rather than silently replaced: the submit comes back as\n`valid: false` with an `errors` entry naming this field, in every\nlanguage.\n","example":"Track order"},"url":{"type":"string","maxLength":2000,"description":"The address a `url` button opens. An address with a placeholder is personalized per recipient, and one without is the same for everyone. At most one variable, and it must sit at the end of the address, written as `{{1}}`. A button address is numbered this way even on a template whose text uses named variables. A variable needs its example value in `example_parameters`. May be empty on a draft; required before submission.\n","example":"https://www.example.com/orders/{{1}}"},"phone_number":{"type":"string","maxLength":20,"description":"The number a `phone_number` button dials, in international format. Some countries publish numbers with a leading zero after the country calling code; WhatsApp strips that zero, so give a number that dials without it. May be empty on a draft; required before submission.\n","example":"+14155550100"},"example_parameters":{"type":"array","description":"The value that fills the variable in `url`, which WhatsApp reviews in the filled address. Send only the value, so a `{{1}}` standing for an order number takes `A-4192`. Required whenever `url` carries a variable. Also required on a `copy_code` button as one `text` entry. The entry carries a sample coupon code of at most 20 characters for WhatsApp to review. Each recipient copies the code you provide when sending the message.\n","items":{"$ref":"#/components/schemas/WhatsAppTemplateExampleParameterWrite"}}}},"WhatsAppTemplateCardComponentTypeWrite":{"type":"string","minLength":1,"enum":["header","body","buttons"],"x-enum-varnames":["WhatsAppTemplateCardComponentTypeWriteHeader","WhatsAppTemplateCardComponentTypeWriteBody","WhatsAppTemplateCardComponentTypeWriteButtons"],"description":"A card block's type. A card carries a `header` showing an image or a video, and optionally a `body` and a `buttons` block. A card has no footer and cannot hold a carousel of its own, which is why a card's blocks are a narrower set than a template's.\n","example":"header"},"WhatsAppTemplateCardHeaderFormatWrite":{"type":"string","minLength":1,"enum":["image","video"],"x-enum-varnames":["WhatsAppTemplateCardHeaderFormatWriteImage","WhatsAppTemplateCardHeaderFormatWriteVideo"],"description":"A card header's kind. A card shows an image or a video only, so a document header works at the top of a template but not on a card.\n","example":"image"},"WhatsAppTemplateCardComponentWrite":{"type":"object","additionalProperties":false,"required":["type"],"description":"One content block inside a carousel card, as written to a draft. A card carries a narrower set of blocks than a template does, so it has its own shape rather than reusing the template's.\n","properties":{"type":{"$ref":"#/components/schemas/WhatsAppTemplateCardComponentTypeWrite"},"format":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateCardHeaderFormatWrite"}],"description":"The card header's kind. Required on a card's `header` block and refused on any other.\n"},"text":{"type":"string","description":"The card block's text, with each variable written as a placeholder. Send it on a `body` block. If any card carries body text then every card must, so the cards keep an even height. May be empty on a draft; required before submission.\n","example":"Chronograph, brown leather"},"example_parameters":{"type":"array","description":"An example value per `{{n}}` in `text`, in placeholder order. Meta reviews the filled message rather than the template, so a block with variables and no examples is refused. A card's `header` block carries its file here as a single entry whose `type` matches the header's `format` and whose `url` addresses the file, the same way a template header does.\n","items":{"$ref":"#/components/schemas/WhatsAppTemplateExampleParameterWrite"}},"buttons":{"type":"array","maxItems":2,"description":"The buttons this card carries, at most two. Send it only on a card's `buttons` block. Every card must carry the same buttons in the same order. A card takes a `quick_reply`, `phone_number` or `url` button; a `copy_code` or `request_contact_info` button belongs on the template itself.\n","items":{"$ref":"#/components/schemas/WhatsAppTemplateButtonWrite"}}}},"WhatsAppTemplateCardWrite":{"type":"object","additionalProperties":false,"description":"One card in a carousel, as written to a draft.","properties":{"components":{"type":"array","maxItems":3,"description":"This card's content blocks, in display order. A card carries a `header` showing an image or a video, and optionally a `body` and a `buttons` block. Every card in the carousel must carry the same blocks, and the same buttons, in the same order, so a card with body text means every card needs body text. May be omitted or empty on a draft; a header block is required before submission.\n","items":{"$ref":"#/components/schemas/WhatsAppTemplateCardComponentWrite"}}}},"WhatsAppTemplateComponentWrite":{"type":"object","additionalProperties":false,"required":["type"],"description":"One content block, as written to a draft. Requests use a separate closed shape because the corresponding response is open. The read shape marks every property as server-assigned, which drops requiredness from a request body. Its block-type vocabulary is also open, which would accept a misspelled type.\n","properties":{"type":{"$ref":"#/components/schemas/WhatsAppTemplateComponentTypeWrite"},"format":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateHeaderFormatWrite"}],"description":"The header's kind. Required on a `header` block and refused on any other, which is a rule a single block schema cannot express, so it is enforced on the write rather than by this schema.\n"},"text":{"type":"string","description":"The block's text, with each variable written as a placeholder. Send it on a `body`, a `footer`, and a `text` header. Four blocks refuse it. A `buttons` block carries text on the buttons themselves. A media or `location` header shows a file or map and has nowhere to put copy. A `carousel` carries text on its cards. For an authentication template's body and footer, WhatsApp writes the copy from the two settings below. May be empty on a draft; required before submission.\n","example":"Your order {{order_id}} has shipped."},"add_security_recommendation":{"type":"boolean","description":"Ends an authentication template's body with WhatsApp's own advice not to share the code. Send it only on an authentication template's body block, whose copy is otherwise WhatsApp's to write.\n"},"code_expiration_minutes":{"type":"integer","description":"How long the passcode stays valid. Send it on an authentication template's footer block and WhatsApp writes the footer stating it; omit the footer block entirely to leave it off. WhatsApp enforces the range it accepts and refuses the submission otherwise.\n","example":60},"example_parameters":{"type":"array","description":"An example value per `{{n}}` in `text`, in placeholder order. Meta reviews the filled message rather than the template, so a block with variables and no examples is refused. A media header also carries its file here as one entry. Its `type` matches the header's `format`, and its `url` addresses the file. A media header has no placeholder, so this entry is the header's own sample rather than a value that fills one.\n","items":{"$ref":"#/components/schemas/WhatsAppTemplateExampleParameterWrite"}},"buttons":{"type":"array","maxItems":10,"description":"The buttons this block carries. Send it only on a `buttons` block. Ten in total, of which at most two open a link and at most one dials a number. Quick replies must sit together rather than being interleaved with the other types, so `Quick reply, Quick reply, Link` is accepted where `Quick reply, Link, Quick reply` is refused. A template with four or more buttons, or one that mixes a quick reply with another type, cannot be opened on WhatsApp's desktop clients, and the recipient is asked to switch to their phone. May be omitted or empty on a draft; at least one button is required before submission.\n","items":{"$ref":"#/components/schemas/WhatsAppTemplateButtonWrite"}},"cards":{"type":"array","maxItems":10,"description":"The cards a `carousel` block scrolls through. Send it only on a `carousel` block, and only on a marketing template. Every card must carry the same blocks in the same order, and a card's header shows an `image` or a `video`. A `document` header works at the top of a template but not on a card. The number of cards is fixed once the language is approved. A template approved with three cards always sends three. Changing the count requires a new template rather than a new version of this one. May be omitted or hold fewer than two on a draft; at least two are required before submission.\n","items":{"$ref":"#/components/schemas/WhatsAppTemplateCardWrite"}}}},"WhatsAppTemplateRevision":{"type":"integer","minimum":1,"description":"A write counter, incremented every time the content it belongs to changes. It sits at 1 on content that has never been written through this API.\n","example":4},"WhatsAppTemplateLanguageUpsert":{"type":"object","additionalProperties":false,"description":"One language's content, as written to a draft.","properties":{"components":{"type":"array","maxItems":10,"items":{"$ref":"#/components/schemas/WhatsAppTemplateComponentWrite"},"description":"The template's content blocks for this language. A body block is required; header, footer and buttons are optional. Every variable needs an example value, which is what Meta reviews against. May be omitted or empty on a draft; a body block is required before the language can be submitted.\n"},"revision":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateRevision"}],"description":"The revision you last read for this language, making the write a compare-and-set: a mismatch is refused with a conflict, leaving an edit you have not seen in place. Omit it for last-write-wins, which is what a single writer wants. Supplying it for a language that does not exist is also refused with a conflict: a stale token whose language was deleted underneath it must never silently get a fresh row. Inside a template's initial languages seed on create, a value here is refused, since no prior revision exists yet to compare against.\n"}}},"WhatsAppTemplateCreate":{"type":"object","additionalProperties":false,"description":"Parameters for creating a WhatsApp template, optionally seeding its first draft.","required":["waba","slug","category","default_language"],"properties":{"waba":{"type":"string","minLength":1,"description":"The WhatsApp Business Account to author this template on. A template belongs to one account at Meta and is reviewed, approved and sent under it, so every create names the account explicitly. It must be an account connected to this workspace.\n","example":"102290129340398"},"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"description":"The template's workspace-unique handle, and what a send references. Editable only before the first submission. The `bird_` prefix is reserved for our built-in templates and is rejected. Meta's own template name is derived from this handle, so the handle itself is validated against the charset Meta accepts.\n","example":"order_update"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The template's display name, shown wherever the template is listed. Free text, changeable at any time. Defaults to the slug when omitted.\n","example":"Order update"},"description":{"type":"string","maxLength":1000,"description":"What the template is for, shown alongside it in the dashboard.","example":"Sent when a customer's order ships."},"category":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateCategoryWrite"}],"description":"The category Meta reviews the template under. Required, because Meta demands one on every template it holds and it determines what a send costs. The API does not choose a billing category by default. Meta can recategorize a template after review; the per-language summary reports what it actually holds.\n"},"default_language":{"allOf":[{"$ref":"#/components/schemas/LanguageTag"}],"description":"The language a send uses when it names none, and the last resort when a requested language is unavailable. When `languages` is supplied it must be one of its keys.\n","example":"en"},"on_missing_language":{"allOf":[{"$ref":"#/components/schemas/TemplateOnMissingLanguage"}],"description":"How a send behaves when it asks for a language this template does not carry. Defaults to `fail`: a WhatsApp send that silently fell back would deliver approved copy the recipient did not expect, at a price the sender did not choose.\n"},"language_source_required":{"type":"boolean","default":false,"description":"Whether a send has to name a language. Set it to have a send that names none rejected, leaving the default language for sends that ask for it.\n"},"languages":{"type":"object","maxProperties":25,"propertyNames":{"$ref":"#/components/schemas/LanguageTag"},"additionalProperties":{"$ref":"#/components/schemas/WhatsAppTemplateLanguageUpsert"},"description":"Optional seed for the template's first draft, keyed by BCP-47 language\ntag. Supplying it opens the draft and writes every language in one\ntransaction: any invalid entry fails the whole create, leaving no\ntemplate behind. Tags are canonicalised, so `pt-br` lands as `pt-BR`, and\ntwo keys canonicalising to one tag are rejected.\n\nOmit it to create the template with no draft, and add content later\nthrough the version and language paths. Nothing here reaches Meta:\ncontent is submitted, never created.\n","example":{"en":{"components":[{"type":"body","text":"Your order {{1}} has shipped.","example_parameters":[{"type":"text","text":"A-4192"}]}]}}}}},"WhatsAppTemplateUpdate":{"type":"object","additionalProperties":false,"description":"Partial update of a template's metadata and language policies. Only the fields you send are changed; the rest are left as they stand. The slug can change only before the first submission.\n","properties":{"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"description":"A new workspace-unique handle. Editable only before the first submission. After a change, the old slug no longer resolves; the template ID stays the same.\n"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The template's display name. Editable at any time; it is not what a send references.","example":"Order update"},"description":{"type":["string","null"],"maxLength":1000,"description":"What the template is for. Send `null` to clear it."},"default_language":{"allOf":[{"$ref":"#/components/schemas/LanguageTag"}],"description":"The language a send falls back to, and the one language the template is required to carry. Repointing it at a language the draft does not hold is refused, the same way removing the current default is: add the language first.\n","example":"en"},"on_missing_language":{"allOf":[{"$ref":"#/components/schemas/TemplateOnMissingLanguage"}],"description":"How a send behaves when it asks for a language this template does not carry. Omitting it leaves the template's current setting alone. The `fail` default belongs to a create, and a partial update never reapplies it.\n"},"language_source_required":{"type":"boolean","description":"Whether a send has to name a language."}}},"WhatsAppTemplateVersionSummary":{"type":"object","additionalProperties":false,"required":["id","template_id","languages","submitted_at","created_at"],"properties":{"id":{"$ref":"#/components/schemas/WhatsAppTemplateVersionID","readOnly":true,"description":"Stable Bird identifier for the version."},"template_id":{"$ref":"#/components/schemas/WhatsAppTemplateID","readOnly":true,"description":"The template this version belongs to."},"version_number":{"type":["integer","null"],"minimum":1,"readOnly":true,"description":"The version's sequence number, assigned when it is submitted. Null on a draft, which has not been submitted and has no place in the sequence yet.\n","example":4},"submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this version was submitted to Meta. Null on a draft, which has not been submitted, and null for a built-in template's version, which Bird ships already approved rather than submitting on your behalf.\n","example":"2026-07-20T11:04:00Z"},"languages":{"type":"object","example":{"en":{"status":"approved"}},"description":"What this version's submission did with each language it holds, keyed by BCP-47 language tag. Content is not here: read the version for that.\n","propertyNames":{"$ref":"#/components/schemas/LanguageTag"},"additionalProperties":{"$ref":"#/components/schemas/WhatsAppTemplateLanguageState"}},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the version was opened. Null for a built-in template's version, which Bird ships rather than stores.\n","example":"2026-07-20T10:31:00Z"},"next":{"type":"array","readOnly":true,"description":"What to do next with this version, given whether it has been submitted. Present on\nreads that compute it: an empty list means there is nothing to do, and the field is\nabsent entirely on responses that do not report next actions.\n\nA version with no `version_number` is the open draft, and routes to writing its\nlanguages and checking it. One that carries a number is frozen, so it routes to\nreading the verdicts it holds. `submitted_at` does not separate the two, because\nit is also null on a built-in template's version.\n","items":{"$ref":"#/components/schemas/NextAction"}}},"description":"One version of a template, without its content. A version holds a full copy of every language it was submitted with. Listing versions therefore names the languages and what became of each without carrying their content. Read a single version for its content. Read its shallow language collection for content hashes.\n"},"WhatsAppTemplateVersionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of the template's versions, newest first.","items":{"$ref":"#/components/schemas/WhatsAppTemplateVersionSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppTemplateVersionLanguage":{"type":"object","additionalProperties":false,"required":["components"],"properties":{"components":{"type":"array","description":"This language's content in this version, in display order.","items":{"$ref":"#/components/schemas/WhatsAppTemplateComponent"}},"status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateLanguageStatus"}],"description":"What this submission did with this language. Absent on a draft, which has not been submitted. Whether the language can be sent right now is a different question, answered by the template's `languages` summary.\n"},"rejection":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateRejection"}],"description":"Why Meta refused this content, present when `status` is `rejected`. Absent otherwise.\n"},"error":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateSubmissionError"}],"description":"Why the submission did not complete, present when `status` is `submit_failed` or `outcome_unknown`. Absent otherwise, including on a rejection, whose reason is in `rejection`.\n"}},"description":"One language's content in one version, and what that submission did with it."},"WhatsAppTemplateVersion":{"type":"object","additionalProperties":false,"required":["id","template_id","languages","submitted_at","created_at"],"properties":{"id":{"$ref":"#/components/schemas/WhatsAppTemplateVersionID","readOnly":true,"description":"Stable Bird identifier for the version."},"template_id":{"$ref":"#/components/schemas/WhatsAppTemplateID","readOnly":true,"description":"The template this version belongs to."},"version_number":{"type":["integer","null"],"minimum":1,"readOnly":true,"description":"The version's sequence number, assigned when it is submitted. Null on a draft, which has not been submitted and has no place in the sequence yet.\n","example":4},"submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this version was submitted to Meta. Null on a draft, which has not been submitted, and null for a built-in template's version, which Bird ships already approved rather than submitting on your behalf.\n","example":"2026-07-20T11:04:00Z"},"languages":{"type":"object","example":{"en":{"status":"approved","components":[{"type":"body","text":"Your verification code is {{1}}.","example_parameters":[{"type":"text","text":"123456"}]}]}},"description":"This version's content, keyed by BCP-47 language tag, with what its submission did with each language.\n","propertyNames":{"$ref":"#/components/schemas/LanguageTag"},"additionalProperties":{"$ref":"#/components/schemas/WhatsAppTemplateVersionLanguage"}},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the version was opened. Null for a built-in template's version, which Bird ships rather than stores.\n","example":"2026-07-20T10:31:00Z"}},"description":"One version of a template: the content of every language it holds, frozen when it was submitted, alongside what Meta made of each. A draft is a version too: a mutable one, with no number and no submission date.\n"},"WhatsAppTemplateDuplicate":{"type":"object","additionalProperties":false,"description":"The account the copy is created on, plus optional overrides. Everything else is inherited from the source.","required":["waba"],"properties":{"waba":{"type":"string","minLength":1,"description":"The WhatsApp Business Account the copy is created on. A template belongs to one account at Meta. Meta reviews, approves, and sends it under that account. The copy therefore names its account rather than taking the source's. A Bird built-in template lives on Bird's own account, which you cannot submit against. It must be an account connected to this workspace.\n","example":"102290129340398"},"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"description":"The copy's handle. The server assigns one when omitted. A `bird_` slug is rejected, and one already in use is a conflict.\n","example":"acme_order_update"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The copy's display name. Inherited from the source when omitted."},"include_languages":{"type":"array","items":{"$ref":"#/components/schemas/LanguageTag"},"minItems":1,"maxItems":100,"uniqueItems":true,"description":"The languages the copy carries, when it should carry only some of them. Every entry must be a language the source holds. A copy narrows the set it inherits, it never adds to it. Omitted, the copy takes all of the source's languages.\n","example":["pt-BR","es-ES"]},"default_language":{"allOf":[{"$ref":"#/components/schemas/LanguageTag"}],"description":"The copy's default language, which must be one of the languages the copy carries. Omitted, the copy keeps the source's default when `include_languages` still carries it. Otherwise it takes the first of the copy's languages sorted by canonical tag, which is the order the copy stores them in, not the order `include_languages` listed them.\n","example":"pt-BR"}}},"WhatsAppTemplateSubmit":{"type":"object","additionalProperties":false,"description":"Options for a submit. Every field is optional, so an empty body submits the draft as it stands.\n","properties":{"validate_only":{"type":"boolean","default":false,"description":"Check the draft without submitting it. Every language is checked and every problem reported, but nothing is frozen and nothing is sent to WhatsApp. A clean result does not guarantee WhatsApp approval: WhatsApp offers no way to ask in advance, so it can still refuse a submission these checks passed. Use a different idempotency key for the validation and the submission. Reusing one key with different request bodies is rejected.\n"}},"example":{"validate_only":true}},"WhatsAppTemplateSubmitProblem":{"type":"object","additionalProperties":false,"description":"One problem found while checking a version.","required":["code","message"],"properties":{"language":{"readOnly":true,"description":"The language the problem belongs to, or null when it concerns the version as a whole.\n","oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}]},"field":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Where the problem sits inside the language's content, as a dotted path with indexes, or null when it is not about one field. The path is rooted at the language you wrote, so `components[0].text` is the first block's text.\n","example":"components[2].cards[0].components[1].example_parameters[0].url"},"code":{"type":"string","minLength":1,"pattern":"^E\\d{5}$","readOnly":true,"description":"The error code a real submit fails with. Look it up in the error catalog for what to do about it.\n","example":"E15025"},"message":{"type":"string","minLength":1,"readOnly":true,"description":"What is wrong, in a form you can show the author.","example":"A body block needs text."}}},"WhatsAppTemplateSubmitResult":{"type":"object","additionalProperties":false,"description":"The outcome of a submit. The shape is the same whether the submit was a check or a real one: read `valid` and `version` to see what happened.\n","required":["valid","errors"],"properties":{"valid":{"type":"boolean","readOnly":true,"description":"Whether the version passed every check."},"errors":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/WhatsAppTemplateSubmitProblem"},"description":"Every problem found across every language. One round trip identifies all problems. Empty when `valid` is true.\n"},"version":{"readOnly":true,"description":"The version this submit froze, or null when nothing was frozen: a check run, or a submit that found problems.\n","oneOf":[{"$ref":"#/components/schemas/WhatsAppTemplateVersion"},{"type":"null"}]},"next":{"type":"array","readOnly":true,"description":"What to do next with this outcome. Present on reads that compute it: an empty list\nmeans there is nothing to do, and the field is absent entirely on responses that do\nnot report next actions.\n\nA failed check routes back to the languages that need an edit; a clean one routes to\nthe real submit, and then to polling the template for WhatsApp's verdicts.\n","items":{"$ref":"#/components/schemas/NextAction"}}}},"WhatsAppTemplateLanguageSummary":{"type":"object","additionalProperties":false,"required":["language","revision","content_hash"],"properties":{"language":{"$ref":"#/components/schemas/LanguageTag","description":"The canonical tag this language is addressed by."},"status":{"$ref":"#/components/schemas/WhatsAppTemplateLanguageStatus","description":"What this version's submission did with this language. Absent on a draft."},"revision":{"$ref":"#/components/schemas/WhatsAppTemplateRevision","readOnly":true,"description":"This language's write counter, incremented every time its content changes."},"content_hash":{"type":"string","minLength":1,"readOnly":true,"description":"A hash over the serialized `components` this API surfaces, for telling whether a language differs without fetching it. It is comparable only within one version of this API: adding a field to the component shape changes every hash without the underlying content changing.\n","example":"sha256:9f2c4e1a7b03d85fbc6e29d417a05e8c3b1d9f76a2e4c018d53b7f9a6c2e18d4"},"next":{"type":"array","readOnly":true,"description":"What to do next about this language, given the verdict it carries. Present on reads\nthat compute it: an empty list means there is nothing to do, and the field is absent\nentirely on responses that do not report next actions.\n\nApproval is per language, so this is where a rejection, a pause, or a reclaimed\nlanguage is answered. The template's own next actions cannot say, because they read\nthe aggregate.\n","items":{"$ref":"#/components/schemas/NextAction"}}},"description":"One language of a version without its content. Fetch the language itself for the content.\n"},"WhatsAppTemplateLanguageList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Every language this version holds, without content.","items":{"$ref":"#/components/schemas/WhatsAppTemplateLanguageSummary"}}}},"WhatsAppTemplateContentHash":{"type":"string","minLength":1,"description":"A hash over the serialized `components` this API surfaces, prefixed with the algorithm that produced it (`sha256:`) so the algorithm can change without the field becoming ambiguous. It tells you whether a language differs without transferring its content. Compare hashes only within one version of this API. Adding a field to the component shape changes every hash even when the underlying content is unchanged. Email's field of the same name carries bare hex and predates this form.\n","example":"sha256:9f2c4e1a7b03d85fbc6e29d417a05e8c3b1d9f76a2e4c018d53b7f9a6c2e18d4"},"WhatsAppTemplateQualityScore":{"type":"string","minLength":1,"x-extensible-enum":["green","yellow","red","unknown"],"description":"Meta's quality rating for one language of a template, derived from how recipients respond to messages sent from it. The `red` score is the leading indicator of a pause. Reaching Meta's lowest rating pauses sending from that language for three hours; a second time pauses it for six, and a third disables it. The `unknown` score is a value Meta reports. When Meta has not rated the language, the rating object is absent. This is an open enum. Accept unrecognized values.\n","example":"green"},"WhatsAppTemplateQuality":{"type":"object","additionalProperties":false,"required":["current_score","updated_at"],"properties":{"current_score":{"$ref":"#/components/schemas/WhatsAppTemplateQualityScore","description":"Meta's rating for this language as of `updated_at`."},"previous_score":{"$ref":"#/components/schemas/WhatsAppTemplateQualityScore","description":"The rating this language held before the most recent change. Absent when Meta has rated it only once. Usually differs from `current_score`, but Meta sometimes reports both as the same value, so compare timestamps rather than assuming a transition.\n"},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the rating last changed. A re-evaluation that lands on the same rating does not move it, so this answers how long the language has held its current rating.\n","example":"2026-07-26T16:41:00Z"}},"description":"Meta's quality rating for one language, with the rating it moved from and when it moved. Present only once Meta has rated the language, and only on the version currently in service. A superseded version's content carries no rating.\n"},"WhatsAppTemplateLanguage":{"type":"object","additionalProperties":false,"required":["language","components","revision","content_hash","submitted_at","updated_at"],"properties":{"language":{"$ref":"#/components/schemas/LanguageTag","description":"The canonical tag this language is addressed by."},"components":{"type":"array","description":"This language's content blocks, in display order, exactly as submitted or as they stand in the draft.","items":{"$ref":"#/components/schemas/WhatsAppTemplateComponent"}},"status":{"$ref":"#/components/schemas/WhatsAppTemplateLanguageStatus","description":"What this submission did with this language. Absent on a draft, which has not been submitted. On a superseded version this is history: how that submission went. It does not report whether the language is sendable now.\n"},"revision":{"$ref":"#/components/schemas/WhatsAppTemplateRevision","readOnly":true,"description":"This language's write counter, incremented every time its content changes. It sits at 1 on content that has never been written through this API, which is every built-in template's language.\n"},"content_hash":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateContentHash"}],"readOnly":true},"category":{"$ref":"#/components/schemas/WhatsAppTemplateCategory","description":"The category Meta is applying to this language, which is what messages from it are priced at."},"previous_category":{"$ref":"#/components/schemas/WhatsAppTemplateCategory","description":"The category this language held before Meta moved it."},"quality":{"$ref":"#/components/schemas/WhatsAppTemplateQuality","description":"Meta's quality rating for this language. Present only on the version currently in service, and only once Meta has rated it.\n"},"rejection":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateRejection"}],"description":"Why Meta refused this content, present when `status` is `rejected`. Absent otherwise.\n"},"error":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateSubmissionError"}],"description":"Why the submission did not complete, present when `status` is `submit_failed` or `outcome_unknown`. Absent otherwise, including on a rejection, whose reason is in `rejection`.\n"},"submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this content was submitted to Meta. Null on a draft, which has not been submitted, and null for a built-in template's language, which Bird ships already approved rather than submitting on your behalf.\n","example":"2026-07-26T16:40:00Z"},"approved_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When Meta approved this exact content. It is a permanent mark on the content rather than a status, so a later pause or archival does not clear it. Null for a built-in template's language, whose approval predates Bird holding a date for it.\n","example":"2026-07-21T08:15:00Z"},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this language last changed. Null for a built-in template's language, which Bird ships rather than stores.\n","example":"2026-07-26T16:41:00Z"},"updated_by":{"oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}],"readOnly":true,"description":"The workspace member who last wrote this language. Always null for a built-in template's language: nobody in the workspace authored it.\n"}},"description":"One language of one version: its content, what the submission carrying it did with it, and everything Meta holds about it.\n"},"WhatsAppTemplateLanguageSaved":{"type":"object","additionalProperties":false,"description":"What a language write returns, enough to make the next write without re-reading.","required":["language","revision","content_hash","updated_at"],"properties":{"language":{"allOf":[{"$ref":"#/components/schemas/LanguageTag"}],"readOnly":true,"description":"The canonical form of the tag you addressed, which is what every path beneath it takes.","example":"pt-BR"},"revision":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateRevision"}],"readOnly":true,"description":"The language's new revision, to pass back on your next write."},"content_hash":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateContentHash"}],"readOnly":true,"description":"A hash over the content as saved, so you can tell whether your next write would change anything without diffing the document.\n"},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this language was last written."}}},"WhatsAppTemplateLanguageDiscard":{"type":"object","additionalProperties":false,"description":"Parameters for discarding one language's draft edits. Pass `revision` to guard against overwriting an edit you haven't seen.","properties":{"revision":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateRevision"}],"description":"The revision you last read for this language, making the discard a compare-and-set: a mismatch is refused with a conflict, leaving an edit you have not seen in place. Omit it for last-write-wins, which is what a single writer wants. Supplying it for a language the draft no longer holds is also refused with a conflict: a stale token whose language was deleted underneath it must never silently get a fresh row.\n"}}},"WhatsAppStatsSummaryPeriod":{"type":"object","additionalProperties":false,"description":"The window the server actually computed against. The summary serves two window grains: calendar days (bounds are YYYY-MM-DD) and hours (bounds are RFC 3339 instants on the hour). The grain of `from` and `to` mirrors the grain of the request's bounds.\n","required":["from","to"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary.","example":"2026-05-25"},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable.\n","example":"2026-05-25T14:03:10Z"}}},"WhatsAppDeliveryStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"WhatsApp lifecycle counts and rates for the requested period, attributed by send time, so a later delivery stays attributed to the period in which its message was accepted, and a recent period under-reports `delivered` while delivery reports are still arriving. The sibling `engagement` block reports read counts and rates. Rates are null when their denominator is zero. Very large counts are close estimates rather than exact tallies.\n","required":["accepted","sent","delivered","failed","rejected","delivery_rate","failure_rate"],"properties":{"accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages accepted for sending after admission checks. This is the denominator for `delivery_rate` and `failure_rate`.","example":4820},"sent":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages handed off for delivery.","example":4810},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages confirmed delivered to the recipient's device.","example":4720},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages that failed during sending or delivery.","example":25},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages rejected before any send attempt, because the recipient is on the workspace's suppression list, no reachable recipient was given, the destination has no price, or the wallet could not fund the send. Rejected messages are never charged and are not counted in `accepted`, so the total addressed is `accepted + rejected`. Excluded from `failure_rate`, which covers send failures only.","example":412},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of accepted messages that were delivered, computed as `delivered / accepted`. Null when no messages were accepted in scope.\n","example":0.9793},"failure_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of accepted messages that ultimately failed, computed as `failed / accepted`. Null when no messages were accepted in scope.\n","example":0.0052}}},"WhatsAppEngagementStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"WhatsApp engagement counts and the derived read rate for the scope of the containing row (the whole requested period or a breakdown dimension). The `read` field is the number of distinct messages confirmed read by the recipient. Send time determines attribution; the instant the read receipt arrived does not. A read is counted in the period its message was accepted in, alongside that message's own delivery when one arrived. The read rate divides reads by messages delivered in the same scope and is null when its denominator is zero. Very large counts are close estimates rather than exact tallies.\n","required":["read","read_rate"],"properties":{"read":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages confirmed read by the recipient.","example":3105},"read_rate":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Distinct messages read relative to messages delivered in the same scope, computed as `read / delivery.delivered`. Both counts are attributed by send time, so a read is counted alongside its own message's delivery. The rate can exceed 1 where a read receipt arrived for a message whose delivery receipt did not, or, at high volume, because the counts are close estimates. Null when `delivery.delivered` is zero.\n","example":0.6578}}},"WhatsAppLatencyQuantiles":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family. All three are null when no qualifying event contributed a measurement.\n","required":["p50_ms","p95_ms","p99_ms"],"properties":{"p50_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement.","example":610},"p95_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.","example":2140},"p99_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.","example":5380}}},"WhatsAppLatencyStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Latency percentiles in milliseconds for the requested scope:\n\n- `processing`: From acceptance to WhatsApp handoff.\n- `delivery`: From WhatsApp handoff to delivery confirmation.\n- `total`: From acceptance to delivery confirmation.\n\nEach family is omitted when no qualifying message contributes a measurement.\nIndividual percentiles can also be null. `delivery` is measured on a best-effort basis, so\nit can be absent for a scope whose `processing` and `total` are present.\n","example":{"processing":{"p50_ms":610,"p95_ms":2140,"p99_ms":5380},"delivery":{"p50_ms":1530,"p95_ms":6820,"p99_ms":18400},"total":{"p50_ms":2180,"p95_ms":9060,"p99_ms":24300}},"properties":{"processing":{"$ref":"#/components/schemas/WhatsAppLatencyQuantiles"},"delivery":{"$ref":"#/components/schemas/WhatsAppLatencyQuantiles"},"total":{"$ref":"#/components/schemas/WhatsAppLatencyQuantiles"}}},"WhatsAppStatsComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Changes from the previous period. A `*_pct_change` value is the signed relative change `(current - previous) / previous` and is null when the previous count is zero. A `*_rate_pp` value is the signed difference between rate fractions and is null when either rate is undefined.\n","required":["accepted_pct_change","sent_pct_change","delivered_pct_change","failed_pct_change","rejected_pct_change","read_pct_change","delivery_rate_pp","failure_rate_pp","read_rate_pp"],"properties":{"accepted_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in accepted messages (`delivery.accepted`) versus the previous period, as a signed fraction. Null when the previous period accepted none.","example":0.508},"sent_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in sent messages (`delivery.sent`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0.508},"delivered_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in delivered messages (`delivery.delivered`) versus the previous period, as a signed fraction. Null when the previous period delivered none.","example":0.513},"failed_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in failed messages (`delivery.failed`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":-0.194},"rejected_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in rejected messages (`delivery.rejected`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0.084},"read_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in messages read (`engagement.read`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0.568},"delivery_rate_pp":{"type":["number","null"],"minimum":-1,"maximum":1,"readOnly":true,"description":"Signed difference between this period's and the previous period's delivery rate, both fractions in [0,1] (multiply by 100 for percentage points). Null when either period's delivery rate is undefined.","example":0.0031},"failure_rate_pp":{"type":["number","null"],"minimum":-1,"maximum":1,"readOnly":true,"description":"Signed difference between this period's and the previous period's failure rate, both fractions in [0,1] (multiply by 100 for percentage points). Null when either period's failure rate is undefined.","example":-0.0045},"read_rate_pp":{"type":["number","null"],"readOnly":true,"description":"Signed difference between the current and previous read-rate fractions. Multiply by 100 for percentage points. The value can fall outside `[-1, 1]` because a read receipt can arrive for a message whose delivery receipt did not, and high-volume counts are approximate. Null when either rate is undefined.\n","example":0.0232}}},"WhatsAppStatsComparison":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The same statistics for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request.\n","required":["period","delivery","engagement","latency","delta"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSummaryPeriod","description":"The preceding window these comparison figures cover, the equal-length window ending immediately before the requested start (the prior day for day windows, the prior hour for hour windows). For a request covering 2026-05-01 to 2026-05-25, this is 2026-04-06 to 2026-04-30, both inclusive.","example":{"from":"2026-04-06","to":"2026-04-30","data_as_of":"2026-05-25T14:03:10Z"}},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryStats"}],"example":{"accepted":3196,"sent":3190,"delivered":3120,"failed":31,"rejected":380,"delivery_rate":0.9762,"failure_rate":0.0097}},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementStats"}],"example":{"read":1980,"read_rate":0.6346}},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppLatencyStats"}]},"delta":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppStatsComparisonDelta"}]}}},"WhatsAppStatsSummary":{"type":"object","additionalProperties":false,"description":"WhatsApp lifecycle counts, rates, engagement, and latency percentiles for the full requested period. Counts aggregate the time buckets. Latency percentiles cover the whole period. Rates are null when their denominator is zero.\n","required":["period","delivery","engagement","latency"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSummaryPeriod","description":"The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppLatencyStats"}]},"comparison":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppStatsComparison"}]}}},"WhatsAppStatsSeriesPeriod":{"type":"object","additionalProperties":false,"description":"The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to.\n","required":["from","to","grain"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window. A calendar day (YYYY-MM-DD) on the day grain, an RFC 3339 instant on the hour grain.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window. A calendar day (YYYY-MM-DD) on the day grain, an RFC 3339 instant on the hour grain.","example":"2026-05-25"},"grain":{"$ref":"#/components/schemas/StatsGrain","readOnly":true},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable.\n","example":"2026-05-25T14:03:10Z"}}},"WhatsAppDeliveryCounts":{"type":"object","additionalProperties":false,"readOnly":true,"description":"WhatsApp lifecycle counts for a time bucket, attributed by send time. A message accepted on Monday and delivered on Tuesday counts in Monday's bucket. The sibling `engagement` block reports read counts. Rates are available only for the whole period. Very large counts are close estimates rather than exact tallies.\n","required":["accepted","sent","delivered","failed","rejected"],"properties":{"accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages accepted for sending after admission checks.","example":4820},"sent":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages handed off for delivery.","example":4810},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages confirmed delivered to the recipient's device.","example":4720},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages that failed during sending or delivery.","example":25},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages rejected before any send attempt, because the recipient is on the workspace's suppression list, no reachable recipient was given, the destination has no price, or the wallet could not fund the send. Rejected messages are never charged and are not counted in `accepted`, so the total addressed is `accepted + rejected`. Excluded from `failure_rate`, which covers send failures only.","example":412}}},"WhatsAppEngagementCounts":{"type":"object","additionalProperties":false,"readOnly":true,"description":"WhatsApp engagement counts for a time bucket, attributed by send time. A message accepted on Monday and read on Tuesday counts in Monday's bucket. Read rates are available only for the whole period. Very large counts are close estimates rather than exact tallies.\n","required":["read"],"properties":{"read":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages confirmed read by the recipient.","example":3105}}},"WhatsAppStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"WhatsApp lifecycle counts, engagement, and latency percentiles for one time bucket (a calendar day or hour), bucketed by send time. Every count in a bucket describes the messages accepted in it, regardless of when their later events arrived. Rates apply to the whole window rather than individual buckets.\n","required":["bucket","delivery","engagement","latency"],"properties":{"bucket":{"type":"string","minLength":1,"readOnly":true,"description":"The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the period's grain.","example":"2026-05-25"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryCounts"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementCounts"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppLatencyStats"}]}}},"WhatsAppStatsResponse":{"type":"object","additionalProperties":false,"description":"Time-series stats payload. `period` echoes the range and bucket grain the server computed against; `data` is one row per bucket in chronological order.\n","required":["period","data"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSeriesPeriod"},"data":{"type":"array","readOnly":true,"description":"One row per day or hour in chronological order. Buckets with no activity contain zero counts.","items":{"$ref":"#/components/schemas/WhatsAppStatsPoint"}}}},"WhatsAppErrorCodeStatsPoint":{"type":"object","additionalProperties":false,"description":"Number of failed messages for a single normalized failure reason over the requested period.","required":["error_code","count"],"properties":{"error_code":{"readOnly":true,"description":"The normalized failure reason this row aggregates, matching the `last_error.code` reported on an individual failed message.","allOf":[{"$ref":"#/components/schemas/WhatsAppErrorCode"}]},"count":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages that failed with this reason in scope.","example":18}}},"WhatsAppStatsByErrorCodeResponse":{"type":"object","additionalProperties":false,"description":"Per-error-code failure breakdown for the requested period, ranked by failure count descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"data":{"type":"array","readOnly":true,"description":"Error-code rows ranked by failure count descending. Empty when no failures occurred in the period.","items":{"$ref":"#/components/schemas/WhatsAppErrorCodeStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct error codes with failures in the period, regardless of `limit`.","example":3}}},"WhatsAppTemplateStatsPoint":{"type":"object","additionalProperties":false,"description":"Lifecycle counts, derived rates, engagement and latency for a single WhatsApp template over the requested period.","required":["template_id","delivery","engagement","latency"],"properties":{"template_id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateID"}],"readOnly":true,"description":"The template these messages were sent from, using the same `id` the WhatsApp template endpoints return. A send that resolved no template does not appear in this breakdown. A template renamed after it was used to send still reports under this one `id`, and a template deleted after sending keeps its row rather than dropping the messages.\n"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppLatencyStats"}]}}},"WhatsAppStatsByTemplateResponse":{"type":"object","additionalProperties":false,"description":"Per-template breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"data":{"type":"array","readOnly":true,"description":"Template rows ranked by accepted volume descending.","items":{"$ref":"#/components/schemas/WhatsAppTemplateStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct templates with activity in the period, regardless of `limit`.","example":7}}},"WhatsAppTemplateCategoryStatsPoint":{"type":"object","additionalProperties":false,"description":"Lifecycle counts, derived rates, and engagement for a single WhatsApp template category over the requested period.","required":["category","delivery","engagement","latency"],"properties":{"category":{"readOnly":true,"description":"The template category this row aggregates.","allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateCategory"}]},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppLatencyStats"}]}}},"WhatsAppStatsByTemplateCategoryResponse":{"type":"object","additionalProperties":false,"description":"Per-template-category breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"data":{"type":"array","readOnly":true,"description":"Category rows ranked by accepted volume descending.","items":{"$ref":"#/components/schemas/WhatsAppTemplateCategoryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct categories with activity in the period, regardless of `limit`.","example":4}}},"WhatsAppTagStatsPoint":{"type":"object","additionalProperties":false,"description":"Lifecycle counts, derived rates, and engagement for a single tag (name:value) over the requested period.","required":["tag","delivery","engagement","latency"],"properties":{"tag":{"type":"string","minLength":1,"readOnly":true,"description":"The tag this row aggregates, in `name:value` form.","example":"campaign:summer_sale"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppLatencyStats"}]}}},"WhatsAppStatsByTagResponse":{"type":"object","additionalProperties":false,"description":"Per-tag breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"data":{"type":"array","readOnly":true,"description":"Tag rows ranked by accepted volume descending.","items":{"$ref":"#/components/schemas/WhatsAppTagStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct tags with activity in the period, regardless of `limit`.","example":12}}},"WhatsAppPhoneNumberStatsPoint":{"type":"object","additionalProperties":false,"description":"Lifecycle counts, rates, and engagement for one business phone number over the requested period, including whether the number is shared.","required":["phone_number","shared","delivery","engagement","latency"],"properties":{"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"The business sender phone number in E.164 form.","example":"+13124495569"},"shared":{"type":"boolean","readOnly":true,"description":"`true` for a shared Bird-managed number; `false` for a number owned by your workspace.\n","example":true},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppLatencyStats"}]}}},"WhatsAppStatsByPhoneNumberResponse":{"type":"object","additionalProperties":false,"description":"Per-phone-number breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"data":{"type":"array","readOnly":true,"description":"Phone-number rows ranked by accepted volume descending.","items":{"$ref":"#/components/schemas/WhatsAppPhoneNumberStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct phone numbers with activity in the period, regardless of `limit`.","example":2}}},"WhatsAppCountryStatsPoint":{"type":"object","additionalProperties":false,"description":"Lifecycle counts, derived rates, engagement and latency for a single destination country over the requested period.","required":["country","delivery","engagement","latency"],"properties":{"country":{"readOnly":true,"description":"The destination country this row aggregates, as an ISO 3166-1 alpha-2 code. `ZZ` collects recipients whose country could not be resolved.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppLatencyStats"}]}}},"WhatsAppStatsByCountryResponse":{"type":"object","additionalProperties":false,"description":"Per-country breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200).\n","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSummaryPeriod","readOnly":true,"description":"The window the response covers (echoed back), plus `data_as_of`."},"data":{"type":"array","readOnly":true,"description":"Country rows ranked by accepted volume descending. Empty when no eligible activity occurred in the period; rows sum to the summary less group-send volume, and less any pre-cutover phone-addressed sends still inside the window.\n","items":{"$ref":"#/components/schemas/WhatsAppCountryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct countries with activity in the period, regardless of `limit`.","example":4}}},"WhatsAppInboundStatsComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The change from the preceding period to the requested one. The `received_pct_change` field is a signed relative change, computed as `(current - previous) / previous`. A value of `0.5` means 50% higher, and `-0.2` means 20% lower. The field is null when the previous period received none.\n","required":["received_pct_change"],"properties":{"received_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in received messages versus the previous period, as a signed fraction. Null when the previous period received none.","example":0.058}}},"WhatsAppInboundStatsComparison":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The received-message count for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request.\n","required":["period","received","delta"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSummaryPeriod","description":"The preceding window these comparison figures cover, the equal-length window ending immediately before the requested start (the prior day for day windows, the prior hour for hour windows). For a request covering 2026-05-01 to 2026-05-25, this is 2026-04-06 to 2026-04-30, both inclusive.","example":{"from":"2026-04-06","to":"2026-04-30","data_as_of":"2026-05-25T14:03:10Z"}},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received in the preceding period.","example":3980},"delta":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppInboundStatsComparisonDelta"}]}}},"WhatsAppInboundStatsSummaryResponse":{"type":"object","additionalProperties":false,"description":"Total received messages for the requested period.\n","required":["period","received"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSummaryPeriod","description":"The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received in the period, counted by the time each message reached your number. Computed across the whole window rather than summed from the daily or hourly series, so it can sit slightly below the sum of those rows.","example":4210},"comparison":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppInboundStatsComparison"}]}}},"WhatsAppInboundStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Received-message count for one time bucket (a calendar day or hour), bucketed by the time each message reached your number.\n","required":["bucket","received"],"properties":{"bucket":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the request's grain.","example":"2026-05-25"},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received in this bucket.","example":182}}},"WhatsAppInboundStatsResponse":{"type":"object","additionalProperties":false,"description":"Received-message time series. `period` echoes the range the server computed against; `data` is one row per bucket in chronological order.\n","required":["period","data"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSeriesPeriod"},"data":{"type":"array","readOnly":true,"description":"One row per bucket (day or hour, matching the request) in the period, in chronological order. Buckets with no activity are included with a count of zero, so the series charts continuously without client-side gap handling.","items":{"$ref":"#/components/schemas/WhatsAppInboundStatsPoint"}}}},"WhatsAppInboundPhoneNumberStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Received-message count for a single business phone number over the requested period.","required":["phone_number","received"],"properties":{"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"The business phone number that received the messages, in E.164 form.","example":"+13124495569"},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages the number received in the period.","example":182}}},"WhatsAppInboundStatsByPhoneNumberResponse":{"type":"object","additionalProperties":false,"description":"Per-phone-number breakdown of received messages for the requested period, ranked by volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/WhatsAppStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"data":{"type":"array","readOnly":true,"description":"Phone-number rows ranked by received-message volume descending, capped at the requested `limit`. A number with no messages in the period is absent rather than zero-filled, because unlike a time bucket it is not part of a continuous axis.\n","items":{"$ref":"#/components/schemas/WhatsAppInboundPhoneNumberStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct phone numbers with received messages in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.","example":2}}},"WhatsAppDataLocalizationRegion":{"type":"string","minLength":2,"enum":["AU","ID","IN","JP","SG","KR","DE","CH","GB","BR","BH","ZA","AE","CA"],"description":"A country where WhatsApp can store a business phone number's message content at rest, as its two-letter ISO 3166 code.\n","example":"DE"},"WhatsAppEmbeddedSignupSubmit":{"type":"object","additionalProperties":false,"required":["code","waba","number_name"],"properties":{"code":{"type":"string","minLength":1,"writeOnly":true,"x-sensitive":true,"description":"The authorization code returned by Meta's embedded signup flow. It expires 30 seconds after the flow completes and is exchanged once, so submit it immediately. If it has expired, run the flow again for a fresh code.\n","example":"AQBhlXsctMxJYbwbrpybxlo9tLPGy-QAmjBJA03jxLos43wxlBlrYozY5C33BXJULd133cOJ"},"waba":{"type":"string","minLength":1,"description":"The WhatsApp Business Account to connect this number under, as returned by the embedded signup flow. An account can be connected to only one workspace.\n","example":"102290129340398"},"meta_phone_number_id":{"type":"string","minLength":1,"description":"The phone number to connect, as returned by the embedded signup flow. If the flow finished without a number, the request is rejected. Run the flow again and add a number.\n","example":"109364591979012"},"number_name":{"type":"string","minLength":1,"maxLength":100,"description":"Your workspace's own label for the number being connected. Where we pre-verified the number for you, it replaces the label that number already carried. This has no bearing on what WhatsApp displays to people the number messages.\n","example":"Sales EU"},"registration_pin":{"type":"string","pattern":"^[0-9]{6}$","writeOnly":true,"x-sensitive":true,"description":"The number's existing six-digit two-step verification PIN. Required only when the number is already registered on WhatsApp with two-step verification enabled, because Bird cannot connect such a number without it. Leave it out for a number that has never been registered.\n","example":"482915"},"data_localization_region":{"$ref":"#/components/schemas/WhatsAppDataLocalizationRegion","description":"Store this number's message content at rest in a specific country, given as its two-letter ISO 3166 code. Leave it out to use WhatsApp's default storage. This cannot be changed once the number is connected, so set it now if your organization requires it.\n"}}},"WhatsAppNumberStatus":{"type":"string","minLength":1,"x-extensible-enum":["awaiting_signup","banned","connected","deleted","disconnected","failed","flagged","migrated","pending","preparing","rate_limited","restricted"],"description":"Operational state of a business phone number. The `preparing` status means the service is verifying a managed number. The `awaiting_signup` status means verification finished and you must complete signup. The `pending` status is WhatsApp's own token for a number it does not hold as registered, and is also returned when no WhatsApp status has been stored, including after setup completes. It does not by itself establish whether setup is complete. The `connected` status means registration completed. The `failed` status means connection was refused permanently. Other values are WhatsApp's own operational states for a number already connected. This is an open enum. Accept unrecognized values.","example":"connected"},"WhatsAppEmbeddedSignupResult":{"type":"object","additionalProperties":false,"required":["waba","number_id","status"],"properties":{"waba":{"type":"string","minLength":1,"readOnly":true,"description":"The WhatsApp Business Account that was onboarded.","example":"102290129340398"},"number_id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberID"}],"readOnly":true,"description":"The number Bird is setting up. Resolve it with `GET /v1/whatsapp/numbers/{number_id}`.\n"},"status":{"$ref":"#/components/schemas/WhatsAppNumberStatus","readOnly":true,"description":"The same field the number resource serves, in the same vocabulary. Always `pending` on a fresh submission, since the number does not exist at WhatsApp yet; a replayed request reports whatever it has reached since.\n"}}},"WhatsAppBusinessAccountID":{"type":"string","minLength":1,"pattern":"^waa_[0-9a-hjkmnp-tv-z]{26}$","example":"waa_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppNumberSummary":{"type":"object","additionalProperties":false,"readOnly":true,"required":["id","phone_number","name"],"description":"The WhatsApp number a resource belongs to, with enough to show it without reading the number back: the same `id`, `phone_number` and `name` the number list returns.\n","properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberID"}],"readOnly":true,"description":"The number's id, which addresses every operation on it."},"phone_number":{"type":["string","null"],"readOnly":true,"description":"The number in E.164 format. Null until the number's setup completes.","example":"+31612340001"},"name":{"type":"string","minLength":1,"maxLength":100,"readOnly":true,"description":"Your workspace's own label for this number. It is not what WhatsApp shows to contacts; that is the profile's `display_name`.\n","example":"Sales EU"}}},"WhatsAppBusinessAccountSummary":{"type":"object","additionalProperties":false,"description":"The WhatsApp Business Account a resource belongs to, with enough to show it without reading the account back. Read the account itself with `GET /v1/whatsapp/business-accounts/{business_account_ref}` for everything else it holds.\n","required":["id","name","waba"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountID"}],"readOnly":true,"description":"The account's id in `GET /v1/whatsapp/business-accounts`."},"name":{"type":["string","null"],"readOnly":true,"description":"The account's name, as WhatsApp reports it. Null until WhatsApp has reported one.\n","example":"Acme Inc"},"waba":{"type":"string","minLength":1,"readOnly":true,"description":"Meta's own identifier for the account. This is the value to send when creating a template on it.\n","example":"102290129340398"}}},"WhatsAppAgentAIAudience":{"type":"string","minLength":1,"enum":["everyone","allowlist"],"description":"Who the agent is allowed to answer on this number.\n\n- `everyone` means the agent answers anyone who writes to the number.\n- `allowlist` means it answers only the contacts you listed for it, and stays out of every other conversation.\n\nStart with `allowlist` to try the agent on chosen contacts, then widen to `everyone`.\n","example":"allowlist"},"WhatsAppAgentSummary":{"type":"object","additionalProperties":false,"required":["onboarded_at","enabled","ai_audience"],"properties":{"onboarded_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When WhatsApp finished preparing the agent, which is the moment it became configurable. Null while WhatsApp is still preparing it, which takes about a minute after onboarding; every change is refused until then.\n","example":"2026-09-01T09:15:40Z"},"enabled":{"type":"boolean","readOnly":true,"description":"Whether the agent answers contacts. Off keeps everything the agent knows; only the answering stops.","example":true},"ai_audience":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentAIAudience"}],"readOnly":true,"description":"Who the agent is allowed to answer."}},"description":"The agent on a number, reduced to what a list row shows: whether WhatsApp has finished preparing it, whether it is answering, and who it may answer. Read the number's agent for everything else.\n"},"WhatsAppAgentListItem":{"type":"object","additionalProperties":false,"required":["number","business_account","eligible","agent"],"properties":{"number":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberSummary"}],"readOnly":true,"description":"The WhatsApp number this row is about. Every agent operation is addressed by its `id`."},"business_account":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountSummary"}],"readOnly":true,"description":"The account the number belongs to, where the business AI terms are accepted."},"eligible":{"type":["boolean","null"],"readOnly":true,"description":"Whether WhatsApp allows an agent on this number, as of the number's last read from WhatsApp. Null until a read has had an answer, which is what a number reads while the account's business AI terms are outstanding. When it is `false`, WhatsApp gives no reason, so `next` links you to WhatsApp Manager rather than restating one.\n","example":true},"agent":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppAgentSummary"},{"type":"null"}],"readOnly":true,"description":"The agent on this number, or null when none has been onboarded. An agent that is turned off is still here with `enabled: false`; only deleting it returns this to null.\n"},"next":{"type":"array","readOnly":true,"description":"What to do next with this number, given the state it is in. Each entry names one\naction and says why it is worth taking, so you can act on this response without\nworking out the order yourself. Present on reads that compute it: an empty list\nmeans there is nothing to do, and the field is absent entirely on responses that\ndo not report next actions.\n\nThis is the row's one call to action: onboard an agent when the number is eligible\nand has none, turn it on when it is off, or open WhatsApp Manager when WhatsApp has\nnot evaluated the number, which is where the account's business AI terms are\naccepted.\n","items":{"$ref":"#/components/schemas/NextAction"}}},"description":"One WhatsApp number your workspace connected itself, with whether it may run an agent and the agent it runs, if any. A number Bird operates on your behalf cannot run an agent and is not listed.\n"},"WhatsAppAgentListItemList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of the numbers your workspace connected itself, each with its agent state.","items":{"$ref":"#/components/schemas/WhatsAppAgentListItem"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppNumberScope":{"type":"string","minLength":1,"enum":["system","workspace"],"example":"workspace"},"WhatsAppNumberSortField":{"type":"string","enum":["created_at"],"default":"created_at","description":"Sortable fields for a WhatsApp number list."},"WhatsAppNumberErrorCode":{"type":"string","minLength":1,"x-extensible-enum":["registration_pin_rejected","registration_pin_rate_limited","registration_attempts_exhausted","number_verification_required","number_not_registered","number_already_linked","number_already_in_use","verification_code_not_received","verification_rate_limited","business_account_locked","credit_currency_mismatch","permission_denied","invalid_request","internal_error"],"description":"Standardized number-connection failure:\n\n- `registration_pin_rejected`: WhatsApp refused the two-step verification PIN.\n- `registration_pin_rate_limited`: Too many PIN attempts occurred recently.\n- `registration_attempts_exhausted`: Registration is blocked for 72 hours.\n- `number_verification_required`: WhatsApp requires the number to be verified again.\n- `number_not_registered`: WhatsApp does not hold the number as registered.\n- `number_already_linked`: Another WhatsApp integration uses the number.\n- `number_already_in_use`: WhatsApp cannot accept the number.\n- `verification_code_not_received`: The verification text did not arrive.\n- `verification_rate_limited`: WhatsApp declined to send this number another verification code, having been asked too often. It clears with time; retrying sooner extends it.\n- `business_account_locked`: WhatsApp locked the business account.\n- `credit_currency_mismatch`: WhatsApp bills the business account in a currency your organization is not billed in. Connect the number under a business account WhatsApp bills in that same currency, or one WhatsApp has set no currency on: an account's billing currency cannot be changed once WhatsApp sets it.\n- `permission_denied`: WhatsApp refused access to the account.\n- `invalid_request`: WhatsApp rejected the connection details.\n- `internal_error`: The service could not classify or resolve the failure.\n\nThis is an open enum. Accept unrecognized values.\n","example":"registration_pin_rejected"},"WhatsAppNumberError":{"type":"object","additionalProperties":false,"readOnly":true,"required":["code"],"description":"Why a number's connection was refused for good. `code` is the standardized reason. `description` explains the failure where one was recorded, and `meta_error_code` carries WhatsApp's own code when available. It accompanies the `failed` status only; a number still being retried carries no error.","properties":{"code":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberErrorCode"}],"readOnly":true,"description":"Standardized failure reason."},"description":{"type":"string","minLength":1,"readOnly":true,"description":"Why the connection failed: WhatsApp's own words, in the language of the account it refused, when WhatsApp answered; our own explanation when the number was refused before WhatsApp was asked; a generic sentence when WhatsApp refused without giving a reason. Absent when the attempt failed without ever reaching WhatsApp, which leaves `code` as the only account of the failure. Show it to the person who owns the number; never match on its text.","example":"Cannot Create Certificate: Please ensure two-factor authentication is disabled."},"meta_error_code":{"type":["string","null"],"readOnly":true,"description":"WhatsApp's most specific code for the refusal: its error subcode where it sent one, otherwise its top-level code. Null when WhatsApp did not provide a code. Treat it as an opaque string.","example":"2388001"}}},"WhatsAppNumberQualityRating":{"type":"string","minLength":1,"x-extensible-enum":["green","yellow","red","unknown"],"description":"WhatsApp quality rating for a business phone number, based on recipient feedback. `green`, `yellow`, and `red` indicate decreasing quality; sustained `red` can restrict the number. `unknown` is itself a reported rating. This is separate from a template-language quality score. Accept unrecognized values.","example":"green"},"WhatsAppNumberMessagingLimit":{"type":"string","minLength":1,"x-extensible-enum":["tier_50","tier_250","tier_1k","tier_10k","tier_100k","tier_unlimited"],"description":"How many unique WhatsApp users can be messaged outside a customer service window in a rolling 24 hours, as WhatsApp's own tier token. WhatsApp calculates this for the business portfolio, and every number in that portfolio shares it; it is not this number's private capacity, and one number can consume all of it. Values are WhatsApp's own tokens, lower-cased. Open enum: WhatsApp documents a 2,000 limit its published tier vocabulary has no token for, so treat an unrecognized value as a tier WhatsApp added.","example":"tier_250"},"WhatsAppNumberThroughputLevel":{"type":"string","minLength":1,"x-extensible-enum":["standard"],"description":"How fast WhatsApp lets this number send, as WhatsApp's own level token. `standard` is 80 messages per second; WhatsApp upgrades an eligible number to 1,000 per second automatically. Values are WhatsApp's own tokens, lower-cased. This enum is open because WhatsApp publishes no vocabulary for the field. The `standard` value is the only value Bird has measured. The upgraded level's token remains unknown until a number returns it.","example":"standard"},"WhatsAppNumber":{"type":"object","additionalProperties":false,"required":["id","phone_number","name","scope","status","created_at","updated_at"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberID"}],"readOnly":true,"description":"Unique identifier for the connected number."},"waba":{"type":"string","minLength":1,"readOnly":true,"description":"The WhatsApp Business Account this number is connected under. Present only for a number your workspace connected itself.\n","example":"102290129340398"},"phone_number":{"type":["string","null"],"readOnly":true,"description":"The number in E.164 format. Null only while the number itself is not yet known: a number your workspace holds carries its E.164 from the moment setup starts, so a value here does not mean the number can send. `status` is what says that.\n","example":"+15550001234"},"number_id":{"allOf":[{"$ref":"#/components/schemas/AllocatedNumberID"}],"readOnly":true,"description":"The number you hold with us that this WhatsApp number was connected from, as its id in GET /v1/numbers. Absent for a number you brought yourself.\n"},"name":{"type":"string","minLength":1,"maxLength":100,"readOnly":true,"description":"Your workspace's own label for this number, given when it was connected and changeable afterwards. It has no bearing on what WhatsApp displays to people the number messages; `GET /v1/whatsapp/numbers/{number_id}/profile` returns that as `display_name`. For a number we operate on your behalf, this is our own label instead and cannot be changed.\n","example":"Sales EU"},"scope":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberScope"}],"readOnly":true,"description":"Whether the number sends under a WhatsApp Business Account we operate on your behalf (`system`) or one your workspace connected itself (`workspace`).\n"},"data_localization_region":{"allOf":[{"$ref":"#/components/schemas/WhatsAppDataLocalizationRegion"}],"readOnly":true,"description":"The country this number's message content is stored at rest in, as its two-letter ISO 3166 code. Absent when it uses WhatsApp's default storage. It can differ from the region requested at connection when WhatsApp requires a particular country for the number.\n"},"status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberStatus"}],"readOnly":true,"description":"WhatsApp's own state for this number as of `meta_synced_at`, except for the three states we answer ourselves because WhatsApp holds nothing to report. A connection we are still verifying reads `preparing`, one waiting for someone to finish signup reads `awaiting_signup`, and a permanently refused one reads `failed`, with `error` saying why. `pending` is WhatsApp's own token for a number it does not hold as registered, and is also what a number with no stored WhatsApp status reads, including after setup completes, so it does not by itself establish whether setup is complete. A number we operate on your behalf reads `connected` as our own assertion rather than a reading from WhatsApp for every number we ship today; that tier carries no `meta_synced_at`."},"next":{"type":"array","readOnly":true,"description":"What to do next about this number, given the state it is in. Each entry names one\naction and says why it is worth taking, so you can act on this response without\nworking out the order yourself. Present on reads that compute it: an empty list\nmeans there is nothing to do, and the field is absent entirely on responses that\ndo not report next actions.\n\nWhile `status` is `awaiting_signup` this carries the browser step that finishes\nthe connection, because embedded signup sits behind an OAuth screen no API call\ncan stand in for.\n","items":{"$ref":"#/components/schemas/NextAction"}},"error":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberError"}],"readOnly":true,"description":"Why this number's connection was refused for good. Present only while `status` is `failed`. A retryable step records its cause on a still-`pending` number without setting this field, because that cause is not a refusal yet, so a connection you are still waiting on reports no error here."},"finish_setup_url":{"type":"string","format":"uri","minLength":1,"readOnly":true,"description":"Where a person finishes connecting this number, present only while `status` is `awaiting_signup`. Finishing means completing WhatsApp's embedded signup, which is a browser flow behind an OAuth screen: it cannot be done over the API, so open this link and have someone with access to the workspace complete it. The number is offered to them already verified. Once they finish, `status` moves on and this link is no longer returned.","example":"https://bird.com/dashboard/w/ws_01krdgeqcxet5s7t44vh8rt9mg/whatsapp/numbers?finish_setup_number=wan_01krdgeqcxet5s7t44vh8rt9mg"},"quality_rating":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberQualityRating"}],"readOnly":true,"description":"WhatsApp's quality rating for this number as of `meta_synced_at`. Absent until WhatsApp has reported one, and always absent for a number we operate on your behalf."},"messaging_limit":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberMessagingLimit"}],"readOnly":true,"description":"The messaging limit WhatsApp applied to this number's business portfolio as of `meta_synced_at`. Absent until WhatsApp has reported one, and always absent for a number we operate on your behalf."},"throughput_level":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberThroughputLevel"}],"readOnly":true,"description":"The send rate WhatsApp allowed this number as of `meta_synced_at`. Absent until WhatsApp has reported one, and always absent for a number we operate on your behalf."},"is_official_business_account":{"type":"boolean","readOnly":true,"description":"Whether WhatsApp grants this number Official Business Account status as of `meta_synced_at`. Absent until WhatsApp has reported it, and always absent for a number we operate on your behalf. WhatsApp grants the status per number, so two numbers on one WhatsApp Business Account can differ. The status also decides whether a rename is possible here: a number that has it cannot be renamed through `PATCH /v1/whatsapp/numbers/{number_id}/profile` at all, and has to be renamed through WhatsApp support instead."},"meta_synced_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this number's state was last read from WhatsApp. `status`, `quality_rating`, `messaging_limit`, `throughput_level`, and `is_official_business_account` all belong to that reading rather than representing live values. We re-read roughly hourly, so a change at WhatsApp can be up to an hour old here. Absent for a number we have never read back and for a number we operate on your behalf."},"pre_verification_requested_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When we last asked WhatsApp to send this number a verification code, which we do only for a number your workspace connected itself from a number you hold with us. Absent for a number we operate on your behalf, and for one you connected through Embedded Signup with a code you read yourself. Wait a few hours after this before repairing a number whose verification failed: WhatsApp rotates the routes it verifies over during that period, and throttles a number asked repeatedly in a short window. Distinct from `updated_at`, which any change to the number moves."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this number was submitted for connection."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this number was last changed."}}},"WhatsAppNumberList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The WhatsApp numbers your workspace can send from.","items":{"$ref":"#/components/schemas/WhatsAppNumber"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"NumbersDedicatedAllocationID":{"type":"string","minLength":1,"pattern":"^nda_[0-9a-hjkmnp-tv-z]{26}$","example":"nda_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppNumberCreate":{"type":"object","additionalProperties":false,"required":["number_id"],"properties":{"number_id":{"allOf":[{"$ref":"#/components/schemas/NumbersDedicatedAllocationID"}],"description":"The number you hold with us to connect to WhatsApp, as its id in GET /v1/numbers. It must be a number of your own, not one you share with other workspaces: WhatsApp takes exclusive ownership of it. It must also be able to receive text messages, because WhatsApp verifies ownership by sending one.\n"},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Your workspace's own label for the number while we prepare it, used until you finish connecting it. Leave it out and the number's own phone number stands in. Finishing embedded signup replaces it with the label given there, and renaming the number afterwards sets the one that lasts. This has no bearing on what WhatsApp displays to people the number messages.\n","example":"Sales EU"},"data_localization_region":{"$ref":"#/components/schemas/WhatsAppDataLocalizationRegion","description":"Store this number's message content at rest in a specific country, given as its two-letter ISO 3166 code. Leave it out to use WhatsApp's default storage. This cannot be changed once the number is connected, so set it now if your organization requires it.\n"}}},"WhatsAppNumberPrecheckRequest":{"type":"object","additionalProperties":false,"required":["phone_number"],"properties":{"phone_number":{"type":"string","minLength":1,"description":"The number to check, in E.164 format. It does not have to be a number you hold with us, since the point of the check is to ask before you buy one.\n","example":"+31612345678"}}},"WhatsAppNumberPrecheckOutcome":{"type":"string","minLength":1,"enum":["available","unavailable"],"description":"Whether WhatsApp accepts this number. The `available` outcome means nothing prevents connecting it today. The `unavailable` outcome means WhatsApp refuses it. It may already be in use on WhatsApp or be a number WhatsApp cannot serve. The two cannot be told apart, so treat it as a number to skip. The answer describes this moment: a number that is available now can be taken by someone else before you connect it.","example":"available"},"WhatsAppNumberPrecheckResult":{"type":"object","additionalProperties":false,"required":["phone_number","outcome"],"properties":{"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"The number that was checked, in E.164 format.","example":"+31612345678"},"outcome":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberPrecheckOutcome"}],"readOnly":true,"description":"Whether WhatsApp accepts this number."}}},"WhatsAppNumberUpdate":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Your workspace's own label for this number. This has no bearing on what WhatsApp displays to people the number messages.\n","example":"Sales EU"}}},"WhatsAppAgentCounts":{"type":"object","additionalProperties":false,"required":["faqs","websites","files","skills","ui_skills","allowlist"],"properties":{"faqs":{"type":"integer","minimum":0,"readOnly":true,"description":"How many questions and answers the agent holds.","example":12},"websites":{"type":"integer","minimum":0,"readOnly":true,"description":"How many websites the agent reads, whatever their crawl status.","example":2},"files":{"type":"integer","minimum":0,"readOnly":true,"description":"How many files the agent answers from.","example":3},"skills":{"type":"integer","minimum":0,"readOnly":true,"description":"How many skills the agent has.","example":4},"ui_skills":{"type":"integer","minimum":0,"readOnly":true,"description":"How many rich-message skills the agent has, enabled or not.","example":1},"allowlist":{"type":"integer","minimum":0,"readOnly":true,"description":"How many contacts are on the agent's allowlist, whatever `ai_audience` is set to.","example":5}},"description":"How many items each of the agent's lists holds. Read the list itself for the items."},"WhatsAppAgent":{"allOf":[{"type":"object","description":"The AI agent that answers contacts on one of your WhatsApp numbers. A number carries at most one, addressed by the number itself. This holds its state and who it may answer; how it behaves lives in its settings and what it knows about the business in its business info, each its own resource under the agent, and the questions, pages, files, skills and allowed contacts it answers from are each their own list, counted here.\n","required":["whatsapp_number_id","onboarded_at","enabled","enabled_at","ai_audience","counts"],"properties":{"whatsapp_number_id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberID"}],"readOnly":true,"description":"The WhatsApp number this agent answers on. The agent has no id of its own; the number is its address."},"onboarded_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When WhatsApp finished preparing the agent, which is the moment it became configurable. Null while WhatsApp is still preparing it, which takes about a minute after onboarding. Until then every change to the agent and its lists is refused with `409`, and reading the agent again is how you learn it is done.\n","example":"2026-09-01T09:15:40Z"},"enabled":{"type":"boolean","readOnly":true,"description":"Whether the agent answers contacts. Turning it off stops it in every conversation at once and keeps everything it knows. Turning it back on applies to conversations that start afterwards, not the ones already open.\n","example":true},"enabled_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the agent was last turned on; null while it is off.","example":"2026-09-01T09:20:12Z"},"ai_audience":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentAIAudience"}],"readOnly":true,"description":"Who the agent is allowed to answer."},"counts":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentCounts"}],"readOnly":true,"description":"How many items each of the agent's lists holds, so a summary screen needs no further reads."},"next":{"type":"array","readOnly":true,"description":"What to do next with this agent, given the state it is in. Each entry names one\naction and says why it is worth taking, so you can act on this response without\nworking out the order yourself. Present on reads that compute it: an empty list\nmeans there is nothing to do, and the field is absent entirely on responses that\ndo not report next actions.\n","items":{"$ref":"#/components/schemas/NextAction"}}}},{"$ref":"#/components/schemas/Timestamps"}]},"WhatsAppAgentHandoffMessageSelection":{"type":"string","minLength":1,"enum":["default","agent","custom"],"description":"Where the words the agent says to a contact as it hands a conversation over come from.\n\n- `default` uses WhatsApp's own standard wording.\n- `agent` lets the agent phrase the handover itself, to fit the conversation.\n- `custom` uses the `message` you give, word for word.\n","example":"agent"},"WhatsAppAgentHandoffSettings":{"type":"object","additionalProperties":false,"required":["enabled"],"properties":{"enabled":{"type":"boolean","readOnly":true,"description":"Whether the agent lets go of a conversation once it has told the contact it is handing over. On, control changes hands, a `whatsapp.agent.handed_off` event is delivered, and the conversation waits for you. Off, the agent says its handoff message but keeps the conversation.\n","example":true},"message_selection":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentHandoffMessageSelection"}],"readOnly":true,"description":"Where the words the agent says to the contact as it hands over come from. Absent when it was never chosen, in which case WhatsApp uses its own default."},"message":{"type":"string","minLength":1,"maxLength":1024,"readOnly":true,"description":"What the agent tells the contact as it hands the conversation over. Present only when `message_selection` is `custom`.\n","example":"I am passing you to a colleague who can help with this. They will reply here shortly."}},"description":"When the agent gives a conversation to your team, and what it tells the contact as it does."},"WhatsAppAgentFollowupInterval":{"type":"integer","enum":[300,900,1800,3600,7200,28800,86400],"description":"How long a contact has to be quiet before the agent follows up, in seconds. WhatsApp\naccepts these values only: five minutes, fifteen minutes, half an hour, one hour, two\nhours, eight hours and one day. `enabled` alone turns following up off; the interval is\nkept while it is off and applies again when it is turned back on.\n","example":3600},"WhatsAppAgentFollowupSettings":{"type":"object","additionalProperties":false,"required":["enabled"],"properties":{"enabled":{"type":"boolean","readOnly":true,"description":"Whether the agent writes again when a contact stops replying mid-conversation.","example":true},"interval_seconds":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentFollowupInterval"}],"readOnly":true,"description":"How long the contact has to be quiet before the agent writes again. Absent when it was never chosen, in which case WhatsApp uses its own default."},"message":{"type":"string","minLength":1,"maxLength":1024,"readOnly":true,"description":"What the agent says when it follows up. Absent when none was set, in which case the agent phrases it to fit the conversation.","example":"Is there anything else I can help you with today?"}},"description":"Whether the agent writes again when a contact goes quiet, after how long, and what it says."},"WhatsAppAgentSettings":{"type":"object","additionalProperties":false,"required":["enabled","enabled_at","ai_audience","handoff","followup","never_say_phrases"],"properties":{"enabled":{"type":"boolean","readOnly":true,"description":"Whether the agent answers contacts. Turning it off stops it in every conversation at once and keeps everything it knows. Turning it back on applies to conversations that start afterwards, not the ones already open.\n","example":true},"enabled_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the agent was last turned on; null while it is off.","example":"2026-09-01T09:20:12Z"},"ai_audience":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentAIAudience"}],"readOnly":true,"description":"Who the agent is allowed to answer."},"handoff":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppAgentHandoffSettings"},{"type":"null"}],"readOnly":true,"description":"When the agent gives a conversation to your team, and what it tells the contact as it does. Null until it has been set once; WhatsApp reports no default in its place.\n"},"followup":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppAgentFollowupSettings"},{"type":"null"}],"readOnly":true,"description":"Whether the agent writes again when a contact goes quiet, and after how long. Null until it has been set once; WhatsApp reports no default in its place.\n"},"never_say_phrases":{"type":"array","readOnly":true,"maxItems":100,"items":{"type":"string","minLength":1,"maxLength":200},"description":"Wording the agent must never use, whatever a contact asks. Empty when none is set. Read from WhatsApp when the agent finishes being prepared, and in step with every change made here afterwards; wording added in WhatsApp Manager after that is not picked up.\n"}},"description":"How the agent on a WhatsApp number behaves: whether it is answering and since when, who it may answer, how it hands over and follows up, and what it must never say. This is the one object WhatsApp keeps these in, held by Bird as WhatsApp last accepted it. What the agent knows about the business is its own resource beside this.\n"},"WhatsAppAgentHandoffSettingsUpdate":{"type":"object","additionalProperties":false,"required":["enabled"],"properties":{"enabled":{"type":"boolean","description":"Whether the agent lets go of a conversation once it has told the contact it is handing over. On, control changes hands, a `whatsapp.agent.handed_off` event is delivered, and the conversation waits for you. Off, the agent says its handoff message but keeps the conversation.\n","example":true},"message_selection":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentHandoffMessageSelection"}],"description":"Where the words the agent says to the contact as it hands over come from. Omit to leave WhatsApp's choice in place."},"message":{"type":"string","minLength":1,"maxLength":1024,"description":"What the agent tells the contact as it hands the conversation over. Only set when `message_selection` is `custom`, and required then. Bird refuses a write that breaks the rule with `422` naming this field, before WhatsApp sees it. The rule is not a `oneOf` here because `message_selection` may be omitted to keep WhatsApp's choice, so no arm could carry it as a discriminator, and an undiscriminated pair of arms would need a narrowed copy of the selection enum for the arm without a message.\n","example":"I am passing you to a colleague who can help with this. They will reply here shortly."}},"description":"When the agent should give a conversation to your team, and what to tell the contact as it does. Replaces the whole setting; once set it cannot return to null, since WhatsApp keeps no unset state for it."},"WhatsAppAgentFollowupSettingsUpdate":{"type":"object","additionalProperties":false,"required":["enabled"],"properties":{"enabled":{"type":"boolean","description":"Whether the agent writes again when a contact stops replying mid-conversation.","example":true},"interval_seconds":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentFollowupInterval"}],"description":"How long the contact has to be quiet before the agent writes again. Omit to leave WhatsApp's choice in place."},"message":{"type":"string","minLength":1,"maxLength":1024,"description":"What the agent says when it follows up. Omit to let the agent phrase it to fit the conversation.","example":"Is there anything else I can help you with today?"}},"description":"Whether the agent should write again when a contact goes quiet, after how long, and what to say. Replaces the whole setting; once set it cannot return to null, since WhatsApp keeps no unset state for it."},"WhatsAppAgentSettingsUpdate":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean","description":"Turn the agent on or off. Off stops it in every conversation at once and keeps everything it knows; on applies to conversations that start afterwards, not the ones already open. Omit to leave it as it is.\n","example":true},"ai_audience":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentAIAudience"}],"description":"Who the agent may answer. Omit to leave it as it is.\n"},"handoff":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentHandoffSettingsUpdate"}],"description":"When the agent gives a conversation to your team, and what it says as it does. Replaces the whole setting. Omit to leave it as it is."},"followup":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentFollowupSettingsUpdate"}],"description":"Whether the agent writes again when a contact goes quiet, and after how long. Replaces the whole setting. Omit to leave it as it is."},"never_say_phrases":{"type":"array","maxItems":100,"items":{"type":"string","minLength":1,"maxLength":200},"description":"Wording the agent must never use. The list replaces the whole set, so send the phrases you want kept along with any you are adding, and send an empty list to clear it. Omit to leave it as it is.\n"}},"description":"The settings to change. Every field is optional and an omitted one is left as it is. The change is written to WhatsApp and saved once WhatsApp accepts it, taking effect on the next conversation turn; there is no draft to publish.\n"},"WhatsAppAgentBusinessContactInfo":{"type":"object","additionalProperties":false,"properties":{"email":{"type":"string","minLength":1,"maxLength":128,"format":"email","readOnly":true,"description":"The address a contact can write to instead of this conversation.","example":"help@example.com"},"hours_of_operation":{"type":"string","minLength":1,"maxLength":256,"readOnly":true,"description":"When the business is open, in whatever wording a contact should be told. It is text rather than a schedule, so unusual hours can be said in full.\n","example":"Monday to Friday 09:00 to 18:00, Saturday 10:00 to 16:00, closed Sunday."},"address":{"type":"string","minLength":1,"maxLength":256,"readOnly":true,"description":"Where the business can be visited, as one line. Omit for a business with no public address.","example":"Keizersgracht 100, 1015 CS Amsterdam, Netherlands"}},"description":"How and when the business can be reached outside this conversation. Every field is optional."},"WhatsAppAgentBusinessInfo":{"type":"object","additionalProperties":false,"properties":{"business_description":{"type":"string","minLength":1,"maxLength":4096,"readOnly":true,"description":"What the business does, in the words you want the agent to use. This is the ground the agent answers from when a question is not covered by a question and answer, a website or a file.\n","example":"A family-run shop selling garden tools and outdoor furniture across the Netherlands."},"purchase_info":{"type":"string","minLength":1,"maxLength":4096,"readOnly":true,"description":"How a contact buys from the business, such as where to order and which payment steps to expect.","example":"Order on our website or by replying here with the product name; we confirm stock and send a payment link."},"delivery_and_shipping":{"type":"string","minLength":1,"maxLength":4096,"readOnly":true,"description":"How and when orders are delivered, and to where.","example":"Orders placed before 16:00 on a working day ship the same day and arrive the next working day in the Netherlands and Belgium."},"return_policy":{"type":"string","minLength":1,"maxLength":4096,"readOnly":true,"description":"How returns and refunds work.","example":"Unused items can be returned within 30 days for a full refund; we email a return label on request."},"payment_method":{"type":"string","minLength":1,"maxLength":4096,"readOnly":true,"description":"Which ways of paying the business accepts.","example":"iDEAL, credit card and PayPal online; cash or card in the shop."},"contact_info":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppAgentBusinessContactInfo"},{"type":"null"}],"readOnly":true,"description":"How and when the business can be reached outside this conversation. Null until it has been set, as WhatsApp returns it."}},"description":"What the agent knows about the business itself. Every field is optional, and what you leave out is something the agent cannot tell a contact. WhatsApp holds it as one object, so a change here is merged into it and the whole object is written there.\n"},"WhatsAppAgentBusinessContactInfoUpdate":{"type":"object","additionalProperties":false,"properties":{"email":{"type":["string","null"],"minLength":1,"maxLength":128,"format":"email","description":"The address a contact can write to instead of this conversation.","example":"help@example.com"},"hours_of_operation":{"type":["string","null"],"minLength":1,"maxLength":256,"description":"When the business is open, in whatever wording a contact should be told. It is text rather than a schedule, so unusual hours can be said in full.\n","example":"Monday to Friday 09:00 to 18:00, Saturday 10:00 to 16:00, closed Sunday."},"address":{"type":["string","null"],"minLength":1,"maxLength":256,"description":"Where the business can be visited, as one line.","example":"Keizersgracht 100, 1015 CS Amsterdam, Netherlands"}},"description":"How and when the business can be reached outside this conversation. Every field is optional. Null does not clear one of these: WhatsApp accepts the change and keeps the value it already has, and the response reports what it actually holds. Deleting the business information is what clears them, and it clears all of it.\n"},"WhatsAppAgentBusinessInfoUpdate":{"type":"object","additionalProperties":false,"properties":{"business_description":{"type":["string","null"],"minLength":1,"maxLength":4096,"description":"What the business does, in the words you want the agent to use. Omit to leave it as it is; null clears the field.","example":"A family-run shop selling garden tools and outdoor furniture across the Netherlands."},"purchase_info":{"type":["string","null"],"minLength":1,"maxLength":4096,"description":"How a contact buys from the business, such as where to order and which payment steps to expect. Omit to leave it as it is; null clears the field.","example":"Order on our website or by replying here with the product name; we confirm stock and send a payment link."},"delivery_and_shipping":{"type":["string","null"],"minLength":1,"maxLength":4096,"description":"How and when orders are delivered, and to where. Omit to leave it as it is; null clears the field.","example":"Orders placed before 16:00 on a working day ship the same day and arrive the next working day in the Netherlands and Belgium."},"return_policy":{"type":["string","null"],"minLength":1,"maxLength":4096,"description":"How returns and refunds work. Omit to leave it as it is; null clears the field.","example":"Unused items can be returned within 30 days for a full refund; we email a return label on request."},"payment_method":{"type":["string","null"],"minLength":1,"maxLength":4096,"description":"Which ways of paying the business accepts. Omit to leave it as it is; null clears the field.","example":"iDEAL, credit card and PayPal online; cash or card in the shop."},"contact_info":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppAgentBusinessContactInfoUpdate"},{"type":"null"}],"description":"How and when the business can be reached outside this conversation. Omit to leave it as it is. What you send is merged into what WhatsApp holds rather than replacing it, and null does not clear it: WhatsApp accepts the change and keeps what it already has. Deleting the business information is the only way to clear these.\n"}},"description":"The parts of what the agent knows about the business to change. Every field is optional, and an omitted one is left as it is. Null clears any of the fields above `contact_info`; it does not clear `contact_info` or anything inside it, which WhatsApp keeps until the business information is deleted. Bird merges the change into the object WhatsApp holds and writes the whole object there, where it takes effect on the next conversation turn.\n"},"WhatsAppAgentFAQID":{"type":"string","minLength":1,"pattern":"^waaf_[0-9a-hjkmnp-tv-z]{26}$","example":"waaf_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppAgentItemMetadata":{"type":"object","additionalProperties":{"type":"string","maxLength":500},"maxProperties":20,"description":"Your own labels on an item, as string keys and string values. The agent does not read them; they are for your bookkeeping and are passed through to WhatsApp unchanged.\n","example":{"source":"helpdesk","owner":"support-team"}},"WhatsAppAgentFAQ":{"allOf":[{"type":"object","required":["id","question","answer"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentFAQID"}],"readOnly":true,"description":"Unique identifier for the question and answer."},"question":{"type":"string","minLength":1,"readOnly":true,"description":"The question a contact might ask, in their words rather than yours.","example":"How long does delivery take?"},"answer":{"type":"string","minLength":1,"readOnly":true,"description":"What the agent answers. It is rewritten to fit the conversation rather than quoted, so write the fact and not the phrasing.\n","example":"Orders placed before 16:00 on a working day arrive the next working day in the Netherlands and Belgium."},"metadata":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentItemMetadata"}],"readOnly":true,"description":"Your own labels on this entry. Absent when none were given."}},"description":"One question and answer the agent works from."},{"$ref":"#/components/schemas/Timestamps"}]},"WhatsAppAgentFAQList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of the questions and answers the agent works from.","items":{"$ref":"#/components/schemas/WhatsAppAgentFAQ"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppAgentFAQCreate":{"type":"object","additionalProperties":false,"required":["question","answer"],"properties":{"question":{"type":"string","minLength":1,"description":"The question a contact might ask, in their words rather than yours.","example":"How long does delivery take?"},"answer":{"type":"string","minLength":1,"description":"What the agent answers. It is rewritten to fit the conversation rather than quoted, so write the fact and not the phrasing.\n","example":"Orders placed before 16:00 on a working day arrive the next working day in the Netherlands and Belgium."},"metadata":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentItemMetadata"}],"description":"Your own labels on this entry."}},"description":"A question and answer to add. It reaches WhatsApp before this request answers and is used from the next conversation turn."},"WhatsAppAgentFAQUpdate":{"type":"object","additionalProperties":false,"properties":{"question":{"type":"string","minLength":1,"description":"The question a contact might ask. Omit to leave it as it is.","example":"How long does delivery take?"},"answer":{"type":"string","minLength":1,"description":"What the agent answers. Omit to leave it as it is.","example":"Orders placed before 16:00 on a working day arrive the next working day in the Netherlands and Belgium."},"metadata":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentItemMetadata"}],"description":"Your own labels on this entry. Replaces the whole set. Omit to leave it as it is."}},"description":"The parts of a question and answer to change. An omitted field is left as it is."},"WhatsAppAgentWebsiteID":{"type":"string","minLength":1,"pattern":"^waaw_[0-9a-hjkmnp-tv-z]{26}$","example":"waaw_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppAgentWebsiteCrawlStatus":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["pending","in_progress","completed","completed_no_data","failed"],"description":"Where WhatsApp stands with reading the website.\n\n- `pending` means WhatsApp has accepted the site and not started reading it.\n- `in_progress` means it is reading the site now.\n- `completed` means it read the site and the agent answers from it.\n- `completed_no_data` means it read the site and found nothing it could use. The site is healthy; there was nothing to extract. The agent does not answer from it.\n- `failed` means it could not read a single page; `crawl_error` says why. Update the entry once the site is reachable to have WhatsApp try again.\n\nWhatsApp owns this set.\n","example":"completed"},"WhatsAppAgentWebsite":{"allOf":[{"type":"object","required":["id","url","crawl_status"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentWebsiteID"}],"readOnly":true,"description":"Unique identifier for the website entry."},"url":{"type":"string","format":"uri","minLength":1,"maxLength":2048,"readOnly":true,"description":"The page WhatsApp reads to learn about the business.","example":"https://example.com/help/delivery"},"crawl_status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentWebsiteCrawlStatus"}],"readOnly":true,"description":"Where WhatsApp stands with reading the site, refreshed every 15 minutes while the read is under way.\n"},"pages_crawled":{"type":"integer","minimum":0,"readOnly":true,"description":"How many pages WhatsApp read, as of Bird's last check. Absent until the read has settled.","example":14},"last_crawled_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When WhatsApp last finished reading the site, as of Bird's last check. Absent until it has.","example":"2026-08-20T10:45:00Z"},"crawl_error":{"type":"string","minLength":1,"readOnly":true,"description":"Why WhatsApp could not read the site, in its own words, such as the site being unreachable or refusing crawlers. Present only when `crawl_status` is `failed`.\n","example":"Blocked by robots.txt"}},"description":"A website the agent draws answers from. WhatsApp reads it in the background, so the site becomes usable once `crawl_status` is `completed`, not when this entry is created.\n"},{"$ref":"#/components/schemas/Timestamps"}]},"WhatsAppAgentWebsiteList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of the websites the agent reads.","items":{"$ref":"#/components/schemas/WhatsAppAgentWebsite"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppAgentWebsiteCreate":{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","format":"uri","minLength":1,"maxLength":2048,"description":"The page WhatsApp should read to learn about the business. WhatsApp starts reading as soon as it accepts the address, and the entry's `crawl_status` says when it has finished.\n","example":"https://example.com/help/delivery"}},"description":"A website to add. Change the address later by updating the entry, which makes WhatsApp read the new page in place of the old one."},"WhatsAppAgentWebsiteUpdate":{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","format":"uri","minLength":1,"maxLength":2048,"description":"The new address for WhatsApp to read. WhatsApp starts reading it as soon as it accepts the change, and the entry's `crawl_status` goes back to `pending` until it has finished.\n","example":"https://example.com/help/returns"}},"description":"The new address for a website the agent reads. What WhatsApp read from the old address is replaced by what it reads from the new one."},"WhatsAppAgentFile":{"type":"object","additionalProperties":false,"required":["id","file_name","mime_type","size_bytes","url","created_at"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppFileID"}],"readOnly":true,"description":"Unique identifier for the file entry."},"file_name":{"type":"string","minLength":1,"maxLength":255,"readOnly":true,"description":"The name the file was uploaded under, as WhatsApp lists it.","example":"returns-policy-2026.pdf"},"mime_type":{"type":"string","minLength":1,"readOnly":true,"description":"Media type of the uploaded bytes, as detected at upload; WhatsApp does not report one.","example":"application/pdf"},"size_bytes":{"type":"integer","format":"int64","minimum":1,"readOnly":true,"description":"Size of the uploaded bytes.","example":482113},"url":{"type":"string","format":"uri","minLength":1,"readOnly":true,"description":"Where to fetch the bytes: this API's `content` link for the file, which redirects to a short-lived storage URL. Fetch it with your API key and follow the redirect without the `Authorization` header; the storage URL is pre-authorized and refuses a request that carries one.\n","example":"https://platform.bird.com/v1/whatsapp/numbers/wan_01kya19eknftrs2s6p82asmvnh/agent/files/waf_01kyb2m4xq7whs0d8n3prv6tez/content"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the file was uploaded.","example":"2026-08-20T10:31:00Z"}},"description":"A document the agent answers from. WhatsApp holds only the name, so Bird keeps a copy of the bytes and serves them through `url`; delete the entry and upload again to replace one.\n"},"WhatsAppAgentFileList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of the files the agent answers from.","items":{"$ref":"#/components/schemas/WhatsAppAgentFile"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppAgentFileCreate":{"type":"object","additionalProperties":false,"required":["file_name","file"],"properties":{"file_name":{"type":"string","minLength":1,"maxLength":255,"description":"The name to file the document under. WhatsApp lists the file by it, and so does this API.","example":"returns-policy-2026.pdf"},"file":{"type":"string","format":"binary","minLength":1,"description":"The document itself, up to 100 MB. WhatsApp accepts PDF, Word (`.doc`, `.docx`), PNG, JPEG, CSV and Excel (`.xlsx`); anything else is refused with `422`.\n"}},"description":"A document to upload for the agent to answer from, sent as a `multipart/form-data` body with the bytes in the `file` part.\n"},"WhatsAppAgentSkillID":{"type":"string","minLength":1,"pattern":"^waas_[0-9a-hjkmnp-tv-z]{26}$","example":"waas_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppAgentSkill":{"allOf":[{"type":"object","required":["id","title","description","skill"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentSkillID"}],"readOnly":true,"description":"Unique identifier for the skill."},"title":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","readOnly":true,"description":"A short handle for the skill, as WhatsApp holds it. It is for you to tell skills apart; the agent reads `description` and `skill`.","example":"cancel-order"},"description":{"type":"string","minLength":1,"maxLength":1024,"readOnly":true,"description":"When the agent should use this skill. Keep it to the trigger; what to do goes in `skill`.","example":"A contact asks to cancel, change or return an order they have already placed."},"skill":{"type":"string","minLength":1,"maxLength":20000,"readOnly":true,"description":"What the agent does when the skill applies, as an instruction to the agent rather than a fact about the business, which is what `business_info` and the questions and answers are for.\n","example":"Ask for the order number. Tell the contact the cancellation window is 24 hours from ordering. If it has passed, offer a return instead and explain the 30-day return policy."}},"description":"An instruction that shapes what the agent does in a situation you describe."},{"$ref":"#/components/schemas/Timestamps"}]},"WhatsAppAgentSkillList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of the agent's skills.","items":{"$ref":"#/components/schemas/WhatsAppAgentSkill"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppAgentSkillCreate":{"type":"object","additionalProperties":false,"required":["title","description","skill"],"properties":{"title":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","description":"A short handle for the skill, in lowercase letters, digits and hyphens. It is for you to tell skills apart; the agent reads `description` and `skill`.","example":"cancel-order"},"description":{"type":"string","minLength":1,"maxLength":1024,"description":"When the agent should use this skill. Keep it to the trigger; what to do goes in `skill`.","example":"A contact asks to cancel, change or return an order they have already placed."},"skill":{"type":"string","minLength":1,"maxLength":20000,"description":"What the agent does when the skill applies, as an instruction to the agent rather than a fact about the business, which is what `business_info` and the questions and answers are for.\n","example":"Ask for the order number. Tell the contact the cancellation window is 24 hours from ordering. If it has passed, offer a return instead and explain the 30-day return policy."}},"description":"A skill to add. It reaches WhatsApp before this request answers and is used from the next conversation turn."},"WhatsAppAgentSkillUpdate":{"type":"object","additionalProperties":false,"properties":{"title":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","description":"A short handle for the skill, in lowercase letters, digits and hyphens. Omit to leave it as it is.","example":"cancel-order"},"description":{"type":"string","minLength":1,"maxLength":1024,"description":"When the agent should use this skill. Omit to leave it as it is.","example":"A contact asks to cancel, change or return an order they have already placed."},"skill":{"type":"string","minLength":1,"maxLength":20000,"description":"What the agent does when the skill applies. Omit to leave it as it is.","example":"Ask for the order number. Tell the contact the cancellation window is 24 hours from ordering. If it has passed, offer a return instead and explain the 30-day return policy."}},"description":"The parts of a skill to change. An omitted field is left as it is."},"WhatsAppAgentUISkillID":{"type":"string","minLength":1,"pattern":"^waau_[0-9a-hjkmnp-tv-z]{26}$","example":"waau_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppAgentUISkillComponentType":{"type":"string","enum":["cta_url","image","interactive_list","interactive_reply_buttons","location","location_request","carousel_url","carousel_quick_reply"],"x-enum-varnames":["CTAURLButton","ImageAttachment","InteractiveList","InteractiveReplyButtons","LocationPin","LocationRequest","CarouselURL","CarouselQuickReply"],"description":"Which of WhatsApp's rich message components the agent may answer with.\n\n- `cta_url` is a button that opens a link.\n- `image` is an image sent with the answer.\n- `interactive_list` is a list of options the contact picks one of.\n- `interactive_reply_buttons` is a small set of buttons the contact taps to reply.\n- `location` is a pin for a place.\n- `location_request` asks the contact to share where they are.\n- `carousel_url` is a set of cards the contact swipes through, each carrying a link button.\n- `carousel_quick_reply` is a set of cards the contact swipes through, each carrying reply buttons.\n\nThe set is closed on purpose: a value outside it is refused before it reaches WhatsApp, so a typo fails at the client rather than as a `422`. WhatsApp's `flow` component is not offered, because Bird does not support WhatsApp Flows; a component WhatsApp adds joins the list once Bird supports it.\n","example":"interactive_reply_buttons"},"WhatsAppAgentUISkill":{"allOf":[{"type":"object","description":"An instruction that lets the agent answer with one of WhatsApp's rich message components instead of plain text.\n","required":["id","title","component_type","enabled","instruction"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentUISkillID"}],"readOnly":true,"description":"Unique identifier for the rich-message skill."},"title":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","readOnly":true,"description":"A short handle for the skill, in lowercase letters, digits and hyphens, for you to tell skills apart.","example":"nearest-pickup-points"},"component_type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentUISkillComponentType"}],"readOnly":true,"description":"Which rich message component the agent may use. Fixed when the skill is created; add a new skill to use another."},"enabled":{"type":"boolean","readOnly":true,"description":"Whether the agent may use this skill.","example":true},"instruction":{"type":"string","minLength":1,"maxLength":4096,"readOnly":true,"description":"When the agent should reach for this component, and what to put in it.","example":"Offer the three nearest pickup points as reply buttons once a contact has given their postcode."}}},{"$ref":"#/components/schemas/Timestamps"}]},"WhatsAppAgentUISkillList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of the agent's rich-message skills.","items":{"$ref":"#/components/schemas/WhatsAppAgentUISkill"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppAgentUISkillCreate":{"type":"object","additionalProperties":false,"required":["title","component_type","enabled","instruction"],"properties":{"title":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","description":"A short handle for the skill, in lowercase letters, digits and hyphens, for you to tell skills apart.","example":"nearest-pickup-points"},"component_type":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentUISkillComponentType"}],"description":"Which rich message component the agent may use. It cannot be changed afterwards; add a new skill to use another."},"enabled":{"type":"boolean","description":"Whether the agent may use this skill from the start.","example":true},"instruction":{"type":"string","minLength":1,"maxLength":4096,"description":"When the agent should reach for this component, and what to put in it.","example":"Offer the three nearest pickup points as reply buttons once a contact has given their postcode."}},"description":"A rich-message skill to add. It reaches WhatsApp before this request answers and is used from the next conversation turn."},"WhatsAppAgentUISkillUpdate":{"type":"object","additionalProperties":false,"properties":{"title":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[a-z0-9]+(-[a-z0-9]+)*$","description":"A short handle for the skill, in lowercase letters, digits and hyphens. Omit to leave it as it is.","example":"nearest-pickup-points"},"enabled":{"type":"boolean","description":"Whether the agent may use this skill. Omit to leave it as it is.","example":true},"instruction":{"type":"string","minLength":1,"maxLength":4096,"description":"When the agent should reach for this component, and what to put in it. Omit to leave it as it is.","example":"Offer the three nearest pickup points as reply buttons once a contact has given their postcode."}},"description":"The parts of a rich-message skill to change. An omitted field is left as it is. `component_type` is fixed at creation and is not here.\n"},"WhatsAppAgentAllowlistEntryID":{"type":"string","minLength":1,"pattern":"^waae_[0-9a-hjkmnp-tv-z]{26}$","example":"waae_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppAgentAllowlistEntry":{"type":"object","additionalProperties":false,"required":["id","phone_number","created_at"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentAllowlistEntryID"}],"readOnly":true,"description":"Unique identifier for the allowlist entry."},"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"The contact the agent may answer, in E.164 format with the leading plus sign.","example":"+31612340001"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the contact was added.","example":"2026-08-20T10:31:00Z"}},"description":"One contact the agent may answer while `ai_audience` is `allowlist`. The list is ignored while the audience is `everyone`, and kept, so narrowing the audience again picks it back up.\n"},"WhatsAppAgentAllowlistEntryList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of the contacts the agent may answer.","items":{"$ref":"#/components/schemas/WhatsAppAgentAllowlistEntry"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppAgentAllowlistEntryCreate":{"type":"object","additionalProperties":false,"required":["phone_number"],"properties":{"phone_number":{"type":"string","minLength":1,"description":"The contact to allow, in E.164 format with the leading plus sign. It is normalized before it reaches WhatsApp, so `+31612340001` and `+31 6 1234 0001` add the same contact. WhatsApp keys its allowlist by phone number and resolves the contact's other identifiers itself. A contact already on the list is refused with `409`.\n","example":"+31612340001"}},"description":"A contact to add to the agent's allowlist. The agent answers them from the next conversation turn while `ai_audience` is `allowlist`."},"WhatsAppAgentTestMessageRequest":{"type":"object","additionalProperties":false,"required":["text"],"properties":{"text":{"type":"string","minLength":1,"description":"What to say to the agent, as a contact would write it.","example":"Do you deliver to Rotterdam on a Saturday?"},"meta_conversation_id":{"type":"string","minLength":1,"maxLength":255,"description":"Continue an earlier test conversation, using the `meta_conversation_id` its reply returned. Omit to start a new one, which the agent answers with no memory of anything you asked before.\n","example":"pfbid0AW2iYSFGpo9H5ZhS65oe6VqCAanTm36N3w6xmuEghnwm7zjYNv2rHrgQYjkEEFwRl"}},"description":"A message to put to the agent. It is answered by the agent's live configuration, which is the only one there is, so a change you have just saved is what gets tested.\n"},"WhatsAppAgentTestMessageResponse":{"type":"object","additionalProperties":false,"required":["meta_message_id","meta_conversation_id","quick_replies","product_variant_ids"],"properties":{"meta_message_id":{"type":"string","minLength":1,"readOnly":true,"description":"WhatsApp's identifier for this exchange.","example":"pfbid0AW2iYSFGpo9H5ZhS65oe6VqCAanTm36N3w6xmuEghnwm7zjYNv2rHrgQYjkEEFwRl_1788403641552"},"reply":{"type":"string","minLength":1,"readOnly":true,"description":"What the agent answered. Absent when it chose to say nothing, which happens after it has handed the conversation over and when `no_response_reason` is set. Nothing about this call reaches a contact.\n","example":"Yes. Saturday deliveries reach Rotterdam if you order before 16:00 on Friday."},"meta_conversation_id":{"type":"string","minLength":1,"readOnly":true,"description":"WhatsApp's handle for the test conversation this exchange belongs to. Pass it back as `meta_conversation_id` to ask a follow-up the agent answers in context.\n","example":"pfbid0AW2iYSFGpo9H5ZhS65oe6VqCAanTm36N3w6xmuEghnwm7zjYNv2rHrgQYjkEEFwRl"},"occurred_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the agent produced the answer. Absent when WhatsApp did not report it.","example":"2026-09-01T09:20:12Z"},"handoff_reason":{"type":"string","minLength":1,"readOnly":true,"description":"Why the agent would have given the conversation to your team instead of answering. Present only when WhatsApp reports one.\n","example":"The contact asked about an invoice."},"no_response_reason":{"type":"string","minLength":1,"readOnly":true,"description":"Why the agent said nothing, in WhatsApp's own words, such as the number failing its eligibility check. Present only when WhatsApp reports one.\n","example":"ELIGIBILITY_CHECK_FAILED"},"quick_replies":{"type":"array","readOnly":true,"description":"Short answers the agent offered the contact to tap instead of typing. Empty when it offered none.","items":{"type":"string"},"example":["Yes","order now","Tell me the price"]},"product_variant_ids":{"type":"array","readOnly":true,"description":"The catalogue product variants the answer refers to, by the id WhatsApp Manager shows on the account's catalogue. Bird has no catalogue surface, so that is where an id is looked up. Empty when the answer refers to none.","items":{"type":"string","minLength":1},"example":["1234567890123456"]}},"description":"WhatsApp's answer to a test message, passed through with its field names aligned to this API: what the agent answered, or why it did not, and whether it would have handed the conversation over. Nothing is stored.\n"},"WhatsAppAgentHandoffEventID":{"type":"string","minLength":1,"pattern":"^waah_[0-9a-hjkmnp-tv-z]{26}$","example":"waah_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppAgentHandoffInitiator":{"type":"string","minLength":1,"x-extensible-enum":["app","agent"],"description":"What caused this handoff.\n\n- `app` means your own application asked for it, with a handoff request on this number.\n- `agent` means the agent passed the conversation up on its own, after telling the contact it was handing over.\n\nIt is not the side that gave control up: `new_controller` already says which side holds the conversation afterwards, and the two are always opposites.\n\nA message you send also moves control, and WhatsApp announces nothing when it does, so no handoff is recorded for it. Should WhatsApp begin reporting those, they arrive here as a value this list does not yet name, so treat an unrecognized value as a cause added later rather than an error.\n","example":"app"},"WhatsAppAgentHandoff":{"type":"object","additionalProperties":false,"required":["id","to","new_controller","initiator","reason","occurred_at"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentHandoffEventID"}],"readOnly":true,"description":"Unique identifier for the handoff."},"to":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"readOnly":true,"description":"The contact whose conversation changed hands. Carries the business-scoped user ID as well as the phone number whenever the conversation was open at the time."},"new_controller":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentController"}],"readOnly":true,"description":"Who held the conversation from this moment on."},"initiator":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentHandoffInitiator"}],"readOnly":true,"description":"What caused the handoff."},"reason":{"type":["string","null"],"readOnly":true,"description":"The note whoever gave up control left for the next handler, or null when they left none. It is never shown to the contact.","example":"Contact asked for a refund on order 88213."},"occurred_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the conversation changed hands.","example":"2026-09-03T14:02:11Z"}},"description":"One change of who answers a contact's conversation on this number: a handoff you asked for, or the agent passing the conversation up after its handoff message. `initiator` says which.\n"},"WhatsAppAgentHandoffList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of handoffs, newest first.","items":{"$ref":"#/components/schemas/WhatsAppAgentHandoff"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppAgentHandoffAction":{"type":"string","minLength":1,"enum":["release","take"],"description":"What to do with control of the conversation.\n\n- `release` gives control up, so the agent picks the conversation back up. Your application must hold it.\n- `take` claims control for your application. WhatsApp accepts this only from the escalation partner configured on the number.\n\nSending a message from this workspace takes control on its own, so an ordinary reply needs no call here.\n","example":"release"},"WhatsAppAgentHandoffRequest":{"type":"object","additionalProperties":false,"required":["to","action"],"properties":{"to":{"type":"string","minLength":1,"description":"The contact whose conversation this is about: a phone number in E.164 format (for example `+31612340001`) or the contact's business-scoped user ID (for example `US.13491208655302741918`), the same forms a message's `to` accepts. A phone number is normalized before the call reaches WhatsApp, so spacing does not matter. WhatsApp accepts either form on this call, and `to` is its own name for it.\n","example":"+31612340001"},"action":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentHandoffAction"}],"description":"What to do with control of the conversation."},"reason":{"type":"string","minLength":1,"maxLength":2000,"description":"A note carried to whoever takes control, so the next handler knows what happened before them. It is delivered on the handover event and is never shown to the contact.\n","example":"Contact asked for a refund on order 88213."}},"description":"A change of control over one contact's conversation."},"WhatsAppAgentNotificationStatus":{"type":"string","minLength":1,"enum":["accepted","success","skipped","failed"],"description":"Where a notification you sent the agent stands. One state is transient and three are final.\n\n- `accepted` means Bird holds the notification: it is on its way to WhatsApp, or WhatsApp is still working on it. Nothing is charged for a notification, unlike a message that reads `accepted`.\n- `success` means the agent acted on it. Whatever it said to the contact arrives as `whatsapp.agent.sent` on that contact's conversation.\n- `skipped` means the agent read it and chose to say nothing; `skipped_reason` says why.\n- `failed` means WhatsApp refused it or reported a failure, or no outcome arrived within a day; `error` says why.\n","example":"success"},"WhatsAppAgentNotificationID":{"type":"string","minLength":1,"pattern":"^waan_[0-9a-hjkmnp-tv-z]{26}$","example":"waan_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppAgentNotificationError":{"type":"object","additionalProperties":false,"readOnly":true,"required":["description","meta_error_code"],"description":"Why a notification sent to the agent failed.","properties":{"description":{"type":"string","minLength":1,"readOnly":true,"description":"WhatsApp's own explanation, passed through: what it said when it refused the notification, or its failure summary once it had worked on it. Show it to the person who sent the notification; never match on its text. Carries Bird's own words instead when the failure was Bird's verdict, such as no outcome arriving within a day.\n","example":"Event payload exceeds the maximum size."},"meta_error_code":{"type":["string","null"],"readOnly":true,"description":"WhatsApp's most specific code when it refused the notification outright: its error subcode where it sent one, otherwise its top-level code. Treat it as an opaque string. Null when WhatsApp took the notification and reported the failure later, which carries no code, and when the failure was Bird's own verdict.\n","example":"100"}}},"WhatsAppAgentNotification":{"allOf":[{"type":"object","description":"A notification you sent the agent about one contact, and what came of it. The agent decides whether to write to the contact about it; that message, if any, shows up on the contact's conversation.\n","required":["id","to","name","description","payload","status"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentNotificationID"}],"readOnly":true,"description":"Unique identifier for the notification."},"to":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"readOnly":true,"description":"The contact the notification was about: the phone number or business-scoped user ID you addressed it to, in the same shape a message's `to` uses.\n"},"name":{"type":"string","minLength":1,"maxLength":256,"readOnly":true,"description":"Your own name for what happened, as you sent it.","example":"order_shipped"},"description":{"type":"string","minLength":1,"maxLength":1024,"readOnly":true,"description":"What happened, as you sent it.","example":"Order 88213 left the warehouse and arrives on Thursday."},"payload":{"type":"string","minLength":1,"maxLength":4096,"readOnly":true,"description":"The data you attached, as you sent it.","example":"{\"order_id\":\"88213\",\"carrier\":\"PostNL\"}"},"status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentNotificationStatus"}],"readOnly":true,"description":"Where the notification stands. `accepted` from the moment Bird takes it, then one of the three final states once WhatsApp has answered. `updated_at` is when it last moved.\n"},"skipped_reason":{"type":"string","minLength":1,"readOnly":true,"description":"WhatsApp's own account of why the agent chose to say nothing, passed through. Present only when `status` is `skipped`. Show it to the person who sent the notification; never match on its text.\n","example":"The contact's conversation is currently held by the business."},"error":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentNotificationError"}],"readOnly":true,"description":"Why the notification failed. Present only when `status` is `failed`."}}},{"$ref":"#/components/schemas/Timestamps"}]},"WhatsAppAgentNotificationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of the notifications sent to the agent.","items":{"$ref":"#/components/schemas/WhatsAppAgentNotification"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppAgentNotificationCreate":{"type":"object","additionalProperties":false,"required":["to","name","description","payload"],"properties":{"to":{"type":"string","minLength":1,"description":"The contact the notification is about: a phone number in E.164 format (for example `+31612340001`), or the contact's business-scoped user ID (for example `US.13491208655302741918`), the same forms a message's `to` accepts. A phone number is normalized before the call reaches WhatsApp, so spacing does not matter. WhatsApp documents a phone number for this call; a business-scoped user ID is passed through as given.\n","example":"+31612340001"},"name":{"type":"string","minLength":1,"maxLength":256,"description":"Your own name for what happened, such as `payment_received` or `order_shipped`. The agent reads it as the kind of thing that happened, so keep one name per kind. WhatsApp calls this the event type.\n","example":"order_shipped"},"description":{"type":"string","minLength":1,"maxLength":1024,"description":"What happened, in a sentence the agent can tell the contact.","example":"Order 88213 left the warehouse and arrives on Thursday."},"payload":{"type":"string","minLength":1,"maxLength":4096,"description":"Details the agent may draw on when it writes to the contact, as one JSON string. WhatsApp passes it to the agent unchanged and does not read it itself.\n","example":"{\"order_id\":\"88213\",\"carrier\":\"PostNL\",\"eta\":\"2026-09-04\"}"}},"description":"Something that happened in your systems that the agent should tell the contact about, such as a payment landing or an order shipping. WhatsApp processes it in the background, so read the notification back for what came of it.\n"},"WhatsAppNumberEventSortField":{"type":"string","enum":["created_at"],"default":"created_at","description":"Sortable fields for a WhatsApp number's event list."},"WhatsAppNumberEventID":{"type":"string","minLength":1,"pattern":"^wne_[0-9a-hjkmnp-tv-z]{26}$","example":"wne_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppNumberEvent":{"type":"object","additionalProperties":false,"required":["id","type","summary","metadata","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppNumberEventID","description":"Event ID."},"type":{"type":"string","minLength":1,"description":"Type of number event. `whatsapp_number.messaging_limit_updated` and `whatsapp_number.profile_name_update` are reported by WhatsApp as they happen; `whatsapp_number.quality_rating_updated` is observed when Bird next reads the number, so it can lag the change by up to an hour. `whatsapp_number.status_changed` records every move of the `status` field on the number itself, whichever side caused it. Open enum: new event types may be added over time, so treat any unrecognized value as a future event rather than an error. The values below are the types known at this version.","x-extensible-enum":["whatsapp_number.created","whatsapp_number.messaging_limit_updated","whatsapp_number.profile_name_update","whatsapp_number.quality_rating_updated","whatsapp_number.status_changed"],"example":"whatsapp_number.quality_rating_updated"},"summary":{"type":"string","minLength":1,"description":"Human-readable summary of what changed.","example":"Quality rating dropped to medium."},"metadata":{"type":"object","description":"Structured details for the event. `from` and `to` carry the values that changed, and `from` is absent when the number had no prior value to report. A status change into `failed` also carries the `reason`; a display-name decision carries `new_display_name`, `decision`, and, when WhatsApp named one for a rejection, `rejection_reason`. A messaging-limit change also carries the `trigger` WhatsApp named for it, such as `onboarding` or `throughput_upgrade`, when it named one. A `whatsapp_number.created` event carries the `source` the number came from, and its `phone_number` once one is known.","additionalProperties":true},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"When the event was recorded."}}},"WhatsAppNumberEventList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of number events, newest first by default.","items":{"$ref":"#/components/schemas/WhatsAppNumberEvent"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppDisplayNameStatus":{"type":"string","description":"Where WhatsApp's review of the display name stands. `available_without_review` means the name met WhatsApp's criteria for immediate use without a review step, and `none` means no display name has been submitted yet. WhatsApp adds states over time, so a value outside this list can be returned.","x-extensible-enum":["approved","available_without_review","declined","expired","non_exists","pending_review","none"],"example":"approved"},"WhatsAppUsernameStatus":{"type":"string","description":"Where the username stands with WhatsApp. WhatsApp adds states over time, so a value outside this list can be returned.","x-extensible-enum":["approved","reserved","deleted"],"example":"approved"},"WhatsAppBusinessVertical":{"type":"string","description":"The industry WhatsApp shows on the business profile. WhatsApp adds categories over time, so a value outside this list can be returned.","x-extensible-enum":["other","auto","beauty","apparel","edu","entertain","event_plan","finance","grocery","govt","hotel","health","nonprofit","prof_services","retail","travel","restaurant","alcohol","online_gambling","physical_gambling","otc_drugs"],"example":"retail"},"WhatsAppNumberProfile":{"type":"object","additionalProperties":false,"description":"The business profile WhatsApp shows to people this number messages. It is read from WhatsApp on each request rather than from a stored copy, so it is always current and a WhatsApp outage makes it briefly unavailable.","properties":{"display_name":{"type":"string","readOnly":true,"description":"The name WhatsApp verifies for this number. Once WhatsApp approves it, it appears at the top of a chat with this number; `display_name_status` is what says whether it has. Set when the number was connected, and changed from the dashboard or the CLI, as [WhatsApp phone numbers](/docs/guides/whatsapp/phone-number-setup) explains. This field still returns the current name until a requested change completes.\n","example":"Lucky Shrub"},"display_name_status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppDisplayNameStatus"}],"readOnly":true,"description":"Where WhatsApp's review of the display name stands. A name still under review is not yet shown at the top of a chat.\n"},"new_display_name":{"type":"string","readOnly":true,"description":"The display name whose change has been requested, whether or not WhatsApp is reviewing it. Absent when no change is pending.\n","example":"Lucky Shrub Garden Center"},"new_display_name_status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppDisplayNameStatus"}],"readOnly":true,"description":"Where the requested display name stands with WhatsApp, including whether it is being reviewed or was accepted for immediate use without a review. Absent when no change is pending. If WhatsApp accepts the name it becomes `display_name`. Every other outcome leaves the number on the name it already had: `declined` is WhatsApp refusing the name, and `expired` is a request that no longer stands and has to be made again.\n"},"username":{"type":"string","readOnly":true,"description":"The username WhatsApp users can find this number by, without an `@`. Absent when the number has no username. Once set it cannot be removed through this API.\n","example":"goldcrest.support"},"username_status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppUsernameStatus"}],"readOnly":true,"description":"Where the username stands with WhatsApp. Absent when the number has no username.\n"},"about":{"type":"string","maxLength":139,"description":"The short line shown under the business name in a chat.","example":"Open Monday to Friday, 9am to 6pm CET."},"address":{"type":"string","maxLength":256,"description":"The business address shown on the profile.","example":"Trompenburgstraat 2C, 1079 TX Amsterdam"},"description":{"type":"string","maxLength":256,"description":"The longer description shown on the profile.","example":"Bird is the platform for messaging with your customers."},"email":{"type":"string","format":"email","maxLength":128,"description":"The contact email shown on the profile.","example":"hello@bird.com"},"vertical":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessVertical"}],"description":"The industry WhatsApp shows on the profile."},"websites":{"type":"array","maxItems":2,"items":{"type":"string","maxLength":256},"description":"Up to two websites shown on the profile.","example":["https://bird.com"]},"profile_picture_url":{"type":"string","readOnly":true,"description":"A link to the profile picture WhatsApp currently shows. WhatsApp signs this link and it expires within days, so load it when you display it and never store it. It is served with permissive cross-origin headers, so a browser can load it directly.","example":"https://pps.whatsapp.net/v/t61.24694-24/643148303_1005107588793925.jpg"}}},"WhatsAppUsernameTransferAction":{"type":"string","enum":["none","force_transfer"],"default":"none","description":"What to do when the username you are asking for is already in use by another of your own WhatsApp numbers. `none` leaves it where it is and the request is refused; `force_transfer` moves it to this number, removing it from the other one. Only usernames on numbers in the same WhatsApp business portfolio can be transferred. Only applies when `username` is also being set in the same request; it is ignored otherwise. The response to a forced transfer describes only the number you called it on. It never names the number that lost the username, so restoring that number's username means knowing which one it was beforehand.","example":"force_transfer"},"WhatsAppNumberProfileUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"description":"The profile fields to change. Omit a field to leave it as it is. Not every field can be cleared by sending null, and each field's description says whether it can.","properties":{"display_name":{"type":"string","minLength":1,"maxLength":75,"pattern":"\\S","description":"The name shown at the top of a chat with this number.\n\nThe change is not immediate. The requested name is returned as\n`new_display_name` and its progress as `new_display_name_status`, while\n`display_name` still returns the current name until the change completes.\nA requested change cannot be withdrawn, so wait for WhatsApp's verdict\nrather than sending a second one.\n\nWhat happens to the name you send:\n\n- Leading and trailing spaces are removed, and the 75-character limit is\n  measured on the value you send, before they are.\n- A name that is empty or only spaces returns a validation error.\n- A name that breaks WhatsApp's display name guidelines is refused by\n  WhatsApp itself.\n- Sending the name the number already has changes nothing, and leaves a\n  change in progress untouched.\n\nOmit this to leave the name as it is; there is no value that removes it.\nWhat a rename costs, and which numbers cannot be renamed over this API,\nis on the operation itself.\n","example":"Lucky Shrub Garden Center"},"about":{"type":"string","minLength":1,"maxLength":139,"description":"The short line shown under the business name in a chat. Cannot be cleared because WhatsApp requires at least one character.","example":"Open Monday to Friday, 9am to 6pm CET."},"address":{"type":["string","null"],"maxLength":256,"description":"The business address shown on the profile.","example":"Trompenburgstraat 2C, 1079 TX Amsterdam"},"description":{"type":["string","null"],"maxLength":256,"description":"The longer description shown on the profile.","example":"Bird is the platform for messaging with your customers."},"email":{"type":["string","null"],"maxLength":128,"description":"The contact email shown on the profile.","example":"hello@bird.com"},"vertical":{"$ref":"#/components/schemas/WhatsAppBusinessVertical"},"websites":{"type":["array","null"],"maxItems":2,"items":{"type":"string","maxLength":256,"pattern":"^https?://"},"description":"Up to two websites, each a full address starting with http:// or https://.","example":["https://bird.com"]},"profile_picture_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The URL of a file in your workspace's media library, which becomes the profile picture WhatsApp shows for this number. A URL from anywhere else is refused. JPEG or PNG up to 5 MB; WhatsApp crops the picture to a circle, so a square image renders best. Omit this to keep the current picture. The profile returns WhatsApp's copy at a different URL.\n","example":"https://eu1.assets.birdusercontent.com/2f8a1c/ast_01jqn8m4x2e7v9k3b6t0dz5rhw/logo.png"},"username":{"type":"string","minLength":3,"maxLength":35,"pattern":"^[A-Za-z0-9._]+$","description":"The username WhatsApp users can find this number by, without an `@`. Letters, digits, periods and underscores only, 3 to 35 characters, with at least one letter, and it may not start or end with a period. Comparison ignores case, so `MyID` and `myid` are the same username, while `my.id` and `my_id` are different ones. A username is unique across all of WhatsApp, so one already in use is refused. Omit this to leave the current username as it is; there is no value that removes it.\n","example":"goldcrest.support"},"username_transfer_action":{"$ref":"#/components/schemas/WhatsAppUsernameTransferAction"}}},"AMBBusinessID":{"type":"string","minLength":1,"pattern":"^abz_[0-9a-hjkmnp-tv-z]{26}$","example":"abz_01krdgeqcxet5s7t44vh8rt9mg"},"AMBBusinessStatus":{"type":"string","minLength":1,"enum":["draft","submitted","in_review","approved","rejected","suspended"],"description":"Where the business record stands with Apple. Values, in lifecycle order:\n\n- `draft` means the record exists but carries no Apple Business ID yet. You move it to `submitted` by supplying one.\n- `submitted` means you have supplied the Apple Business ID and Bird has not yet confirmed the connection with Apple.\n- `in_review` means Bird has confirmed the connection and Apple's own review of the business is underway.\n- `approved` means Apple has approved the business. It can send and receive.\n- `rejected` means Apple declined the business, whether that followed a review or the business was turned down straight from `submitted`.\n- `suspended` means an approved business has had its ability to send withdrawn. It cannot receive new messages while suspended; conversation-close callbacks are still accepted.\n\nYou drive `draft` to `submitted` by supplying the Apple Business ID. Every other transition is recorded by Bird staff as Apple's review of the business progresses.\n","example":"approved"},"AMBMSPApp":{"type":"string","minLength":1,"enum":["eu1","us1"],"description":"The regional Messaging Service Provider application this business is registered under. A separate application runs in each of `eu1` and `us1`, each with its own Apple-issued identifiers, so a business's message content never leaves the region its application runs in. Set when the business is created, and expected to require registering with Apple again to change, so treat it as fixed for the life of the business.","example":"eu1"},"AMBBusiness":{"type":"object","additionalProperties":false,"required":["id","apple_business_id","display_name","status","status_reason","msp_app","invitations_enabled","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/AMBBusinessID","readOnly":true,"description":"Unique identifier for the business record."},"apple_business_id":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"The Business ID Apple issued for this brand. Null until you submit one, which moves the record from `draft` to `submitted`.","example":"260817-Acme-Retail-EU"},"display_name":{"type":"string","minLength":1,"description":"The brand name shown for this business record inside Bird. This is not the name Apple shows to customers; that name comes from the Apple Business Register account behind the Business ID.","example":"Acme Retail"},"status":{"$ref":"#/components/schemas/AMBBusinessStatus","readOnly":true,"description":"Where the business record stands with Apple."},"status_reason":{"type":["string","null"],"readOnly":true,"description":"Free-text detail behind `status`, such as why Apple's review ended in `rejected`. Null when Bird has recorded no detail for the current status.","example":null},"msp_app":{"$ref":"#/components/schemas/AMBMSPApp","readOnly":true,"description":"The regional Messaging Service Provider application this business is registered under."},"invitations_enabled":{"type":"boolean","readOnly":true,"description":"Whether this business can send invitations. Apple grants invitation access separately from approving the business itself, so this can be false on an `approved` business.","example":false},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the business record was created.","example":"2026-08-20T09:14:52Z"},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the business record was last changed.","example":"2026-08-25T16:42:01Z"}}},"AMBBusinessList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The business records your workspace holds.","items":{"$ref":"#/components/schemas/AMBBusiness"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AMBBusinessCreateRequest":{"type":"object","additionalProperties":false,"required":["display_name"],"properties":{"display_name":{"type":"string","minLength":1,"maxLength":100,"description":"The brand name shown for this business record inside Bird.","example":"Acme Retail"},"apple_business_id":{"type":"string","minLength":1,"description":"The Business ID Apple issued for this brand, if you already have it. Supplying it creates the business already `submitted`; omit it to create a `draft` and supply the ID later.","example":"260817-Acme-Retail-EU"}}},"AMBBusinessUpdateRequest":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"display_name":{"type":"string","minLength":1,"maxLength":100,"description":"The brand name shown for this business record inside Bird.","example":"Acme Retail"},"apple_business_id":{"type":"string","minLength":1,"description":"The Business ID Apple issued for this brand. Accepted only while the business is `draft`; sending it moves the record to `submitted`. Sending it on a business that already has one, or that is past `draft`, is rejected.","example":"260817-Acme-Retail-EU"}}},"AMBAuthenticationProviderID":{"type":"string","minLength":1,"pattern":"^aauthp_[0-9a-hjkmnp-tv-z]{26}$","example":"aauthp_01krdgeqcxet5s7t44vh8rt9mg"},"AMBAuthenticationMethod":{"type":"string","description":"How Bird authenticates its confidential client to the identity provider. `private_key_jwt` signs a client assertion with a Bird-managed private key; register the provider configuration's public JWK. `client_secret_basic` sends the client credentials in the HTTP Basic authorization header. `client_secret_post` sends them in the token request body.\n","enum":["private_key_jwt","client_secret_basic","client_secret_post"],"minLength":1},"AMBAuthenticationPublicJWK":{"type":"object","additionalProperties":false,"description":"Public signing key to register with your identity provider. The private key never leaves Bird.","required":["kty","kid","use","alg","n","e"],"properties":{"kty":{"type":"string","minLength":1,"description":"Key type."},"kid":{"type":"string","minLength":1,"description":"Key identifier."},"use":{"type":"string","minLength":1,"description":"Intended key use."},"alg":{"type":"string","minLength":1,"description":"Signing algorithm."},"n":{"type":"string","minLength":1,"description":"RSA modulus."},"e":{"type":"string","minLength":1,"description":"RSA exponent."}}},"AMBAuthenticationProvider":{"type":"object","additionalProperties":false,"description":"Business authentication configuration. Contains no client secret, private key or end-user tokens.","required":["id","business_id","issuer","client_id","auth_method","collect_name","collect_email","enabled","revision","callback_url"],"properties":{"id":{"$ref":"#/components/schemas/AMBAuthenticationProviderID","readOnly":true},"business_id":{"$ref":"#/components/schemas/AMBBusinessID","readOnly":true},"issuer":{"type":"string","minLength":1,"description":"HTTPS issuer of your OpenID Connect provider.","format":"uri","maxLength":2048,"readOnly":true},"client_id":{"type":"string","minLength":1,"description":"Client registered for Bird with your provider.","maxLength":512,"readOnly":true},"auth_method":{"$ref":"#/components/schemas/AMBAuthenticationMethod","readOnly":true},"collect_name":{"type":"boolean","description":"Request the profile scope and retain the returned name.","readOnly":true},"collect_email":{"type":"boolean","description":"Request the email scope and retain the returned email and verification flag.","readOnly":true},"enabled":{"type":"boolean","description":"Allow new authentication requests with this provider.","readOnly":true},"revision":{"type":"integer","format":"int64","minimum":1,"description":"Configuration revision bound to authentication attempts.","readOnly":true},"callback_url":{"type":"string","minLength":1,"description":"Register this exact callback URL with your provider.","format":"uri","readOnly":true},"public_jwk":{"$ref":"#/components/schemas/AMBAuthenticationPublicJWK","readOnly":true}}},"AMBAuthenticationProviderUpdateRequest":{"type":"object","additionalProperties":false,"description":"Configure one OpenID Connect provider for this business. Register Bird's callback URL and client details with the provider and Apple. Omit client_secret to preserve it only when the issuer, client and authentication method remain unchanged. Switching to private_key_jwt removes the shared secret.\n","required":["issuer","client_id","auth_method","collect_name","collect_email","enabled"],"properties":{"issuer":{"type":"string","minLength":1,"description":"HTTPS issuer of your OpenID Connect provider.","format":"uri","maxLength":2048},"client_id":{"type":"string","minLength":1,"description":"Client registered for Bird with your provider.","maxLength":512},"auth_method":{"$ref":"#/components/schemas/AMBAuthenticationMethod"},"collect_name":{"type":"boolean","description":"Request the profile scope and retain the returned name."},"collect_email":{"type":"boolean","description":"Request the email scope and retain the returned email and verification flag."},"enabled":{"type":"boolean","description":"Allow new authentication requests with this provider."},"client_secret":{"type":"string","minLength":1,"description":"Replacement client secret of at most 8192 UTF-8 bytes. Required for a new client using a shared secret. Never returned.","maxLength":8192,"writeOnly":true,"x-sensitive":true},"rotate_credentials":{"type":"boolean","default":false,"description":"Generate a new signing key for private_key_jwt. Register the returned public key with your provider before enabling new requests. For secret authentication, supply client_secret instead."}}},"AMBEntryPoint":{"type":"object","additionalProperties":false,"required":["id","group","intent","body"],"properties":{"id":{"type":"string","minLength":1,"maxLength":64,"description":"Identifier for this entry point, chosen by you and unique within the business's entry points. A conversation opened through this entry point carries it as `entry_point`.","example":"website-footer"},"group":{"type":"string","minLength":1,"description":"The group value Apple reports on a conversation opened through this entry point. Matched against the `group` the first inbound message carries.","example":"support"},"intent":{"type":"string","minLength":1,"description":"The intent value Apple reports on a conversation opened through this entry point. Matched against the `intent` the first inbound message carries, together with `group`.","example":"general-inquiry"},"body":{"type":"string","minLength":1,"maxLength":1000,"description":"The message text pre-filled for the customer when they open a conversation through this entry point.","example":"Hi, I have a question about my order."}}},"AMBChannelSettings":{"type":"object","additionalProperties":false,"required":["entry_points","brand_name","logo_asset_id"],"properties":{"entry_points":{"type":"array","description":"The entry points customers can use to open a conversation with this business, each matched against the group and intent an inbound message reports.","items":{"$ref":"#/components/schemas/AMBEntryPoint"}},"default_locale":{"type":["string","null"],"minLength":1,"description":"The locale used for this business when a conversation reports none of its own, in BCP-47 form. Null until you set one; a newly created business has none yet.","example":"en-US"},"brand_name":{"type":"string","minLength":1,"maxLength":100,"description":"The brand name shown on the Bird-hosted landing page customers use to connect this business.","example":"Acme Retail"},"logo_asset_id":{"oneOf":[{"$ref":"#/components/schemas/AssetID"},{"type":"null"}],"description":"The business's logo, as an asset in your media library. Null until one is set, either from Apple's own redirect or from a later change here.","example":"ast_01krdgeqcxet5s7t44vh8rt9mg"}},"description":"A business's entry points, default locale, and landing-page brand settings. OpenID Connect providers use separate business authentication provider operations. Apple Pay merchant settings use the separate business Apple Pay configuration operations. Supply custom iMessage app metadata on each message."},"AMBChannelSettingsUpdateRequest":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"entry_points":{"type":"array","description":"The entry points customers can use to open a conversation with this business. Sending this replaces the entire set; there is no way to add or remove a single entry point without resending the rest.","items":{"$ref":"#/components/schemas/AMBEntryPoint"}},"default_locale":{"type":"string","minLength":1,"description":"The locale used for this business when a conversation reports none of its own, in BCP-47 form.","example":"en-US"},"brand_name":{"type":"string","minLength":1,"maxLength":100,"description":"The brand name shown on the Bird-hosted landing page customers use to connect this business.","example":"Acme Retail"},"logo_asset_id":{"oneOf":[{"$ref":"#/components/schemas/AssetID"},{"type":"null"}],"description":"The business's logo, as an asset in your media library. Send null to clear it.","example":"ast_01krdgeqcxet5s7t44vh8rt9mg"}}},"AMBPaymentEnvironment":{"type":"string","minLength":1,"enum":["test","live"],"description":"Isolates provider test data from live merchant payments."},"AMBApplePayConfigurationID":{"type":"string","minLength":1,"pattern":"^apcfg_[0-9a-hjkmnp-tv-z]{26}$","example":"apcfg_01krdgeqcxet5s7t44vh8rt9mg"},"AMBPaymentConnectionID":{"type":"string","minLength":1,"pattern":"^apcon_[0-9a-hjkmnp-tv-z]{26}$","example":"apcon_01krdgeqcxet5s7t44vh8rt9mg"},"AMBApplePaySupportedNetwork":{"type":"string","enum":["amex","discover","jcb","masterCard","privateLabel","visa"]},"AMBApplePayReadiness":{"type":"string","minLength":1,"enum":["unvalidated","blocked","ready"]},"AMBApplePayValidationCheckStatus":{"type":"string","minLength":1,"enum":["ready","blocked"]},"AMBApplePayValidationCheck":{"type":"object","additionalProperties":false,"required":["name","status","detail","checked_at"],"properties":{"name":{"type":"string","minLength":1},"status":{"$ref":"#/components/schemas/AMBApplePayValidationCheckStatus"},"detail":{"type":["string","null"],"minLength":1},"checked_at":{"type":"string","minLength":1,"format":"date-time"}}},"AMBApplePayCredentialMetadata":{"type":"object","additionalProperties":false,"required":["identity_configured","identity_certificate_fingerprint","identity_certificate_expires_at","processing_certificate_fingerprint","processing_certificate_expires_at","domain_association_configured"],"properties":{"identity_configured":{"type":"boolean"},"identity_certificate_fingerprint":{"type":["string","null"],"minLength":1},"identity_certificate_expires_at":{"type":["string","null"],"minLength":1,"format":"date-time"},"processing_certificate_fingerprint":{"type":["string","null"],"minLength":1},"processing_certificate_expires_at":{"type":["string","null"],"minLength":1,"format":"date-time"},"domain_association_configured":{"type":"boolean"}}},"AMBApplePayConfiguration":{"type":"object","additionalProperties":false,"required":["id","business_id","connection_id","environment","revision","merchant_identifier","country_code","display_name","supported_networks","verification_domain","callback_url","enabled","readiness","device_verified","checks","checked_at","credentials","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/AMBApplePayConfigurationID"},"business_id":{"$ref":"#/components/schemas/AMBBusinessID"},"connection_id":{"$ref":"#/components/schemas/AMBPaymentConnectionID"},"environment":{"$ref":"#/components/schemas/AMBPaymentEnvironment"},"revision":{"type":"integer","format":"int64","minimum":1},"merchant_identifier":{"type":"string","minLength":1,"maxLength":255},"country_code":{"type":"string","minLength":2,"maxLength":2,"pattern":"^[A-Z]{2}$"},"display_name":{"type":"string","minLength":1,"maxLength":128},"supported_networks":{"type":"array","items":{"$ref":"#/components/schemas/AMBApplePaySupportedNetwork"}},"verification_domain":{"type":"string","minLength":1,"description":"Bird gateway domain to register with Apple."},"callback_url":{"type":"string","minLength":1,"format":"uri","description":"Bird gateway callback URL for this configuration."},"enabled":{"type":"boolean"},"readiness":{"$ref":"#/components/schemas/AMBApplePayReadiness"},"device_verified":{"type":"boolean","description":"True only after required real-device evidence has been recorded."},"checks":{"type":"array","items":{"$ref":"#/components/schemas/AMBApplePayValidationCheck"}},"checked_at":{"type":["string","null"],"minLength":1,"format":"date-time"},"credentials":{"$ref":"#/components/schemas/AMBApplePayCredentialMetadata"},"created_at":{"type":"string","minLength":1,"format":"date-time"},"updated_at":{"type":"string","minLength":1,"format":"date-time"}}},"AMBApplePayConfigurationSave":{"type":"object","additionalProperties":false,"properties":{"connection_id":{"$ref":"#/components/schemas/AMBPaymentConnectionID"},"merchant_identifier":{"type":"string","minLength":1},"country_code":{"type":"string","pattern":"^[A-Z]{2}$"},"display_name":{"type":"string","minLength":1,"maxLength":128},"supported_networks":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/AMBApplePaySupportedNetwork"}},"identity_certificate":{"type":"string","format":"byte","writeOnly":true,"x-sensitive":true},"identity_private_key":{"type":"string","format":"byte","writeOnly":true,"x-sensitive":true},"processing_certificate":{"type":"string","format":"byte","writeOnly":true,"x-sensitive":true},"domain_association":{"type":"string","format":"byte","writeOnly":true,"x-sensitive":true},"enabled":{"type":"boolean"}}},"AMBApplePayValidation":{"type":"object","additionalProperties":false,"required":["ready","device_verified","checks","checked_at"],"properties":{"ready":{"type":"boolean"},"device_verified":{"type":"boolean","description":"Remains false until Bird records required real-device evidence."},"checks":{"type":"array","items":{"$ref":"#/components/schemas/AMBApplePayValidationCheck"}},"checked_at":{"type":"string","minLength":1,"format":"date-time"}}},"AMBPaymentProvider":{"type":"string","minLength":1,"enum":["stripe","airwallex"]},"AMBPaymentConnectionAvailability":{"type":"string","minLength":1,"enum":["available","restricted","unavailable"]},"AMBPaymentConnectionGrantStatus":{"type":"string","minLength":1,"enum":["active","disconnecting","disabled","reconnect_required"],"description":"Authorization state. Active means authorization is available, but does not by itself establish payment readiness. Disconnecting means new payments are blocked while teardown and unresolved payment recovery finish; wait for teardown before reconnecting. Disabled means the connection is no longer authorized; reconnect the account before accepting new payments. Reconnect required means authorization must be renewed before accepting new payments."},"AMBPaymentConnection":{"type":"object","additionalProperties":false,"required":["id","provider","environment","account_reference","availability","grant_status","home_region","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/AMBPaymentConnectionID"},"provider":{"$ref":"#/components/schemas/AMBPaymentProvider"},"environment":{"$ref":"#/components/schemas/AMBPaymentEnvironment"},"account_reference":{"type":"string","minLength":1},"display_name":{"type":["string","null"]},"availability":{"$ref":"#/components/schemas/AMBPaymentConnectionAvailability"},"grant_status":{"$ref":"#/components/schemas/AMBPaymentConnectionGrantStatus"},"home_region":{"type":"string","minLength":1},"created_at":{"type":"string","minLength":1,"format":"date-time"},"updated_at":{"type":"string","minLength":1,"format":"date-time"}}},"AMBPaymentAuthorizationOption":{"type":"object","additionalProperties":false,"required":["provider","environment"],"properties":{"provider":{"$ref":"#/components/schemas/AMBPaymentProvider"},"environment":{"$ref":"#/components/schemas/AMBPaymentEnvironment"}}},"AMBPaymentConnectionList":{"allOf":[{"type":"object","required":["data","authorization_options"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AMBPaymentConnection"}},"authorization_options":{"type":"array","description":"Provider and environment combinations available for starting an account connection. These options do not establish Apple Pay readiness for an existing connection.","items":{"$ref":"#/components/schemas/AMBPaymentAuthorizationOption"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AMBPaymentConnectionAuthorizationRequest":{"type":"object","additionalProperties":false,"required":["provider","environment"],"properties":{"provider":{"$ref":"#/components/schemas/AMBPaymentProvider"},"environment":{"$ref":"#/components/schemas/AMBPaymentEnvironment"},"business_id":{"oneOf":[{"$ref":"#/components/schemas/AMBBusinessID"},{"type":"null"}]}}},"AMBPaymentAuthorizationID":{"type":"string","minLength":1,"pattern":"^apaut_[0-9a-hjkmnp-tv-z]{26}$","example":"apaut_01krdgeqcxet5s7t44vh8rt9mg"},"AMBPaymentConnectionAuthorization":{"type":"object","additionalProperties":false,"required":["id","provider","environment","authorization_url","expires_at"],"properties":{"id":{"$ref":"#/components/schemas/AMBPaymentAuthorizationID"},"provider":{"$ref":"#/components/schemas/AMBPaymentProvider"},"environment":{"$ref":"#/components/schemas/AMBPaymentEnvironment"},"authorization_url":{"type":"string","minLength":1,"format":"uri"},"expires_at":{"type":"string","minLength":1,"format":"date-time"}}},"AMBRoutingRuleID":{"type":"string","minLength":1,"pattern":"^arr_[0-9a-hjkmnp-tv-z]{26}$","example":"arr_01krdgeqcxet5s7t44vh8rt9mg"},"AMBRoutingRuleMatchKind":{"type":"string","minLength":1,"enum":["intent","group","both"],"description":"What a routing rule matches against the entry point that started the conversation.\n\n- `intent` matches on the entry point's intent alone: `match_intent_id` is set and `match_group_id` is null.\n- `group` matches on the entry point's group alone: `match_group_id` is set and `match_intent_id` is null.\n- `both` matches only when the entry point carries the given intent and the given group together, so `match_intent_id` and `match_group_id` are both set. There are two match fields rather than one because `both` needs to carry an intent and a group at once.\n","example":"intent"},"AMBRoutingRule":{"description":"A rule that puts a conversation into a queue when it is created or reopened, based on the group and intent the entry point that started it carried.\n","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","business_id","match_kind","queue","precedence","is_default"],"properties":{"id":{"readOnly":true,"description":"Unique identifier for the routing rule.","$ref":"#/components/schemas/AMBRoutingRuleID"},"business_id":{"description":"The Apple Messages for Business brand this rule belongs to.","$ref":"#/components/schemas/AMBBusinessID"},"match_kind":{"$ref":"#/components/schemas/AMBRoutingRuleMatchKind"},"match_intent_id":{"type":["string","null"],"minLength":1,"description":"The entry point intent this rule matches, as sent in Apple's `intentID`. Set when `match_kind` is `intent` or `both`, null when it is `group`.\n","example":"order_status"},"match_group_id":{"type":["string","null"],"minLength":1,"description":"The entry point group this rule matches, as sent in Apple's `groupID`. Set when `match_kind` is `group` or `both`, null when it is `intent`.\n","example":"support"},"queue":{"type":"string","minLength":1,"description":"The queue a matching conversation is filed into. A queue is a label your console filters by rather than a resource you create ahead of time, so any value routes.\n","example":"billing"},"precedence":{"type":"integer","format":"int32","description":"Evaluation order among this business's rules. The highest-precedence rule a conversation matches wins; rules tied on precedence are evaluated by their `id`.\n","example":10},"is_default":{"type":"boolean","description":"Whether this rule catches a conversation that matches nothing else. A business has at most one. A conversation created or reopened while none exists routes to an empty queue, which the console lists as unrouted.\n","example":false}}}]},"AMBRoutingRuleList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The workspace's routing rules, optionally filtered by business, highest precedence first and ties broken by `id`. Rules are evaluated within their business in this order. The set is returned in full; this list is not paginated.\n","items":{"$ref":"#/components/schemas/AMBRoutingRule"}}}},"AMBRoutingRuleCreateRequest":{"type":"object","additionalProperties":false,"required":["business_id","match_kind","queue"],"properties":{"business_id":{"description":"The Apple Messages for Business brand this rule belongs to.","$ref":"#/components/schemas/AMBBusinessID"},"match_kind":{"$ref":"#/components/schemas/AMBRoutingRuleMatchKind"},"match_intent_id":{"type":["string","null"],"minLength":1,"description":"The entry point intent to match, as sent in Apple's `intentID`. Required when `match_kind` is `intent` or `both`, and rejected when it is `group`.\n","example":"order_status"},"match_group_id":{"type":["string","null"],"minLength":1,"description":"The entry point group to match, as sent in Apple's `groupID`. Required when `match_kind` is `group` or `both`, and rejected when it is `intent`.\n","example":"support"},"queue":{"type":"string","minLength":1,"description":"The queue a matching conversation is filed into. Any value is accepted; your console lists whichever values your rules use.\n","example":"billing"},"precedence":{"type":"integer","format":"int32","default":0,"description":"Evaluation order among this business's rules. The highest-precedence rule a conversation matches wins. Omit it to default to 0.\n","example":10},"is_default":{"type":"boolean","default":false,"description":"Set to make this the rule that catches a conversation matching nothing else. A business can have only one; creating a second while one exists returns a `409`.\n","example":false}}},"AMBRoutingRuleUpdateRequest":{"type":"object","additionalProperties":false,"properties":{"queue":{"type":"string","minLength":1,"description":"Change the queue this rule files a matching conversation into. What the rule matches is fixed once created; to change that, delete this rule and create another.\n","example":"billing"},"precedence":{"type":"integer","format":"int32","description":"Change this rule's evaluation order among the business's other rules.","example":10},"is_default":{"type":"boolean","description":"Set to true to make this the rule that catches a conversation matching nothing else, or to false to stop it from being the default. Setting it true while the business already has a different default rule returns a `409`.\n","example":false}}},"AMBAttachmentUploadRequest":{"type":"object","additionalProperties":false,"required":["from","file"],"properties":{"from":{"type":"string","minLength":1,"description":"Apple Business ID of an approved business in this workspace."},"file":{"type":"string","minLength":1,"format":"binary","description":"File bytes, fewer than 100,000,000 bytes. The part filename and Content-Type become the attachment name and media type."}}},"AMBAttachmentUploadResult":{"x-sensitive":true,"type":"object","additionalProperties":false,"required":["url","owner","signature_base64","key","size"],"description":"Encrypted attachment reference. Include this object in a message's attachments array; keep its key private.","properties":{"name":{"type":"string","minLength":1,"description":"Original filename."},"mime_type":{"type":"string","minLength":1,"description":"Media type of the uploaded file."},"url":{"type":"string","format":"uri","pattern":"^https://","minLength":1,"description":"Location of the encrypted attachment."},"owner":{"type":"string","minLength":1,"description":"Owner identifier supplied by Apple."},"signature_base64":{"type":"string","minLength":1,"description":"Signature supplied by Apple."},"key":{"x-sensitive":true,"type":"string","minLength":66,"maxLength":66,"pattern":"^00[0-9a-fA-F]{64}$","description":"Decryption key supplied by Apple."},"size":{"type":"integer","format":"int64","minimum":1,"description":"Size of the encrypted attachment in bytes."}}},"AMBMessageID":{"type":"string","minLength":1,"pattern":"^amb_[0-9a-hjkmnp-tv-z]{26}$","example":"amb_01krdgeqcxet5s7t44vh8rt9mg"},"AMBMediaKind":{"type":"string","enum":["image","app_icon"],"description":"The interactive media to download. Images use their position in the images array; the app icon uses index zero."},"AMBConversationID":{"type":"string","minLength":1,"pattern":"^acv_[0-9a-hjkmnp-tv-z]{26}$","example":"acv_01krdgeqcxet5s7t44vh8rt9mg"},"AMBMessageDirection":{"type":"string","minLength":1,"enum":["outbound","inbound"],"description":"Whether a message was sent by the business or received from the customer:\n\n- `outbound`: A reply the business sent into the conversation.\n- `inbound`: A message the customer sent.\n"},"AMBMessageStatus":{"type":"string","minLength":1,"enum":["accepted","sent","send_failed","rejected","received"],"description":"Send status:\n\n- `accepted`: Accepted and queued for delivery to Apple.\n- `sent`: Handed to Apple. There is no delivery or read receipt on this\n  channel, so `sent` is the furthest an outbound message's status\n  advances.\n- `send_failed`: Sending stopped because of a business or conversation\n  restriction, a recipient opt-out, an Apple refusal, or exhausted attempts.\n  An earlier attempt may have reached Apple if its response or the local\n  record of success was lost. See `last_error` for why sending stopped.\n- `rejected`: Refused by Bird before any send attempt and never charged:\n  the destination has no price, the wallet could not fund the send, or the\n  content cannot be sent yet. See `last_error`.\n- `received`: Received as an inbound message.\n"},"AMBContentKind":{"type":"string","minLength":1,"enum":["text","attachment","rich_link","quick_reply","list_picker","time_picker","form","apple_pay","authenticate","imessage_app","interactive"],"description":"Derived message classification for filtering and statistics. Send requests use the native content.type families; apple_pay and authenticate remain reserved.\n\n- text: Text, optionally with a subject.\n- attachment: One or more files, images, audio clips, or videos.\n- rich_link: A link with a preview card.\n- quick_reply: Two to five reply choices.\n- list_picker: A grouped menu of choices.\n- time_picker: Appointment time slots; a reply may contain only a selected label.\n- form: A multi-page form.\n- imessage_app: A custom iMessage app interaction on a compatible device.\n- interactive: An opaque interactive reference whose subtype is unknown.\n- apple_pay: An Apple Pay request created through the conversation payment operations.\n- authenticate: Reserved for OAuth2."},"AMBMessageSource":{"type":"string","minLength":1,"enum":["operator","automation","api"],"description":"Who sent an outbound message:\n\n- `operator`: A person, through a signed-in dashboard session.\n- `automation`: A workflow or bot acting on the workspace's behalf,\n  through a signed-in session.\n- `api`: A direct API call, authenticated with an API key.\n\nA credential can send only the sources it is permitted; naming one\noutside that set is refused with a `422` `AMBMessageSourceNotPermitted`.\n\nThis is not `from`, which a send carries alongside it. That names\nthe brand the message goes out as; this names who composed it.\n"},"AMBMessageType":{"type":"string","minLength":1,"enum":["text","rich_link","interactive"],"description":"Apple message family."},"AMBNativeAttachment":{"type":"object","additionalProperties":false,"description":"Provide source_url or the complete encrypted reference, never both.","properties":{"source_url":{"type":"string","minLength":1,"description":"HTTPS URL Bird downloads and uploads to Apple.","format":"uri","pattern":"^https://","x-sensitive":true},"name":{"type":"string","description":"Display filename."},"mime_type":{"type":"string","description":"Media type of the attachment."},"url":{"type":"string","format":"uri","pattern":"^https://","minLength":1,"description":"Encrypted attachment URL returned by Apple."},"owner":{"type":"string","minLength":1,"description":"Opaque owner value returned by Apple."},"signature_base64":{"type":"string","minLength":1,"description":"Attachment authorization signature returned by Apple."},"key":{"type":"string","minLength":66,"maxLength":66,"pattern":"^00[0-9a-fA-F]{64}$","description":"Attachment decryption key returned by Apple."},"size":{"type":"integer","format":"int64","minimum":1,"description":"Attachment size in bytes.","maximum":99999999}},"oneOf":[{"required":["source_url"],"not":{"anyOf":[{"properties":{"url":{}},"required":["url"]},{"properties":{"owner":{}},"required":["owner"]},{"properties":{"key":{}},"required":["key"]},{"properties":{"signature_base64":{}},"required":["signature_base64"]},{"properties":{"size":{}},"required":["size"]}]}},{"required":["url","owner","key","signature_base64","size"],"not":{"properties":{"source_url":{}},"required":["source_url"]}}]},"AMBNativeTextContent":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"const":"text","description":"Always text.","$ref":"#/components/schemas/AMBMessageType"},"body":{"type":"string","description":"Text displayed in the message. Use one U+FFFC object replacement character per attachment to control placement.","x-sensitive":true},"subject":{"type":"string","description":"Subject displayed above the message body.","x-sensitive":true},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/AMBNativeAttachment"},"description":"Ordered attachments. Each object supplies a source URL or an encrypted Apple reference."}},"anyOf":[{"required":["body"]},{"required":["attachments"],"properties":{"attachments":{"minItems":1}}}]},"AMBNativeRichLinkImage":{"type":"object","additionalProperties":false,"required":["source_url"],"properties":{"source_url":{"type":"string","description":"HTTPS URL of a PNG preview image up to 200 kB. Bird fetches and encodes it when sending.","format":"uri","pattern":"^https://","minLength":1,"x-sensitive":true},"mime_type":{"type":"string","description":"PNG media type required by Apple. Defaults to image/png.","const":"image/png","default":"image/png"}}},"AMBNativeRichLinkVideo":{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","description":"HTTPS video URL fetched by Apple.","format":"uri","pattern":"^https://","minLength":1,"x-sensitive":true},"mime_type":{"type":"string","description":"Media type of the video. Defaults to video/mp4; supply the actual type for other formats.","default":"video/mp4"}}},"AMBNativeRichLinkAssets":{"type":"object","additionalProperties":false,"required":["image"],"properties":{"image":{"$ref":"#/components/schemas/AMBNativeRichLinkImage"},"video":{"$ref":"#/components/schemas/AMBNativeRichLinkVideo"}}},"AMBNativeRichLinkData":{"type":"object","additionalProperties":false,"required":["url","title","assets"],"properties":{"url":{"type":"string","description":"HTTPS URL opened by the preview.","format":"uri","pattern":"^https://","minLength":1,"x-sensitive":true},"title":{"type":"string","description":"Preview title.","minLength":1},"assets":{"$ref":"#/components/schemas/AMBNativeRichLinkAssets"}}},"AMBRichLinkReference":{"type":"object","additionalProperties":false,"required":["url","owner","signature_base64","size"],"description":"Reusable Apple content reference. Supply the decryption key, or the signed bid and data_ref_sig returned by Apple.","properties":{"title":{"type":"string","minLength":1,"description":"Title supplied by Apple for the preview."},"bid":{"type":"string","minLength":1,"description":"Messages extension identifier supplied by Apple, when present."},"data_ref_sig":{"type":"string","minLength":1,"description":"Signature binding the reference to the business, when supplied by Apple."},"url":{"type":"string","format":"uri","pattern":"^https://","minLength":1,"description":"Location of the encrypted preview."},"owner":{"type":"string","minLength":1,"description":"Owner identifier supplied by Apple."},"signature_base64":{"type":"string","minLength":1,"description":"Signature supplied by Apple."},"key":{"type":"string","minLength":66,"maxLength":66,"pattern":"^00[0-9a-fA-F]{64}$","description":"Decryption key supplied by Apple."},"size":{"type":"integer","format":"int64","minimum":1,"description":"Size of the encrypted preview in bytes."}},"anyOf":[{"required":["key"]},{"required":["bid","data_ref_sig"]}],"x-sensitive":true},"AMBNativeRichLinkContent":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"const":"rich_link","description":"Always rich_link.","$ref":"#/components/schemas/AMBMessageType"},"body":{"type":"string","description":"Text displayed in the message. Use one U+FFFC object replacement character per attachment to control placement.","x-sensitive":true},"subject":{"type":"string","description":"Subject displayed above the message body.","x-sensitive":true},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/AMBNativeAttachment"},"description":"Ordered attachments. Each object supplies a source URL or an encrypted Apple reference."},"rich_link_data":{"$ref":"#/components/schemas/AMBNativeRichLinkData"},"rich_link_data_ref":{"$ref":"#/components/schemas/AMBRichLinkReference"}},"oneOf":[{"required":["rich_link_data"],"not":{"properties":{"rich_link_data_ref":{}},"required":["rich_link_data_ref"]}},{"required":["rich_link_data_ref"],"not":{"properties":{"rich_link_data":{}},"required":["rich_link_data"]}}]},"AMBMessageBubbleStyle":{"type":"string","enum":["icon","small","large"],"description":"Layout of an Apple interactive message bubble."},"AMBMessageBubble":{"type":"object","additionalProperties":false,"required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":512,"description":"Text shown on the message bubble."},"subtitle":{"type":"string","maxLength":512,"description":"Secondary text shown below the title."},"style":{"$ref":"#/components/schemas/AMBMessageBubbleStyle","description":"Bubble layout. Apple defaults to `icon` when omitted and ignores it for custom iMessage apps."},"image_identifier":{"type":"string","minLength":1,"description":"Identifier of an image in interactive_data.data.images. Apple ignores it for custom iMessage apps."},"image_title":{"type":"string","maxLength":512,"description":"Title shown over an attached image in a custom iMessage app bubble."},"image_subtitle":{"type":"string","maxLength":512,"description":"Subtitle shown over an attached image in a custom iMessage app bubble."},"secondary_subtitle":{"type":"string","maxLength":512,"description":"Right-aligned title in a custom iMessage app bubble."},"tertiary_subtitle":{"type":"string","maxLength":512,"description":"Right-aligned subtitle in a custom iMessage app bubble."}}},"AMBNativeImage":{"type":"object","additionalProperties":false,"required":["identifier","source_url"],"properties":{"identifier":{"type":"string","description":"Identifier referenced by a bubble, item, or event.","minLength":1},"source_url":{"type":"string","description":"HTTPS URL of a PNG image up to 200 kB. Total interactive image data must not exceed 5 MB.","format":"uri","pattern":"^https://","minLength":1,"x-sensitive":true},"description":{"type":"string","description":"Accessibility description read by VoiceOver.","x-sensitive":true}}},"AMBQuickReplyItem":{"type":"object","additionalProperties":false,"required":["identifier","title"],"properties":{"identifier":{"type":"string","minLength":1,"description":"Opaque choice identifier returned in interactive_data.data.quick_reply.selected_identifier."},"title":{"type":"string","minLength":1,"description":"Label shown on the button."}}},"AMBNativeQuickReply":{"type":"object","additionalProperties":false,"required":["summary_text","items"],"properties":{"summary_text":{"type":"string","minLength":1,"x-sensitive":true,"description":"Text used for the device notification and shown in the transcript after the customer chooses an item. Send a separate text message to introduce the choices.\n"},"items":{"type":"array","minItems":2,"maxItems":5,"items":{"$ref":"#/components/schemas/AMBQuickReplyItem"},"description":"The buttons offered to the customer. Apple requires between two and five; outside that range the request is refused with a `422` `AMBQuickReplyItemsInvalid`. For more choices, send `list_picker` content instead.\n"}}},"AMBListPickerItem":{"type":"object","additionalProperties":false,"required":["identifier","title"],"properties":{"identifier":{"type":"string","minLength":1,"description":"Opaque item identifier returned in interactive_data.data.list_picker.sections."},"title":{"type":"string","minLength":1,"description":"Label shown on the row."},"subtitle":{"type":["string","null"],"description":"Secondary line shown under the title."},"image_identifier":{"type":["string","null"],"minLength":1,"description":"Identifier of an image in interactive_data.data.images, shown next to this row. A key with no matching entry in `images` is refused with a `422` `AMBInteractiveImageInvalid`.\n"},"order":{"type":"integer","minimum":0,"description":"Position within the section, ascending. Defaults to the row's array position."}}},"AMBListPickerSection":{"type":"object","additionalProperties":false,"required":["title","items"],"properties":{"title":{"type":"string","minLength":1,"description":"Heading shown above this section's rows."},"order":{"type":["integer","null"],"minimum":0,"description":"Where this section sits relative to its siblings, ascending. Sections omitting it are laid out in list order, after any that specify one.\n"},"items":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AMBListPickerItem"},"description":"The rows in this section."},"multiple_selection":{"type":"boolean","default":false,"description":"Whether the customer can select more than one row in this section."}}},"AMBNativeListPicker":{"type":"object","additionalProperties":false,"required":["sections"],"properties":{"sections":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AMBListPickerSection"},"description":"The menu's sections, each with its own heading and rows."}}},"AMBLocation":{"type":"object","additionalProperties":false,"dependentRequired":{"latitude":["longitude"],"longitude":["latitude"]},"properties":{"title":{"type":"string","description":"Name shown for the appointment location."},"latitude":{"type":"number","format":"double","minimum":-90,"maximum":90,"description":"Latitude in degrees. Set together with `longitude`."},"longitude":{"type":"number","format":"double","minimum":-180,"maximum":180,"description":"Longitude in degrees. Set together with `latitude`."},"radius":{"type":"number","format":"double","minimum":0,"description":"Location radius in meters. Apple ignores it without coordinates."}}},"AMBTimeSlot":{"type":"object","additionalProperties":false,"required":["identifier","start_at","duration_seconds"],"properties":{"identifier":{"type":"string","minLength":1,"description":"Opaque slot identifier. Apple may instead return only a localized label in interactive_data.reply_message.title."},"start_at":{"type":"string","format":"date-time","minLength":1,"description":"When this slot begins. Seconds and fractional seconds must be zero, for example `2026-09-02T14:30:00Z`; otherwise sending returns `422` with error code `E01001`. The timestamp is converted to UTC for Apple while preserving the instant.\n"},"duration_seconds":{"type":"integer","minimum":0,"description":"Duration in seconds. Zero indicates no duration."}}},"AMBNativeEvent":{"type":"object","additionalProperties":false,"required":["timeslots"],"properties":{"identifier":{"type":"string","minLength":1,"description":"Your identifier for the event. Defaults to the message identifier."},"location":{"$ref":"#/components/schemas/AMBLocation","description":"Optional appointment location."},"timezone_offset":{"type":"integer","description":"Minutes from GMT at the event location. Omit to use the customer's time zone."},"timeslots":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AMBTimeSlot"},"description":"Appointment times with RFC 3339 timestamps and duration in seconds."},"image_identifier":{"type":"string","minLength":1,"description":"Identifier of the event image in interactive_data.data.images."},"title":{"type":"string","description":"Event title."}}},"AMBFormSplash":{"type":"object","additionalProperties":false,"required":["button_title"],"properties":{"header":{"type":"string"},"splash_text":{"type":"string"},"button_title":{"type":"string","minLength":1},"image_identifier":{"type":"string","minLength":1}}},"AMBFormPageCommon":{"type":"object","required":["page_identifier","type","subtitle"],"properties":{"page_identifier":{"type":"string","minLength":1,"maxLength":19,"description":"Unique identifier for this page."},"type":{"type":"string","minLength":1},"title":{"type":"string"},"subtitle":{"type":"string","description":"Question shown on this page.","minLength":0},"next_page_identifier":{"type":"string","minLength":1,"maxLength":19,"description":"Next page to show. Omit to finish the form. Single-select pages route through their items instead."},"submit_form":{"type":"boolean","default":false,"description":"Marks this page as an end page for the form. A page with no next page also finishes the form."}},"if":{"required":["submit_form"],"properties":{"submit_form":{"const":true}}},"then":{"properties":{"next_page_identifier":{"not":{}}}}},"AMBFormSelectItem":{"type":"object","additionalProperties":false,"required":["identifier","title","value"],"properties":{"identifier":{"type":"string","minLength":1},"title":{"type":"string","minLength":0},"value":{"type":"string","minLength":0},"image_identifier":{"type":"string","minLength":1},"next_page_identifier":{"type":"string","minLength":1,"maxLength":19}}},"AMBFormSelectPage":{"allOf":[{"$ref":"#/components/schemas/AMBFormPageCommon"},{"type":"object","required":["type","items"],"properties":{"type":{"type":"string","minLength":1,"const":"select"},"multiple_selection":{"type":"boolean","default":false},"items":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AMBFormSelectItem"}}},"if":{"required":["multiple_selection"],"properties":{"multiple_selection":{"const":true}}},"then":{"properties":{"items":{"items":{"properties":{"next_page_identifier":{"not":{}}}}}}},"else":{"properties":{"next_page_identifier":{"not":{}}}}}],"if":{"required":["submit_form"],"properties":{"submit_form":{"const":true}}},"then":{"properties":{"items":{"items":{"properties":{"next_page_identifier":{"not":{}}}}}}},"unevaluatedProperties":false},"AMBFormPickerItem":{"type":"object","additionalProperties":false,"required":["identifier","title","value"],"properties":{"identifier":{"type":"string","minLength":1},"title":{"type":"string","minLength":0},"value":{"type":"string","minLength":0}}},"AMBFormPickerPage":{"allOf":[{"$ref":"#/components/schemas/AMBFormPageCommon"},{"type":"object","required":["type","items"],"properties":{"type":{"type":"string","minLength":1,"const":"picker"},"picker_title":{"type":"string","description":"Text beside the picker field. Omit to center the field without a label."},"selected_item_index":{"type":"integer","minimum":0,"description":"Zero-based index into `items`. Defaults to `0`. Must be less than the number of items; otherwise sending returns `422` `AMBFormPagesInvalid`.\n"},"items":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AMBFormPickerItem"}}}}],"unevaluatedProperties":false},"AMBFormDatePickerOptions":{"type":"object","additionalProperties":false,"description":"Apple defaults to UTC when interpreting these dates.","properties":{"date_format":{"type":"string","minLength":1,"description":"Format used to read the date values in these options. Defaults to `MM/dd/yyyy`."},"start_date":{"type":"string","minLength":1,"description":"Date initially shown by the picker, written in `date_format`. Defaults to the current date."},"maximum_date":{"type":"string","minLength":1,"description":"Latest date the picker shows, written in `date_format`. Defaults to the current date."},"minimum_date":{"type":"string","minLength":1,"description":"Earliest date the picker shows, written in `date_format`."},"label_text":{"type":"string","description":"Label beside the date field. Defaults to `Date`."}}},"AMBFormDatePickerPage":{"allOf":[{"$ref":"#/components/schemas/AMBFormPageCommon"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","minLength":1,"const":"date_picker"},"hint_text":{"type":"string"},"options":{"$ref":"#/components/schemas/AMBFormDatePickerOptions"}}}],"unevaluatedProperties":false},"AMBFormInputType":{"type":"string","minLength":1,"enum":["singleline","multiline"]},"AMBFormKeyboardType":{"type":"string","minLength":1,"x-extensible-enum":["default","asciiCapable","numbersAndPunctuation","URL","numberPad","phonePad","namePhonePad","emailAddress","decimalPad","webSearch"],"description":"Apple UIKit value, passed through without changing its spelling."},"AMBFormTextContentType":{"type":"string","minLength":1,"x-extensible-enum":["name","namePrefix","givenName","middleName","familyName","nameSuffix","nickname","jobTitle","organizationName","location","fullStreetAddress","streetAddressLine1","streetAddressLine2","addressCity","addressState","addressCityAndState","sublocality","countryName","postalCode","telephoneNumber","emailAddress","URL","creditCardNumber","username","password","newPassword","oneTimeCode"],"description":"Apple UIKit value, passed through without changing its spelling."},"AMBFormInputOptions":{"type":"object","additionalProperties":false,"properties":{"regex":{"type":"string","minLength":1,"description":"Pattern Apple uses to validate the input. Use JSON string escaping for backslashes."},"placeholder":{"type":"string","description":"Shown when the field is empty. Defaults to `Required` when `required` is true, otherwise `Optional`."},"required":{"type":"boolean","default":false,"description":"Disables the next-page button until the customer enters a value."},"input_type":{"$ref":"#/components/schemas/AMBFormInputType","description":"Defaults to `singleline`."},"label_text":{"type":"string","description":"Label for `singleline` input only. Omit for no label."},"prefix_text":{"type":"string","description":"Text beside `singleline` input only, such as a currency symbol. Omit for no prefix."},"maximum_character_count":{"type":"integer","minimum":1,"description":"Defaults to 30 for `singleline` input and 300 for `multiline` input."},"keyboard_type":{"$ref":"#/components/schemas/AMBFormKeyboardType","description":"Keyboard to display. Defaults to `default`."},"text_content_type":{"$ref":"#/components/schemas/AMBFormTextContentType","description":"Content hint used for autofill."}}},"AMBFormInputPage":{"allOf":[{"$ref":"#/components/schemas/AMBFormPageCommon"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","minLength":1,"const":"input"},"hint_text":{"type":"string"},"options":{"$ref":"#/components/schemas/AMBFormInputOptions"}}}],"unevaluatedProperties":false},"AMBFormPage":{"oneOf":[{"$ref":"#/components/schemas/AMBFormSelectPage"},{"$ref":"#/components/schemas/AMBFormPickerPage"},{"$ref":"#/components/schemas/AMBFormDatePickerPage"},{"$ref":"#/components/schemas/AMBFormInputPage"}],"discriminator":{"propertyName":"type","mapping":{"select":"#/components/schemas/AMBFormSelectPage","picker":"#/components/schemas/AMBFormPickerPage","date_picker":"#/components/schemas/AMBFormDatePickerPage","input":"#/components/schemas/AMBFormInputPage"}}},"AMBNativeFormData":{"type":"object","additionalProperties":false,"required":["start_page_identifier","pages"],"properties":{"start_page_identifier":{"type":"string","minLength":1,"maxLength":19,"description":"Identifier of the first page to show."},"private":{"type":"boolean","default":false,"description":"Whether Apple marks the submitted response as private."},"show_summary":{"type":"boolean","default":false,"description":"Whether Apple shows a summary before the customer submits."},"splash":{"$ref":"#/components/schemas/AMBFormSplash"},"pages":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AMBFormPage"},"description":"Form pages referenced by the start page and navigation identifiers."}}},"AMBNativeDynamic":{"type":"object","additionalProperties":false,"required":["data"],"description":"Form content. Bird supplies Apple’s messageForms template and protocol version.","properties":{"data":{"$ref":"#/components/schemas/AMBNativeFormData"}}},"AMBAuthenticationID":{"type":"string","minLength":1,"pattern":"^amauth_[0-9a-hjkmnp-tv-z]{26}$","example":"amauth_01krdgeqcxet5s7t44vh8rt9mg"},"AMBNativeAuthentication":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Authentication attempt created through the conversation authentication endpoint. Contains no authorization parameters or credentials.","required":["authentication_id"],"properties":{"authentication_id":{"readOnly":true,"$ref":"#/components/schemas/AMBAuthenticationID"}}},"AMBPaymentID":{"type":"string","minLength":1,"pattern":"^apay_[0-9a-hjkmnp-tv-z]{26}$","example":"apay_01krdgeqcxet5s7t44vh8rt9mg"},"AMBNativePayment":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Apple Pay request created through the conversation payment endpoint. Contains no payment token or provider credentials.","required":["payment_id"],"properties":{"payment_id":{"readOnly":true,"$ref":"#/components/schemas/AMBPaymentID"}}},"AMBNativeInteractivePayload":{"type":"object","additionalProperties":false,"description":"Exactly one built-in interaction. Protocol versions are managed by Bird.","properties":{"request_identifier":{"type":"string","description":"Correlation identifier for this interaction. Bird generates one when omitted."},"images":{"type":"array","items":{"$ref":"#/components/schemas/AMBNativeImage"},"description":"Images referenced by identifier."},"quick_reply":{"$ref":"#/components/schemas/AMBNativeQuickReply"},"list_picker":{"$ref":"#/components/schemas/AMBNativeListPicker"},"event":{"$ref":"#/components/schemas/AMBNativeEvent"},"dynamic":{"$ref":"#/components/schemas/AMBNativeDynamic"},"authenticate":{"$ref":"#/components/schemas/AMBNativeAuthentication","readOnly":true},"payment":{"$ref":"#/components/schemas/AMBNativePayment","readOnly":true}},"oneOf":[{"required":["quick_reply"],"not":{"anyOf":[{"properties":{"list_picker":{}},"required":["list_picker"]},{"properties":{"event":{}},"required":["event"]},{"properties":{"dynamic":{}},"required":["dynamic"]},{"properties":{"authenticate":{}},"required":["authenticate"]},{"properties":{"payment":{}},"required":["payment"]}]}},{"required":["list_picker"],"not":{"anyOf":[{"properties":{"quick_reply":{}},"required":["quick_reply"]},{"properties":{"event":{}},"required":["event"]},{"properties":{"dynamic":{}},"required":["dynamic"]},{"properties":{"authenticate":{}},"required":["authenticate"]},{"properties":{"payment":{}},"required":["payment"]}]}},{"required":["event"],"not":{"anyOf":[{"properties":{"quick_reply":{}},"required":["quick_reply"]},{"properties":{"list_picker":{}},"required":["list_picker"]},{"properties":{"dynamic":{}},"required":["dynamic"]},{"properties":{"authenticate":{}},"required":["authenticate"]},{"properties":{"payment":{}},"required":["payment"]}]}},{"required":["dynamic"],"not":{"anyOf":[{"properties":{"quick_reply":{}},"required":["quick_reply"]},{"properties":{"list_picker":{}},"required":["list_picker"]},{"properties":{"event":{}},"required":["event"]},{"properties":{"authenticate":{}},"required":["authenticate"]},{"properties":{"payment":{}},"required":["payment"]}]}},{"required":["authenticate"],"not":{"anyOf":[{"properties":{"quick_reply":{}},"required":["quick_reply"]},{"properties":{"list_picker":{}},"required":["list_picker"]},{"properties":{"event":{}},"required":["event"]},{"properties":{"dynamic":{}},"required":["dynamic"]},{"properties":{"payment":{}},"required":["payment"]}]}},{"required":["payment"],"not":{"anyOf":[{"properties":{"quick_reply":{}},"required":["quick_reply"]},{"properties":{"list_picker":{}},"required":["list_picker"]},{"properties":{"event":{}},"required":["event"]},{"properties":{"dynamic":{}},"required":["dynamic"]},{"properties":{"authenticate":{}},"required":["authenticate"]}]}}]},"AMBNativeInteractiveData":{"type":"object","additionalProperties":false,"properties":{"app_id":{"type":"string","minLength":1,"description":"App Store identifier of the iMessage app."},"app_name":{"type":"string","minLength":1,"description":"Name of the iMessage app."},"bid":{"type":"string","minLength":1,"description":"Identifier of the iMessage extension, in Apple's `com.apple.messages.MSMessageExtensionBalloonPlugin:team-id:extension-id` format.\n"},"url":{"x-sensitive":true,"type":"string","minLength":1,"description":"Opaque URL string that Messages passes to the iMessage app.","example":"?order=1234&view=detail"},"use_live_layout":{"type":"boolean","description":"Whether Messages renders the received and reply bubbles using Live Layout."},"session_identifier":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$","description":"Session UUID to preserve across interactions. Apple creates one when omitted."},"received_message":{"$ref":"#/components/schemas/AMBMessageBubble","description":"Content Messages shows in the received message bubble."},"reply_message":{"$ref":"#/components/schemas/AMBMessageBubble","description":"Content Messages shows in the reply message bubble."},"app_icon_source_url":{"type":"string","minLength":1,"format":"uri","pattern":"^https://","description":"Publicly accessible HTTPS URL of the app's PNG icon. The icon must be smaller than 15 kB. We fetch and include it in the request to Apple.\n","x-sensitive":true},"data":{"$ref":"#/components/schemas/AMBNativeInteractivePayload"}},"description":"A built-in interaction or custom iMessage app. Custom apps require the app metadata and both message bubbles.","oneOf":[{"required":["data"],"not":{"anyOf":[{"properties":{"app_id":{}},"required":["app_id"]},{"properties":{"app_name":{}},"required":["app_name"]},{"properties":{"bid":{}},"required":["bid"]},{"properties":{"url":{}},"required":["url"]},{"properties":{"app_icon_source_url":{}},"required":["app_icon_source_url"]},{"properties":{"use_live_layout":{}},"required":["use_live_layout"]}]}},{"required":["app_id","app_name","bid","url","app_icon_source_url","use_live_layout","received_message","reply_message"],"not":{"properties":{"data":{}},"required":["data"]}}]},"AMBNativeInteractiveContent":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"const":"interactive","description":"Always interactive.","$ref":"#/components/schemas/AMBMessageType"},"body":{"type":"string","description":"Text displayed in the message. Use one U+FFFC object replacement character per attachment to control placement.","x-sensitive":true},"subject":{"type":"string","description":"Subject displayed above the message body.","x-sensitive":true},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/AMBNativeAttachment"},"description":"Ordered attachments. Each object supplies a source URL or an encrypted Apple reference."},"interactive_data":{"$ref":"#/components/schemas/AMBNativeInteractiveData"},"interactive_data_ref":{"$ref":"#/components/schemas/AMBRichLinkReference"}},"oneOf":[{"required":["interactive_data"],"not":{"properties":{"interactive_data_ref":{}},"required":["interactive_data_ref"]}},{"required":["interactive_data_ref"],"not":{"properties":{"interactive_data":{}},"required":["interactive_data"]}}]},"AMBMessageContent":{"description":"Apple message families with Bird field naming and media URLs. Authentication and Apple Pay requests are created through their dedicated conversation endpoints.","oneOf":[{"$ref":"#/components/schemas/AMBNativeTextContent"},{"$ref":"#/components/schemas/AMBNativeRichLinkContent"},{"$ref":"#/components/schemas/AMBNativeInteractiveContent"}],"discriminator":{"propertyName":"type","mapping":{"text":"#/components/schemas/AMBNativeTextContent","rich_link":"#/components/schemas/AMBNativeRichLinkContent","interactive":"#/components/schemas/AMBNativeInteractiveContent"}}},"AMBNativeInboundQuickReply":{"type":"object","additionalProperties":false,"required":["selected_identifier"],"properties":{"selected_identifier":{"type":"string","description":"Identifier selected by the customer.","minLength":1},"selected_index":{"type":"integer","minimum":0,"description":"Index reported by Apple."},"items":{"type":"array","items":{"$ref":"#/components/schemas/AMBQuickReplyItem"},"description":"Items returned by the customer device."}}},"AMBNativeInboundListPickerSection":{"type":"object","additionalProperties":false,"required":["items"],"properties":{"title":{"type":"string","minLength":1,"description":"Heading shown above this section's rows."},"order":{"type":["integer","null"],"minimum":0,"description":"Where this section sits relative to its siblings, ascending. Sections omitting it are laid out in list order, after any that specify one.\n"},"items":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AMBListPickerItem"},"description":"The rows in this section."},"multiple_selection":{"type":"boolean","default":false,"description":"Whether the customer can select more than one row in this section."}}},"AMBNativeInboundListPicker":{"type":"object","additionalProperties":false,"required":["sections"],"properties":{"sections":{"type":"array","items":{"$ref":"#/components/schemas/AMBNativeInboundListPickerSection"},"description":"Sections and selected rows returned by Apple."}}},"AMBNativeInboundEvent":{"type":"object","additionalProperties":false,"properties":{"identifier":{"type":"string","minLength":1,"description":"Event identifier returned by Apple, when provided."},"location":{"$ref":"#/components/schemas/AMBLocation","description":"Optional appointment location."},"timezone_offset":{"type":"integer","description":"Minutes from GMT at the event location. Omit to use the customer's time zone."},"timeslots":{"type":"array","items":{"$ref":"#/components/schemas/AMBTimeSlot"},"description":"Time slots returned by Apple. May be empty when the device supplies only a selected label."},"image_identifier":{"type":"string","minLength":1,"description":"Identifier of the event image in interactive_data.data.images."},"title":{"type":"string","description":"Event title."}}},"AMBFormPageType":{"type":"string","minLength":1,"enum":["select","picker","date_picker","input"]},"AMBInboundFormItem":{"type":"object","additionalProperties":false,"required":["identifier","type","title","value"],"properties":{"identifier":{"type":"string","minLength":1},"type":{"$ref":"#/components/schemas/AMBFormPageType"},"title":{"type":"string","minLength":0,"x-sensitive":true,"description":"Display value Apple returned, including any input prefix. May be empty for an optional input."},"value":{"type":"string","minLength":0,"x-sensitive":true,"description":"Machine value Apple returned for the selection or input. May be empty for an optional input."}}},"AMBInboundFormSelection":{"type":"object","additionalProperties":false,"required":["page_identifier","title","subtitle","items"],"properties":{"page_identifier":{"type":"string","minLength":1},"title":{"type":"string","minLength":0,"description":"Page title returned by Apple. Empty when the page has no title."},"subtitle":{"type":"string","minLength":1},"items":{"type":"array","items":{"$ref":"#/components/schemas/AMBInboundFormItem"}}}},"AMBNativeInboundFormData":{"type":"object","additionalProperties":false,"required":["selections"],"properties":{"private":{"type":"boolean","default":false,"description":"Whether the sender asked the MSP to treat this form response as private."},"selections":{"type":"array","items":{"$ref":"#/components/schemas/AMBInboundFormSelection"}}}},"AMBNativeInboundDynamic":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AMBNativeInboundFormData"}}},"AMBNativeInboundPayment":{"type":"object","additionalProperties":false,"x-sensitive":true,"properties":{"state":{"type":"string","description":"Payment state reported by Apple. This does not confirm that a payment settled."}}},"AMBNativeInboundAuthentication":{"type":"object","additionalProperties":false,"x-sensitive":true,"properties":{"status":{"type":"string","description":"Authentication status reported by Apple. This does not establish an authenticated Bird session."}}},"AMBNativeInboundImage":{"type":"object","additionalProperties":false,"required":["identifier","download_url"],"properties":{"identifier":{"type":"string","description":"Identifier used by the received message.","minLength":1},"description":{"type":"string","description":"Accessibility description supplied by Apple.","x-sensitive":true},"download_url":{"type":"string","description":"Relative Bird API URL for downloading the image with the same authentication and workspace as the message.","format":"uri-reference","minLength":1}}},"AMBNativeInboundPayload":{"type":"object","additionalProperties":false,"properties":{"request_identifier":{"type":"string","description":"Correlation identifier reported by Apple."},"quick_reply":{"$ref":"#/components/schemas/AMBNativeInboundQuickReply"},"list_picker":{"$ref":"#/components/schemas/AMBNativeInboundListPicker"},"event":{"$ref":"#/components/schemas/AMBNativeInboundEvent"},"dynamic":{"$ref":"#/components/schemas/AMBNativeInboundDynamic"},"payment":{"$ref":"#/components/schemas/AMBNativeInboundPayment"},"authenticate":{"$ref":"#/components/schemas/AMBNativeInboundAuthentication"},"images":{"type":"array","items":{"$ref":"#/components/schemas/AMBNativeInboundImage"},"description":"Images returned by Apple, with authenticated download URLs."}}},"AMBNativeInboundInteractiveData":{"type":"object","additionalProperties":false,"properties":{"app_id":{"type":"string","description":"Custom app identifier."},"app_name":{"type":"string","description":"Custom app name."},"bid":{"type":"string","description":"Apple extension identifier."},"url":{"type":"string","description":"Opaque custom app response URL.","x-sensitive":true},"use_live_layout":{"type":"boolean","description":"Whether the app uses live layout."},"session_identifier":{"type":"string","description":"Apple interaction session identifier."},"reply_message":{"$ref":"#/components/schemas/AMBMessageBubble"},"received_message":{"$ref":"#/components/schemas/AMBMessageBubble"},"data":{"$ref":"#/components/schemas/AMBNativeInboundPayload"},"app_icon_url":{"type":"string","description":"Relative Bird API URL for downloading the custom app icon with the same authentication and workspace as the message.","format":"uri-reference"}}},"AMBNativeInboundInteractiveContent":{"type":"object","additionalProperties":false,"required":["type","interactive_data"],"properties":{"type":{"const":"interactive","description":"Always interactive.","$ref":"#/components/schemas/AMBMessageType"},"body":{"type":"string","description":"Message body supplied by Apple.","x-sensitive":true},"subject":{"type":"string","description":"Message subject supplied by Apple.","x-sensitive":true},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/AMBNativeAttachment"},"description":"Ordered attachments supplied by Apple."},"interactive_data":{"$ref":"#/components/schemas/AMBNativeInboundInteractiveData"}}},"AMBInboundContent":{"description":"Received text, attachments, and interactive replies, preserving Apple message structure.","oneOf":[{"$ref":"#/components/schemas/AMBNativeTextContent"},{"$ref":"#/components/schemas/AMBNativeInboundInteractiveContent"}],"discriminator":{"propertyName":"type","mapping":{"text":"#/components/schemas/AMBNativeTextContent","interactive":"#/components/schemas/AMBNativeInboundInteractiveContent"}}},"AMBStatsErrorCode":{"type":"string","minLength":1,"pattern":"^(bird:[a-z0-9_]+|apple:\\d{3})$","description":"Machine-readable reason a send failed, in one of two namespaces: `bird:` for a reason Bird's own pipeline assigned (for example `bird:business_not_registered`), or `apple:` followed by the HTTP status Apple's API returned for the send attempt (for example `apple:404`). This is an open, growing set in both namespaces; accept unrecognized values.\n","example":"bird:business_not_registered"},"AMBError":{"type":["object","null"],"additionalProperties":false,"readOnly":true,"required":["code","description","occurred_at"],"description":"Failure detail for a message or invitation that could not be sent or was rejected.","properties":{"code":{"$ref":"#/components/schemas/AMBStatsErrorCode"},"description":{"type":"string","minLength":1,"description":"The failure in words. Free-form, so branch on `code` and show this to a human.","example":"Apple refused the message with HTTP status 404."},"occurred_at":{"type":"string","format":"date-time","minLength":1,"description":"When the failure occurred."}}},"AMBMessage":{"type":"object","additionalProperties":false,"required":["id","conversation_id","business_id","direction","status","kind","content","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AMBMessageID","description":"ID of the message, assigned when it is accepted or received. Pass it as `message_id` to the get-message and list-events endpoints.\n"},"conversation_id":{"readOnly":true,"$ref":"#/components/schemas/AMBConversationID","description":"The conversation this message belongs to."},"business_id":{"readOnly":true,"$ref":"#/components/schemas/AMBBusinessID","description":"The business the message was sent from or received by."},"direction":{"readOnly":true,"$ref":"#/components/schemas/AMBMessageDirection"},"status":{"readOnly":true,"$ref":"#/components/schemas/AMBMessageStatus"},"kind":{"readOnly":true,"$ref":"#/components/schemas/AMBContentKind","description":"Derived content classification for filtering and statistics."},"source":{"readOnly":true,"$ref":"#/components/schemas/AMBMessageSource","description":"Who sent this message. Absent on an inbound message, which has no source to report."},"content":{"readOnly":true,"description":"Native message content. Outgoing interactions contain requests; incoming interactions contain replies.","anyOf":[{"$ref":"#/components/schemas/AMBMessageContent"},{"$ref":"#/components/schemas/AMBInboundContent"}],"x-sensitive":true},"in_reply_to_message_id":{"readOnly":true,"$ref":"#/components/schemas/AMBMessageID","description":"Original message matched through Apple’s request identifier within the same workspace, business, and conversation. Omitted when no verified match exists."},"locale":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"Locale for this message, preserved in Apple’s format, for example en_US. Outbound messages use the request override, then the conversation locale, then the business default. Inbound messages preserve the locale in Apple’s callback. Null when unknown.","example":"en_US"},"category":{"type":"string","readOnly":true,"description":"The category this message was sent with, for grouping in statistics. Defaults to an empty string when a send names no category. Absent on an inbound message, which has no category to report.\n","example":"order_update"},"tags":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/Tag"},"description":"Structured `{name, value}` filter labels applied to this message. Absent on an inbound message."},"last_error":{"readOnly":true,"description":"Failure detail on a message whose send failed or that Bird rejected before any send attempt. Null otherwise.","$ref":"#/components/schemas/AMBError"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"The moment this message was accepted (outbound) or received (inbound). This is the timestamp the outbound statistics families bucket and attribute on; there is no separate `accepted_at` field.\n"},"sent_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the selected sending outcome occurred. Null unless the current status is `sent` and the message is outbound. For older messages without a retained sending event, the stored record time is used."},"data_ref":{"$ref":"#/components/schemas/AMBRichLinkReference","readOnly":true,"description":"Reusable encrypted content reference returned by Apple after a successful send. Absent until Apple returns one."},"group":{"type":"string","readOnly":true,"minLength":1,"description":"Apple department identifier carried by this message. Omitted when absent from the message or unavailable on a historical record."},"intent":{"type":"string","readOnly":true,"minLength":1,"description":"Apple purpose identifier carried by this message. Omitted when absent from the message or unavailable on a historical record."}}},"AMBMessageList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of Apple Messages for Business messages, newest first.","items":{"$ref":"#/components/schemas/AMBMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AMBMessageSendRequest":{"type":"object","additionalProperties":false,"required":["from","to","content"],"properties":{"from":{"type":"string","minLength":1,"description":"Apple business identifier of the brand sending the message. Read it from the business’s apple_business_id. The customer must have opened the conversation with this business.","example":"260817-Acme-Retail-EU"},"to":{"type":"string","minLength":1,"description":"Apple’s opaque customer identifier for this business, available as the conversation’s opaque_user_id. The conversation must exist and be open."},"source":{"$ref":"#/components/schemas/AMBMessageSource","description":"Who this message is sent as. A signed-in session may send as `operator` or `automation`; an API key may send as `api` or `automation`. Omit it to take the default for your credential. Naming a source your credential cannot use is refused with a `422` `AMBMessageSourceNotPermitted`.\n"},"content":{"$ref":"#/components/schemas/AMBMessageContent","description":"Message content in Apple’s native text, rich_link, or interactive family.","x-sensitive":true},"category":{"type":"string","maxLength":64,"description":"Free-form category label for grouping in statistics, for example `order_update`. Omit it to send with the default empty category.\n","example":"order_update"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"maxItems":20,"description":"Structured `{name, value}` labels for filtering. Maximum 20 tags per send.\n"},"group":{"type":"string","description":"Department identifier for this message."},"intent":{"type":"string","description":"Purpose of this conversation."},"locale":{"type":"string","description":"Apple locale identifier, for example en_US. Defaults to the conversation locale."}}},"AMBConstructPayloadRequest":{"type":"object","additionalProperties":false,"required":["from","url"],"properties":{"from":{"type":"string","minLength":1,"description":"The Business ID Apple issued for the approved brand that will send the rich link. The business must be active in this workspace. An unknown value returns `404` `AMBBusinessNotFound`.\n"},"url":{"type":"string","minLength":1,"format":"uri","pattern":"^https://","description":"The App Clip, Apple Music, or Apple Maps URL to build a preview for. An `http://` URL is refused with a `422` `AMBEndpointNotHTTPS`. A URL Apple cannot build a preview for is refused with a `422` `AMBConstructPayloadFailed`; send `rich_link` content with your own `title` and `image` instead.\n","example":"https://apps.apple.com/app/id123456789?clip=1","x-sensitive":true},"store_region":{"$ref":"#/components/schemas/CountryCode","description":"App Store storefront used to build an App Clip preview. Defaults to `US` when omitted.\n"}}},"AMBConstructedPayload":{"type":"object","additionalProperties":false,"required":["url","ref"],"properties":{"url":{"type":"string","readOnly":true,"minLength":1,"format":"uri","description":"The URL the payload was built for, echoed back.","x-sensitive":true},"ref":{"x-sensitive":true,"$ref":"#/components/schemas/AMBRichLinkReference","readOnly":true,"description":"Apple’s signed preview reference. Pass it as content.rich_link_data_ref when sending a rich_link message."}}},"AMBMessageEventType":{"type":"string","minLength":1,"description":"Message timeline event type:\n\n- `amb.accepted`: The API accepted the request.\n- `amb.sent`: The message was handed to Apple.\n- `amb.send_failed`: Apple refused the message, or its send attempts were exhausted.\n- `amb.rejected`: Bird refused the message before any send attempt.\n- `amb.received`: An inbound message arrived from the customer.\n\nThis is an open enum. Accept unrecognized values.\n","x-extensible-enum":["amb.accepted","amb.sent","amb.send_failed","amb.rejected","amb.received"],"example":"amb.sent"},"AMBEventID":{"type":"string","minLength":1,"pattern":"^aev_[0-9a-hjkmnp-tv-z]{26}$","example":"aev_01krdgeqcxet5s7t44vh8rt9mg"},"AMBMessageEvent":{"type":"object","additionalProperties":false,"required":["id","type","occurred_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AMBEventID","description":"ID of the event, unique within the message's timeline."},"type":{"readOnly":true,"$ref":"#/components/schemas/AMBMessageEventType"},"occurred_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this event occurred."},"error":{"readOnly":true,"$ref":"#/components/schemas/AMBError","description":"Failure detail, on an `amb.send_failed` or `amb.rejected` event. Null on every other event type."}}},"AMBMessageEventList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The message's events, oldest first. Not paginated: a message's timeline is bounded and returned in full.\n","items":{"$ref":"#/components/schemas/AMBMessageEvent"}}}},"AMBInvitationStatus":{"type":"string","minLength":1,"enum":["queued","sent","rejected","send_failed"],"description":"Send status:\n\n- `queued`: We accepted the invitation and queued it for sending.\n- `sent`: Apple's gateway accepted the invitation. This does not indicate a\n  customer response or delivery to a device.\n- `rejected`: We refused the invitation before submitting it to Apple because\n  billing could not accept the charge, including missing prices or insufficient\n  funds. The invitation was not charged.\n- `send_failed`: Sending stopped after queueing because Apple refused the\n  invitation, eligibility changed or retry attempts were exhausted. The charge\n  may stand. An earlier attempt may have reached Apple.\n  See `error` for details.\n"},"AMBInvitationID":{"type":"string","minLength":1,"pattern":"^ain_[0-9a-hjkmnp-tv-z]{26}$","example":"ain_01krdgeqcxet5s7t44vh8rt9mg"},"AMBInvitationNotification":{"type":"object","additionalProperties":false,"required":["template_id","reference_id","parameters"],"description":"The Apple-managed template and values used to render an invitation.","properties":{"template_id":{"type":"string","minLength":1,"description":"Apple template identifier approved for this business.","example":"binaryChoice.engage.withImage"},"reference_id":{"type":"string","minLength":1,"maxLength":1000,"pattern":"^[^\"']+$","description":"Your reference for the invitation, such as an order number or case ID. Quotation marks and apostrophes are not accepted.","example":"case-1009273616"},"locale":{"type":"string","description":"Locale Apple uses to render the template. Omit it to use the template's default language.","example":"en-us"},"parameters":{"type":"object","additionalProperties":true,"description":"Values for the selected Apple template. Supply the parameter names and JSON value shapes required by that template.","example":{"brandName":"Acme Retail","brandLogo":"decoded-png-image"}}}},"AMBInvitation":{"description":"An invitation sent through Apple Messages for Business.","readOnly":true,"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","business_id","from","to","notification","request_identifier","status","error"],"properties":{"id":{"$ref":"#/components/schemas/AMBInvitationID","description":"Invitation identifier assigned when we accept the request."},"business_id":{"$ref":"#/components/schemas/AMBBusinessID","description":"The Apple Messages for Business business that sent the invitation."},"from":{"type":"string","minLength":1,"description":"Apple business identifier used to send the invitation.","example":"260817-Acme-Retail-EU"},"to":{"type":"string","minLength":5,"maxLength":16,"pattern":"^\\+[1-9][0-9]{3,14}$","description":"Customer phone number in canonical E.164 format.","example":"+14155550100"},"notification":{"$ref":"#/components/schemas/AMBInvitationNotification","x-sensitive":true},"request_identifier":{"type":"string","minLength":1,"description":"Correlation identifier supplied in the request or generated by us."},"group":{"type":"string","description":"Department identifier for this invitation, when supplied."},"intent":{"type":"string","description":"Purpose of the conversation, when supplied."},"status":{"$ref":"#/components/schemas/AMBInvitationStatus"},"error":{"$ref":"#/components/schemas/AMBError","description":"Failure details for an invitation we rejected or could not send; null while queued or sent."},"cost":{"$ref":"#/components/schemas/Money","description":"The amount charged for the invitation, in its source currency."},"sent_at":{"type":"string","format":"date-time","description":"When Apple's gateway accepted the invitation."}}}]},"AMBInvitationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Invitations in the workspace, newest first.","items":{"$ref":"#/components/schemas/AMBInvitation"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AMBCreateInvitationRequest":{"type":"object","additionalProperties":false,"required":["from","to","notification"],"description":"An Apple Messages for Business invitation to send to a customer.","properties":{"from":{"type":"string","minLength":1,"description":"Apple business identifier of the brand sending the invitation. Read it from the business's `apple_business_id`.","example":"260817-Acme-Retail-EU"},"to":{"type":"string","minLength":5,"maxLength":16,"pattern":"^\\+[1-9][0-9]{3,14}$","description":"Customer phone number in E.164 format, including the leading plus sign and country code. The customer must have opted in to invitations from this business before you call this endpoint.","example":"+14155550100"},"notification":{"$ref":"#/components/schemas/AMBInvitationNotification","x-sensitive":true},"request_identifier":{"type":"string","description":"Correlation identifier for this invitation. We generate one when omitted."},"group":{"type":"string","description":"Department identifier for this invitation."},"intent":{"type":"string","description":"Purpose of the conversation this invitation starts or continues."}}},"AMBConversationStatus":{"type":"string","minLength":1,"enum":["open","closed"],"description":"Whether a conversation is open or closed. There is no close operation on this API: only the customer closes a conversation from their device, and any inbound message on a closed conversation reopens it.\n"},"AMBConversationOrigin":{"type":"string","minLength":1,"enum":["entry_point","invitation"],"description":"How the conversation started. `entry_point` means the customer opened it from one of your configured Apple Messages for Business entry points. `invitation` means you sent them a link to start it, which this surface does not yet create. This is set once when the conversation is created and never changes.\n"},"AMBConversationClosedReason":{"type":"string","enum":["user_close","gone"],"description":"Why a closed conversation was closed. `user_close` means the customer sent a close message from their device. `gone` means Apple returned a 410 for the conversation. The console renders this as a banner on a closed conversation.\n"},"AMBAuthenticationStatus":{"type":"string","description":"Progress of a conversation identity verification attempt. `pending` waits for the customer or identity provider after the authentication message is accepted. `processing` means Bird claimed the callback and is exchanging its authorization code. `succeeded` means Bird verified and stored the identity. `failed` means message delivery, provider exchange or identity validation failed. `cancelled` means the customer or provider declined authorization. `expired` means the attempt exceeded its ten-minute lifetime. `superseded` means a newer request or a change to the conversation, business, provider or access made the attempt inapplicable. All values except `pending` and `processing` are terminal.\n","enum":["pending","processing","succeeded","failed","cancelled","expired","superseded"],"minLength":1},"AMBVerifiedIdentity":{"type":"object","additionalProperties":false,"description":"Validated identity claims. Verification does not create a persistent login session or grant access to customer systems.","required":["issuer","subject","verified_at"],"properties":{"issuer":{"type":"string","minLength":1,"description":"Issuer that verified the identity.","format":"uri","readOnly":true},"subject":{"type":"string","minLength":1,"description":"Provider-issued user identifier. Not necessarily your CRM customer ID.","maxLength":255,"readOnly":true},"name":{"type":"string","minLength":1,"description":"Name supplied by the provider when name collection is enabled.","maxLength":512,"readOnly":true},"email":{"type":"string","minLength":1,"description":"Email supplied by the provider when email collection is enabled.","maxLength":320,"readOnly":true},"email_verified":{"type":"boolean","description":"Whether the provider verified this email address.","readOnly":true},"authenticated_at":{"type":"string","minLength":1,"description":"When the provider says the user authenticated. May precede this verification.","format":"date-time","readOnly":true},"verified_at":{"type":"string","minLength":1,"description":"When Bird validated the identity response.","format":"date-time","readOnly":true}}},"AMBAuthentication":{"type":"object","additionalProperties":false,"description":"A single attempt to verify the identity associated with an Apple Messages for Business conversation.","required":["id","conversation_id","provider_id","provider_revision","message_id","status","created_at","expires_at"],"properties":{"id":{"$ref":"#/components/schemas/AMBAuthenticationID","readOnly":true},"conversation_id":{"$ref":"#/components/schemas/AMBConversationID","readOnly":true},"provider_id":{"$ref":"#/components/schemas/AMBAuthenticationProviderID","readOnly":true},"provider_revision":{"type":"integer","format":"int64","minimum":1,"description":"Provider revision used by this attempt.","readOnly":true},"message_id":{"oneOf":[{"$ref":"#/components/schemas/AMBMessageID"},{"type":"null"}],"description":"Authentication message reference, or null before acceptance.","readOnly":true},"status":{"$ref":"#/components/schemas/AMBAuthenticationStatus","readOnly":true},"created_at":{"type":"string","minLength":1,"description":"When the attempt was created.","format":"date-time","readOnly":true},"expires_at":{"type":"string","minLength":1,"description":"When this attempt expires.","format":"date-time","readOnly":true},"completed_at":{"type":"string","minLength":1,"description":"When the attempt reached a terminal outcome.","format":"date-time","readOnly":true},"failure_code":{"type":"string","minLength":1,"description":"Safe failure identifier. Contains no provider response or credentials.","maxLength":128,"readOnly":true},"identity":{"$ref":"#/components/schemas/AMBVerifiedIdentity","readOnly":true}}},"AMBConversationVerifiedIdentity":{"type":"object","additionalProperties":false,"description":"Most recent successful identity verification for this conversation.","required":["authentication_id","provider_id","provider_revision","identity","historical"],"properties":{"authentication_id":{"$ref":"#/components/schemas/AMBAuthenticationID","readOnly":true},"provider_id":{"$ref":"#/components/schemas/AMBAuthenticationProviderID","readOnly":true},"provider_revision":{"type":"integer","format":"int64","minimum":1,"description":"Provider revision used for verification.","readOnly":true},"identity":{"$ref":"#/components/schemas/AMBVerifiedIdentity","readOnly":true},"historical":{"type":"boolean","description":"The conversation or provider changed after verification. This result is retained as history.","readOnly":true}}},"AMBConversationAuthentication":{"type":"object","additionalProperties":false,"description":"Latest attempt and last successful verification. A new pending attempt does not erase an earlier success.","properties":{"latest_attempt":{"$ref":"#/components/schemas/AMBAuthentication","readOnly":true},"last_verified":{"$ref":"#/components/schemas/AMBConversationVerifiedIdentity","readOnly":true}}},"AMBConversation":{"type":"object","additionalProperties":false,"description":"A conversation between your business and one customer on Apple Messages for Business. It holds the customer's device capabilities, the console's read state, assignment, and labels, and the routing queue the conversation is in.\n","required":["id","business_id","status","origin","opaque_user_id","phone_number","group_id","intent_id","device_capabilities","supported_content_kinds","locale","unread_count","message_count","last_message_at","last_direction","assigned_to","labels","closed_at","closed_reason","open_count","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AMBConversationID","description":"Conversation ID."},"business_id":{"readOnly":true,"$ref":"#/components/schemas/AMBBusinessID","description":"Business this conversation belongs to."},"status":{"$ref":"#/components/schemas/AMBConversationStatus","readOnly":true},"origin":{"$ref":"#/components/schemas/AMBConversationOrigin","readOnly":true},"opaque_user_id":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"Apple's opaque identifier for the customer's device, present once the customer has opened the conversation from an entry point. Null on a conversation created from an invitation that has not yet been accepted.\n"},"phone_number":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"The phone number an invitation was sent to, before the customer accepts and Apple issues `opaque_user_id`. Null on a conversation that did not start from an invitation. This surface never creates an invitation.\n"},"group_id":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"The `group` value carried by the inbound message that opened the conversation. Your business chooses it when configuring an entry point with Apple, and Apple passes it through; used with `intent_id` to route the conversation. Null when the opening message carried none.\n"},"intent_id":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"The `intent` value carried by the inbound message that opened the conversation. Your business chooses it when configuring an entry point with Apple, and Apple passes it through; used with `group_id` to route the conversation. Null when the opening message carried none.\n"},"entry_point":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"The entry point in your channel settings whose group and intent matched the inbound message that opened the conversation. Null when no configured entry point matched.\n","example":"support"},"device_capabilities":{"type":"array","readOnly":true,"items":{"type":"string","minLength":1},"description":"The capability tokens the customer's device advertised on its most recent message, replaced by each inbound rather than accumulated, so this describes the device in use now. An empty list means the device's capabilities are unknown. Implemented message types may still be sent, but device rendering support has not been confirmed. Authentication requires an explicitly advertised AUTH2 capability.\n","example":["QUICK","LIST","TIME"]},"supported_content_kinds":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AMBContentKind"},"description":"Implemented baseline types plus interactive types confirmed by `device_capabilities`. An empty capability list yields text, attachments and rich links; it does not establish support for other types. Unadvertised quick replies, list pickers, time pickers and forms are refused when capabilities are known. Custom apps and opaque interactive references are not included because their device support cannot be inferred from these tokens. Unsupported roadmap types cannot be sent.\n"},"locale":{"type":"string","readOnly":true,"minLength":1,"description":"The customer's locale from the most recent inbound message, or your business's default locale before any inbound arrives. Preserved in Apple's locale format, for example `en_US@rg=nlzzzz`.\n","example":"en-US"},"unread_count":{"type":"integer","readOnly":true,"minimum":0,"description":"Number of inbound messages since this conversation was last marked read. Incremented once per inbound message, reset to zero by marking the conversation read and by any outbound message your workspace sends.\n"},"message_count":{"type":"integer","readOnly":true,"minimum":0,"description":"Number of messages in this conversation, both directions."},"last_message_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the most recent message in this conversation was sent or received."},"last_direction":{"$ref":"#/components/schemas/AMBMessageDirection","readOnly":true,"description":"Direction of the most recent message."},"assigned_to":{"oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}],"description":"The user this conversation is assigned to, or null when unassigned. Assignment is not rechecked against workspace membership on read, so it can still name a user whose access was removed.\n"},"labels":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"maxItems":20,"description":"Operator-set tags on this conversation. Unlike email, there are no system placement labels: every value here is one an operator chose.\n"},"queue":{"type":"string","readOnly":true,"description":"The console queue this conversation is routed to. Empty when no routing rule matched, which the console lists as unrouted.\n","example":"support"},"closed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this conversation was closed. Null while it is open."},"closed_reason":{"oneOf":[{"$ref":"#/components/schemas/AMBConversationClosedReason"},{"type":"null"}],"readOnly":true,"description":"Why this conversation was closed. Null while it is open."},"open_count":{"type":"integer","readOnly":true,"minimum":1,"description":"Number of times this conversation has been opened, starting at 1 and incremented on each reopen. A closed conversation reopens on the next inbound message rather than creating a new conversation.\n"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this conversation was created."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this conversation last changed."},"authentication":{"readOnly":true,"$ref":"#/components/schemas/AMBConversationAuthentication","description":"Identity verification progress and the last verified identity. Included only when a user session reads one conversation."}}},"AMBConversationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of conversations, newest first by last message.","items":{"$ref":"#/components/schemas/AMBConversation"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AMBConversationUpdateRequest":{"type":"object","additionalProperties":false,"description":"Assignment, labels, and read state on a conversation. There is no close action here: only the customer closes a conversation, from their device. Every field is optional; omit a field to leave it unchanged.\n","properties":{"assigned_to":{"oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}],"description":"User to assign this conversation to. Pass null to unassign it."},"labels":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"maxItems":20,"description":"Replaces the full set of labels on this conversation. Pass an empty array to clear every label.\n"},"read":{"type":"boolean","description":"Set to true to mark this conversation read, resetting `unread_count` to zero. There is no way to mark a conversation unread through this field; false has no effect.\n"}}},"AMBPaymentMethod":{"type":"string","minLength":1,"enum":["apple_pay"]},"AMBPaymentStatus":{"type":"string","minLength":1,"enum":["pending","processing","succeeded","failed","expired","cancelled"],"description":"Payment request status:\n\n- `pending`: Awaiting a payment attempt. A retryable decline returns the\n  request to this status so the customer can try again before it expires.\n- `processing`: A payment attempt is in progress or its provider outcome is\n  unresolved. Wait for reconciliation before requesting another payment.\n- `succeeded`: The provider confirmed payment for the requested amount and\n  currency. Terminal.\n- `failed`: The request stopped because of a non-retryable payment or\n  dispatch failure. See `last_error` for the reason. Terminal.\n- `expired`: The request expired while awaiting a payment attempt. Terminal.\n- `cancelled`: The provider cancelled the payment, or the payment connection\n  was disconnected before payment could proceed. Terminal.\n"},"AMBPaymentError":{"type":"object","additionalProperties":false,"required":["code","description"],"properties":{"code":{"type":"string","minLength":1},"description":{"type":"string","minLength":1}}},"AMBPayment":{"type":"object","additionalProperties":false,"required":["id","conversation_id","business_id","connection_id","provider","environment","simulated","method","order_reference","amount","currency","status","message_id","expires_at","last_error","verified_at","provider_reference","created_at","updated_at","version"],"properties":{"id":{"$ref":"#/components/schemas/AMBPaymentID"},"conversation_id":{"$ref":"#/components/schemas/AMBConversationID"},"business_id":{"$ref":"#/components/schemas/AMBBusinessID"},"connection_id":{"$ref":"#/components/schemas/AMBPaymentConnectionID"},"provider":{"$ref":"#/components/schemas/AMBPaymentProvider"},"environment":{"$ref":"#/components/schemas/AMBPaymentEnvironment"},"simulated":{"type":"boolean","description":"True when the local test simulator produced this lifecycle. A simulated success is not provider or device verification."},"method":{"$ref":"#/components/schemas/AMBPaymentMethod"},"order_reference":{"type":"string","minLength":1,"maxLength":128},"amount":{"type":"string","minLength":1,"pattern":"^[0-9]+(?:\\.[0-9]+)?$"},"currency":{"type":"string","minLength":3,"maxLength":3,"pattern":"^[A-Z]{3}$"},"status":{"$ref":"#/components/schemas/AMBPaymentStatus"},"message_id":{"oneOf":[{"$ref":"#/components/schemas/AMBMessageID"},{"type":"null"}]},"expires_at":{"type":"string","minLength":1,"format":"date-time"},"last_error":{"oneOf":[{"$ref":"#/components/schemas/AMBPaymentError"},{"type":"null"}]},"verified_at":{"type":["string","null"],"minLength":1,"format":"date-time"},"provider_reference":{"type":["string","null"],"minLength":1,"description":"Read-only diagnostic reference for the connected provider transaction."},"created_at":{"type":"string","minLength":1,"format":"date-time"},"updated_at":{"type":"string","minLength":1,"format":"date-time"},"version":{"type":"integer","format":"int64","minimum":1}}},"AMBPaymentList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AMBPayment"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AMBPaymentLineItem":{"type":"object","additionalProperties":false,"required":["label","amount"],"properties":{"label":{"type":"string","minLength":1,"maxLength":128},"amount":{"type":"string","minLength":1,"pattern":"^-?[0-9]+(?:\\.[0-9]+)?$","description":"Exact decimal amount in the payment currency, without rounding."}}},"AMBPaymentCreateRequest":{"type":"object","additionalProperties":false,"required":["environment","order_reference","amount","currency"],"properties":{"environment":{"$ref":"#/components/schemas/AMBPaymentEnvironment"},"order_reference":{"type":"string","minLength":1,"maxLength":128},"amount":{"type":"string","minLength":1,"pattern":"^[0-9]+(?:\\.[0-9]+)?$"},"currency":{"type":"string","minLength":3,"maxLength":3,"pattern":"^[A-Z]{3}$"},"total_label":{"type":"string","minLength":1,"maxLength":128},"line_items":{"type":"array","minItems":1,"maxItems":20,"items":{"$ref":"#/components/schemas/AMBPaymentLineItem"}}}},"AMBConversationTypingEvent":{"type":"string","minLength":1,"enum":["typing_start","typing_end"],"description":"The typing signal to send. `typing_start` tells the customer's device that an operator is composing a reply. `typing_end` tells it composition stopped without a message following. Apple expects at most one `typing_start` before the reply it precedes; sending it again before that reply is not meaningful and may be dropped. `typing_end`'s behavior against a live conversation is unproven: the legacy platform's implementation was disabled after it caused issues, so treat it as best-effort.\n"},"AMBConversationTypingRequest":{"type":"object","additionalProperties":false,"description":"A typing indicator to send to the customer's device on this conversation.\n","required":["event"],"properties":{"event":{"$ref":"#/components/schemas/AMBConversationTypingEvent"}}},"AMBSuppressionHandleType":{"type":"string","minLength":1,"enum":["phone_number","opaque_user_id"],"description":"What kind of value `handle` holds.\n\n- `phone_number` means `handle` is the customer's phone number. Apple's CloseSession event carries a phone number rather than an opaque identifier, so a suppression opened by a close on a conversation identified by phone number takes this kind.\n- `opaque_user_id` means `handle` is the opaque identifier Apple assigns to the customer's conversation with the business, stable across a close and a later re-initiation.\n","example":"opaque_user_id"},"AMBSuppressionReason":{"type":"string","minLength":1,"x-extensible-enum":["manual","opted_out"],"description":"Why the handle is suppressed. `manual` means it was added directly through this API or the dashboard. `opted_out` covers every case where Apple or the customer signaled they should not be contacted: a close, a permanent delivery failure, a declined invitation, or a stop keyword. This list grows over time, so treat an unknown value as informational rather than rejecting the record.\n","example":"manual"},"AMBSuppressionID":{"type":"string","minLength":1,"pattern":"^asp_[0-9a-hjkmnp-tv-z]{26}$","example":"asp_01krdgeqcxet5s7t44vh8rt9mg"},"AMBSuppressionOrigin":{"type":"string","minLength":1,"enum":["user","api_key","close_session","gone","invitation_declined","keyword"],"description":"How this episode came to exist.\n\n- `user` means a dashboard user added it directly.\n- `api_key` means an API caller added it directly.\n- `close_session` means the customer sent Apple's close event, ending the conversation.\n- `gone` means a send to the handle returned Apple's permanent-failure response, which Bird treats as the same signal as a close.\n- `invitation_declined` means the customer declined an invitation. No operation writes this value yet; it starts once invitations ship.\n- `keyword` means the customer sent a stop keyword. No operation writes this value yet; it starts once keyword automation ships.\n","example":"user"},"AMBSuppression":{"description":"One period during which a handle is suppressed on one Apple Messages for Business brand, or across the whole workspace: when it started and, once it has ended, what ended it. A handle suppressed, ended, and suppressed again has two of these on record rather than one current state. The list returns only episodes still in force; fetch one by ID to read one that has ended.\n","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","handle","handle_type","reason","origin","applies_to","effective_at"],"properties":{"id":{"readOnly":true,"description":"Unique identifier for the suppression episode.","$ref":"#/components/schemas/AMBSuppressionID"},"business_id":{"description":"The brand this episode applies to, or null when it covers every brand in the workspace. Apple requires that a brand not message someone who opted out of it, so an episode scoped to one brand does not suppress a sibling brand in the same workspace.\n","oneOf":[{"$ref":"#/components/schemas/AMBBusinessID"},{"type":"null"}]},"handle":{"type":"string","minLength":1,"description":"The suppressed phone number or opaque identifier. `handle_type` says which. For a phone number this is canonical E.164 with a leading plus sign.\n","example":"+15551234567"},"handle_type":{"$ref":"#/components/schemas/AMBSuppressionHandleType"},"reason":{"readOnly":true,"$ref":"#/components/schemas/AMBSuppressionReason"},"origin":{"readOnly":true,"$ref":"#/components/schemas/AMBSuppressionOrigin"},"applies_to":{"readOnly":true,"type":"string","minLength":1,"x-extensible-enum":["all","invitations"],"description":"What this episode blocks. `all` blocks every outbound path: operator and automated replies, API sends, typing indicators, and invitations. `invitations` blocks only a new invitation to the handle, leaving replies in a conversation the customer opens directly unaffected; no operation writes this value yet, since invitations are a future feature. This list grows over time, so treat an unknown value as blocking.\n","example":"all"},"source_message_id":{"readOnly":true,"type":["string","null"],"minLength":1,"description":"ID of what opened this episode. A permanent delivery failure on send names the message that failed, an `amb_` id. A close names the conversation event Bird recorded for it instead, an `aev_` id, because a close carries no message of its own. Null for an episode opened directly through this API or by a phone-addressed close that has no conversation event.\n","example":"amb_01krdgeqcxet5s7t44vh8rt9mg"},"source_end_message_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/AMBMessageID"},{"type":"null"}],"description":"ID of the inbound message that ended this episode by re-initiating the conversation. Null while the episode is still in force, and null for an episode ended through this API instead.\n","example":"amb_01krdgeqcxet5s7t44vh8rt9mg"},"effective_at":{"readOnly":true,"type":"string","format":"date-time","minLength":1,"description":"When this episode started blocking outbound messages.","example":"2026-08-12T09:00:00Z"},"ended_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When this episode stopped applying. Null while it is still blocking outbound messages, which is the case for every episode the list returns.\n","example":null},"ended_reason":{"readOnly":true,"type":["string","null"],"x-extensible-enum":["user","api_key","reinitiated"],"description":"What ended it. `reinitiated` means the same customer started the conversation again: this ends an episode opened by `close_session` or `gone` automatically, with no API call, and does not apply to one opened by `invitation_declined`, which needs an explicit re-subscription instead. `user` and `api_key` mean this API ended it directly, which works for an episode of any origin. Null while the episode is still in force.\n","example":null},"ended_effective_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When the ended state took effect, which can differ from `ended_at` when the record of an end arrives later than the event that caused it.\n","example":null}}}]},"AMBSuppressionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Active suppression episodes for the workspace, most recently effective first. Episodes that have ended are left out; fetch one by ID to read it.\n","items":{"$ref":"#/components/schemas/AMBSuppression"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AMBSuppressionCreateRequest":{"type":"object","additionalProperties":false,"required":["handle","handle_type"],"properties":{"business_id":{"description":"Suppress the handle for this brand only. Omit or send null to block it for every brand in the workspace.\n","oneOf":[{"$ref":"#/components/schemas/AMBBusinessID"},{"type":"null"}]},"handle":{"type":"string","minLength":1,"description":"The phone number or opaque identifier to suppress. For a phone number, supply canonical E.164 with a leading plus sign.\n","example":"+15551234567"},"handle_type":{"$ref":"#/components/schemas/AMBSuppressionHandleType"}}},"AMBStatsSummaryPeriod":{"type":"object","additionalProperties":false,"description":"The window the server actually computed against. The summary serves two window grains: calendar days (bounds are YYYY-MM-DD) and hours (bounds are RFC 3339 instants on the hour). The grain of `from` and `to` mirrors the grain of the request's bounds.\n","required":["from","to"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary. Historical starts are preserved; the maximum request length does not impose a historical cutoff.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary.","example":"2026-05-25"},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable.\n","example":"2026-05-25T14:03:10Z"}}},"AMBStatsAttribution":{"type":"string","minLength":1,"readOnly":true,"enum":["accepted_time","event_time"],"description":"Which timestamp a statistics response buckets its rows and totals by:\n\n- `accepted_time`: attributed to when Bird accepted the outbound message for sending. The outbound send statistics use this, so a later event for the same message, such as a send failure, still counts against the day or hour its message was accepted.\n- `event_time`: attributed to when the event itself occurred. Inbound message statistics, conversation statistics and the staff per-business failure counts use this, since there is no earlier outbound event to anchor them to.\n\nA response never mixes the two axes: every row and total in one payload shares the same attribution.\n","example":"accepted_time"},"AMBOutboundStatsCounts":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Outbound Apple Messages for Business counts for the requested scope, attributed to when each message was accepted. Apple Messages for Business has no delivery receipt, so there is no `delivered` count anywhere in this API: `sent` is the last outbound state Bird observes for a message. Very large counts are close estimates rather than exact tallies. Rates are computed once here, clamped to 1, and null when nothing was accepted.\n","required":["accepted","sent","send_failed","rejected","sent_rate","send_failure_rate"],"properties":{"accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages accepted for sending after admission checks. This is the denominator for `sent_rate` and `send_failure_rate`.","example":4820},"sent":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages handed off to Apple.","example":4790},"send_failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct accepted messages that Apple refused or that exhausted their send attempts. See `last_error.code` on the message for the reason; a refused charge is not a send failure, it is `rejected`.","example":30},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages refused before any send attempt, because the destination has no price, the wallet could not fund the send, or the content cannot be sent yet. Rejected messages are never charged and are not counted in `accepted`, so the total addressed is `accepted + rejected`. Excluded from `send_failure_rate`, which covers send failures only.","example":4},"sent_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of accepted messages Apple acknowledged, computed as `sent / accepted`. Null when no messages were accepted in scope. This stands where other channels report a delivery rate.","example":0.9938},"send_failure_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of accepted messages that failed to send, computed as `send_failed / accepted`. Null when no messages were accepted in scope.","example":0.0062}}},"AMBStatsQuantiles":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family. All three are null when no qualifying event contributed a measurement.\n","required":["p50_ms","p95_ms","p99_ms"],"properties":{"p50_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement.","example":610},"p95_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.","example":2140},"p99_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.","example":5380}}},"AMBStatsLatency":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Processing-latency percentiles in milliseconds for the requested scope, from acceptance to Apple handoff. Apple Messages for Business has no delivery receipt, so there is no `delivery` or `total` member beside `processing`. Conversation response timing is reported separately in `first_response`. Always present; every percentile is null when no qualifying message in scope has a measurement.\n","required":["processing"],"properties":{"processing":{"$ref":"#/components/schemas/AMBStatsQuantiles"}}},"AMBStatsComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Changes from the previous period. Each value is the signed relative change `(current - previous) / previous` and is null when the previous count is zero.\n","required":["accepted_pct_change","sent_pct_change","send_failed_pct_change","rejected_pct_change"],"properties":{"accepted_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in accepted messages (`counts.accepted`) versus the previous period, as a signed fraction. Null when the previous period accepted none.","example":0.508},"sent_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in sent messages (`counts.sent`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0.501},"send_failed_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in send failures (`counts.send_failed`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":-0.12},"rejected_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in rejected messages (`counts.rejected`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0}}},"AMBStatsComparison":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The same statistics for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request.\n","required":["period","counts","latency","delta"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The preceding window these comparison figures cover, the equal-length window ending immediately before the requested start (the prior day for day windows, the prior hour for hour windows)."},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBOutboundStatsCounts"},"latency":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsLatency"},"first_response":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsQuantiles","description":"First-response latency percentiles for the preceding period. Omitted when no qualifying message in that period has a measurement."},"delta":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsComparisonDelta"}}},"AMBStatsSummary":{"type":"object","additionalProperties":false,"description":"Outbound Apple Messages for Business counts and latency percentiles for the full requested period. Counts and percentiles are computed over the whole period rather than combined from the returned time-series values.\n","required":["period","attribution","counts","latency"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBOutboundStatsCounts"},"latency":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsLatency"},"first_response":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsQuantiles","description":"Time from the oldest unanswered inbound message to acceptance of the first outbound reply that claims it. Reported on summaries and breakdowns. Omitted when no qualifying reply contributes a measurement."},"comparison":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsComparison"}}},"AMBStatsSeriesPeriod":{"type":"object","additionalProperties":false,"description":"The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to.\n","required":["from","to","grain"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window. A calendar day (YYYY-MM-DD) on the day grain, an RFC 3339 instant on the hour grain. Historical starts are preserved; the maximum request length does not impose a historical cutoff.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window. A calendar day (YYYY-MM-DD) on the day grain, an RFC 3339 instant on the hour grain.","example":"2026-05-25"},"grain":{"$ref":"#/components/schemas/StatsGrain","readOnly":true},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable.\n","example":"2026-05-25T14:03:10Z"}}},"AMBStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Outbound Apple Messages for Business counts and processing-latency percentiles for one time bucket (a calendar day or hour), bucketed by acceptance time. Every count in a bucket describes the messages accepted in it, regardless of when their later events arrived. There is no `first_response` here. Read first-response latency from the summary or a breakdown endpoint.\n","required":["bucket","counts","latency"],"properties":{"bucket":{"type":"string","minLength":1,"readOnly":true,"description":"The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the period's grain.","example":"2026-05-25"},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBOutboundStatsCounts"},"latency":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsLatency"}}},"AMBStatsResponse":{"type":"object","additionalProperties":false,"description":"Time-series stats payload. `period` echoes the range and bucket grain the server computed against; `data` is one row per bucket in chronological order.\n","required":["period","attribution","data"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSeriesPeriod"},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"One row per day or hour in chronological order. Buckets with no activity contain zero counts.","items":{"$ref":"#/components/schemas/AMBStatsPoint"}}}},"AMBBusinessStatsPoint":{"type":"object","additionalProperties":false,"description":"Outbound counts and latency percentiles for a single business over the requested period.","required":["business_id","counts","latency"],"properties":{"business_id":{"$ref":"#/components/schemas/AMBBusinessID","readOnly":true,"description":"The business these messages were sent from."},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBOutboundStatsCounts"},"latency":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsLatency"},"first_response":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsQuantiles","description":"First-response latency percentiles for this business. Omitted when no qualifying message contributes a measurement."}}},"AMBStatsByBusinessResponse":{"type":"object","additionalProperties":false,"description":"Per-business breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","attribution","data","total"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"Business rows ranked by accepted volume descending.","items":{"$ref":"#/components/schemas/AMBBusinessStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct businesses with activity in the period, regardless of `limit`.","example":1}}},"AMBMessageKindStatsPoint":{"type":"object","additionalProperties":false,"description":"Outbound counts and latency percentiles for a single message content kind over the requested period.","required":["message_kind","counts","latency"],"properties":{"message_kind":{"$ref":"#/components/schemas/AMBContentKind","readOnly":true,"description":"The content kind these messages were sent as, the same value the message read returns as its content kind discriminator."},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBOutboundStatsCounts"},"latency":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsLatency"},"first_response":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsQuantiles","description":"First-response latency percentiles for this content kind. Omitted when no qualifying message contributes a measurement."}}},"AMBStatsByMessageKindResponse":{"type":"object","additionalProperties":false,"description":"Per-content-kind breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","attribution","data","total"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"Content-kind rows ranked by accepted volume descending.","items":{"$ref":"#/components/schemas/AMBMessageKindStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct content kinds with activity in the period, regardless of `limit`.","example":4}}},"AMBIntentStatsPoint":{"type":"object","additionalProperties":false,"description":"Outbound counts and latency percentiles for a single entry-point intent over the requested period.","required":["intent","counts","latency"],"properties":{"intent":{"type":"string","minLength":1,"readOnly":true,"description":"The intent these messages were routed under, as configured in the business's entry points. Intents are workspace-defined and have no fixed vocabulary.","example":"order_support"},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBOutboundStatsCounts"},"latency":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsLatency"},"first_response":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsQuantiles","description":"First-response latency percentiles for this intent. Omitted when no qualifying message contributes a measurement."}}},"AMBStatsByIntentResponse":{"type":"object","additionalProperties":false,"description":"Per-intent breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","attribution","data","total"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"Intent rows ranked by accepted volume descending.","items":{"$ref":"#/components/schemas/AMBIntentStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct intents with activity in the period, regardless of `limit`.","example":5}}},"AMBGroupStatsPoint":{"type":"object","additionalProperties":false,"description":"Outbound counts and latency percentiles for a single entry-point group over the requested period.","required":["group","counts","latency"],"properties":{"group":{"type":"string","minLength":1,"readOnly":true,"description":"The group these messages were routed under, as configured in the business's entry points. Groups are workspace-defined and have no fixed vocabulary.","example":"sales"},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBOutboundStatsCounts"},"latency":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsLatency"},"first_response":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsQuantiles","description":"First-response latency percentiles for this group. Omitted when no qualifying message contributes a measurement."}}},"AMBStatsByGroupResponse":{"type":"object","additionalProperties":false,"description":"Per-group breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","attribution","data","total"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"Group rows ranked by accepted volume descending.","items":{"$ref":"#/components/schemas/AMBGroupStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct groups with activity in the period, regardless of `limit`.","example":3}}},"AMBCategoryStatsPoint":{"type":"object","additionalProperties":false,"description":"Outbound counts and latency percentiles for a single message category over the requested period.","required":["counts","latency"],"properties":{"category":{"type":"string","readOnly":true,"description":"The category these messages were sent with. Defaults to an empty string when a send names no category.","example":"order_update"},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBOutboundStatsCounts"},"latency":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsLatency"},"first_response":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsQuantiles","description":"First-response latency percentiles for this category. Omitted when no qualifying message contributes a measurement."}}},"AMBStatsByCategoryResponse":{"type":"object","additionalProperties":false,"description":"Per-category breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","attribution","data","total"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"Category rows ranked by accepted volume descending.","items":{"$ref":"#/components/schemas/AMBCategoryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct categories with activity in the period, regardless of `limit`.","example":6}}},"AMBTagStatsPoint":{"type":"object","additionalProperties":false,"description":"Outbound counts and latency percentiles for a single tag over the requested period.","required":["tag","counts","latency"],"properties":{"tag":{"type":"string","minLength":1,"readOnly":true,"description":"The tag these messages carry, as a bare name or a `name:value` pair. A message with several tags is counted once under each, so rows do not sum to the period total.","example":"campaign:spring_launch"},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBOutboundStatsCounts"},"latency":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsLatency"},"first_response":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsQuantiles","description":"First-response latency percentiles for this tag. Omitted when no qualifying message contributes a measurement."}}},"AMBStatsByTagResponse":{"type":"object","additionalProperties":false,"description":"Per-tag breakdown for the requested period, ranked by accepted volume descending and capped at the requested `limit` (default 50, max 200). Only tagged messages appear.","required":["period","attribution","data","total"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"Tag rows ranked by accepted volume descending.","items":{"$ref":"#/components/schemas/AMBTagStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct tags with activity in the period, regardless of `limit`.","example":9}}},"AMBErrorCodeStatsPoint":{"type":"object","additionalProperties":false,"description":"Outbound counts and latency percentiles for a single failure reason over the requested period.","required":["error_code","counts","latency"],"properties":{"error_code":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsErrorCode","description":"The failure reason these messages share."},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBOutboundStatsCounts"},"latency":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsLatency"},"first_response":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsQuantiles","description":"First-response latency for failed replies that claimed an unanswered inbound message. Omitted when no qualifying reply contributes a measurement.\n"}}},"AMBStatsByErrorCodeResponse":{"type":"object","additionalProperties":false,"description":"Per-failure-reason breakdown for the requested period, ranked by `send_failed + rejected` descending and capped at the requested `limit` (default 50, max 200). Only messages carrying an error code appear: a `bird:` code is usually a rejection, an `apple:` code a send failure.","required":["period","attribution","data","total"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"Error-code rows ranked by `send_failed + rejected` descending.","items":{"$ref":"#/components/schemas/AMBErrorCodeStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct error codes with activity in the period, regardless of `limit`.","example":2}}},"AMBInboundStatsComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The change from the preceding period to the requested one. The `received_pct_change` field is a signed relative change, computed as `(current - previous) / previous`. A value of `0.5` means 50% higher, and `-0.2` means 20% lower. The field is null when the previous period received none.\n","required":["received_pct_change"],"properties":{"received_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in received messages versus the previous period, as a signed fraction. Null when the previous period received none.","example":0.058}}},"AMBInboundStatsComparison":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The received-message count for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request.\n","required":["period","received","delta"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The preceding window these comparison figures cover, the equal-length window ending immediately before the requested start (the prior day for day windows, the prior hour for hour windows)."},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received in the preceding period.","example":3980},"delta":{"readOnly":true,"$ref":"#/components/schemas/AMBInboundStatsComparisonDelta"}}},"AMBInboundStatsSummary":{"type":"object","additionalProperties":false,"description":"Total inbound Apple Messages for Business messages for the requested period.\n","required":["period","attribution","received"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `event_time` for inbound statistics."},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received in the period, counted by the time each message occurred. Computed across the whole window rather than summed from the daily or hourly series, so it can sit slightly below the sum of those rows.","example":4210},"comparison":{"readOnly":true,"$ref":"#/components/schemas/AMBInboundStatsComparison"}}},"AMBInboundStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Received-message count for one time bucket (a calendar day or hour), bucketed by the time each message occurred.\n","required":["bucket","received"],"properties":{"bucket":{"type":"string","minLength":1,"readOnly":true,"description":"The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the request's grain.","example":"2026-05-25"},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received in this bucket.","example":182}}},"AMBInboundStatsResponse":{"type":"object","additionalProperties":false,"description":"Received-message time series. `period` echoes the range the server computed against; `data` is one row per bucket in chronological order.\n","required":["period","attribution","data"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSeriesPeriod"},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `event_time` for inbound statistics."},"data":{"type":"array","readOnly":true,"description":"One row per bucket (day or hour, matching the request) in the period, in chronological order. Buckets with no activity are included with a count of zero, so the series charts continuously without client-side gap handling.","items":{"$ref":"#/components/schemas/AMBInboundStatsPoint"}}}},"AMBInboundBusinessStatsPoint":{"type":"object","additionalProperties":false,"description":"Received-message count for a single business over the requested period.","required":["business_id","received"],"properties":{"business_id":{"$ref":"#/components/schemas/AMBBusinessID","readOnly":true,"description":"The business that received these messages."},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received by this business in the period.","example":640}}},"AMBInboundStatsByBusinessResponse":{"type":"object","additionalProperties":false,"description":"Per-business breakdown of received messages for the requested period, ranked by volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","attribution","data","total"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `event_time` for inbound statistics."},"data":{"type":"array","readOnly":true,"description":"Business rows ranked by received-message volume descending, capped at the requested `limit`. A business with no received messages in the period is absent rather than zero-filled, because unlike a time bucket it is not part of a continuous axis.","items":{"$ref":"#/components/schemas/AMBInboundBusinessStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct businesses with received messages in the period, regardless of `limit`.","example":1}}},"AMBInboundIntentStatsPoint":{"type":"object","additionalProperties":false,"description":"Received-message count for a single entry-point intent over the requested period.","required":["intent","received"],"properties":{"intent":{"type":"string","minLength":1,"readOnly":true,"description":"The intent these messages arrived under, as configured in the business's entry points. Intents are workspace-defined and have no fixed vocabulary.","example":"order_support"},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct messages received under this intent in the period.","example":305}}},"AMBInboundStatsByIntentResponse":{"type":"object","additionalProperties":false,"description":"Per-intent breakdown of received messages for the requested period, ranked by volume descending and capped at the requested `limit` (default 50, max 200).","required":["period","attribution","data","total"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back), plus `data_as_of`."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `event_time` for inbound statistics."},"data":{"type":"array","readOnly":true,"description":"Intent rows ranked by received-message volume descending, capped at the requested `limit`. An intent with no received messages in the period is absent rather than zero-filled, because unlike a time bucket it is not part of a continuous axis.","items":{"$ref":"#/components/schemas/AMBInboundIntentStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total distinct intents with received messages in the period, regardless of `limit`.","example":4}}},"AMBConversationStatsCounts":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Conversation lifecycle counts for the requested scope, attributed to when each event occurred. A conversation can start, reopen, and close more than once over its life, so `started`, `reopened`, and `closed` can each exceed `conversations`, the number of distinct conversations touched in scope. Very large counts are close estimates rather than exact tallies.\n","required":["started","reopened","closed","conversations"],"properties":{"started":{"type":"integer","minimum":0,"readOnly":true,"description":"Count of conversation-started events in scope.","example":210},"reopened":{"type":"integer","minimum":0,"readOnly":true,"description":"Count of conversation-reopened events in scope.","example":34},"closed":{"type":"integer","minimum":0,"readOnly":true,"description":"Count of conversation-closed events in scope.","example":198},"conversations":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct conversations with at least one lifecycle event in scope.","example":205}}},"AMBConversationStatsComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Changes from the previous period. Each value is the signed relative change `(current - previous) / previous` and is null when the previous count is zero.\n","required":["started_pct_change","reopened_pct_change","closed_pct_change","conversations_pct_change"],"properties":{"started_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in conversation starts (`counts.started`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0.22},"reopened_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in conversation reopens (`counts.reopened`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":-0.05},"closed_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in conversation closes (`counts.closed`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0.19},"conversations_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in distinct conversations touched (`counts.conversations`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0.21}}},"AMBConversationStatsComparison":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The same statistics for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request.\n","required":["period","counts","delta"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The preceding window these comparison figures cover, the equal-length window ending immediately before the requested start (the prior day for day windows, the prior hour for hour windows)."},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBConversationStatsCounts"},"delta":{"readOnly":true,"$ref":"#/components/schemas/AMBConversationStatsComparisonDelta"}}},"AMBConversationStatsSummary":{"type":"object","additionalProperties":false,"description":"Conversation lifecycle counts for the full requested period. Counts aggregate the time buckets.\n","required":["period","attribution","counts"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The window the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `event_time` for conversation statistics."},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBConversationStatsCounts"},"comparison":{"readOnly":true,"$ref":"#/components/schemas/AMBConversationStatsComparison"}}},"AMBConversationStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Conversation lifecycle counts for one time bucket (a calendar day or hour), bucketed by when each event occurred.\n","required":["bucket","counts"],"properties":{"bucket":{"type":"string","minLength":1,"readOnly":true,"description":"The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the period's grain.","example":"2026-05-25"},"counts":{"readOnly":true,"$ref":"#/components/schemas/AMBConversationStatsCounts"}}},"AMBConversationStatsResponse":{"type":"object","additionalProperties":false,"description":"Conversation lifecycle time series. `period` echoes the range and bucket grain the server computed against; `data` is one row per bucket in chronological order.\n","required":["period","attribution","data"],"properties":{"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSeriesPeriod"},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `event_time` for conversation statistics."},"data":{"type":"array","readOnly":true,"description":"One row per day or hour in chronological order. Buckets with no activity contain zero counts.","items":{"$ref":"#/components/schemas/AMBConversationStatsPoint"}}}},"WhatsAppSignupNumber":{"type":"object","additionalProperties":false,"required":["number_id","phone_number"],"properties":{"number_id":{"allOf":[{"$ref":"#/components/schemas/NumbersDedicatedAllocationID"}],"readOnly":true,"description":"The number you hold with us, as its id in GET /v1/numbers. Pass it to POST /v1/whatsapp/numbers to start connecting this number to WhatsApp.\n"},"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"The number in E.164 form.","example":"+31612340001"},"whatsapp_number_id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppNumberID"}],"readOnly":true,"description":"Identifies a pre-verified entry, present only for a number we have already pre-verified for you. It is the id of the WhatsApp number waiting for you to finish signup, as in GET /v1/whatsapp/numbers/{number_id}. Absent for a number that has not been pre-verified.\n"},"meta_preverified_id":{"type":"string","minLength":1,"readOnly":true,"description":"WhatsApp's own id for the number while we prepare it for you. We hand it to WhatsApp when you finish signing up, so WhatsApp knows this is the number we already verified for you. Present whenever `whatsapp_number_id` is; absent for a number that has not been pre-verified.\n","example":"109364591979012"},"verified_until":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"Present for most pre-verified numbers, naming when we expect WhatsApp to release an unclaimed one if you have not finished signup by then. Re-verifying it after that can take a little time, since WhatsApp does not always accept a new request right away. Absent for a number pre-verified before we started recording this moment, and for a number that has not been pre-verified at all.\n","example":"2026-11-19T10:22:31Z"}}},"WhatsAppSignupNumberList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The numbers your workspace can connect to WhatsApp.","items":{"$ref":"#/components/schemas/WhatsAppSignupNumber"}}}},"WhatsAppBusinessAccountSortField":{"type":"string","enum":["created_at"],"default":"created_at","description":"Sortable fields for a WhatsApp Business Account list."},"WhatsAppBusinessAccountStatus":{"type":"string","minLength":1,"x-extensible-enum":["active"],"description":"WhatsApp's own state for a WhatsApp Business Account. Values are WhatsApp's own tokens, lower-cased. This enum is open because WhatsApp documents the field in neither its API reference nor its machine-readable schema. The `active` value is the only value in WhatsApp's example response, so it is the only one Bird can name. Treat anything else as a state WhatsApp reports and this list has not caught up with.","example":"active"},"WhatsAppBusinessAccountReviewStatus":{"type":"string","minLength":1,"x-extensible-enum":["approved","deferred","pending","rejected"],"description":"How far WhatsApp's own review of this WhatsApp Business Account has got. `deferred` is WhatsApp postponing the review rather than refusing it. Values are WhatsApp's own tokens, lower-cased. Open enum: treat an unrecognized value as a review state WhatsApp added rather than as an error.","example":"approved"},"WhatsAppBusinessVerificationStatus":{"type":"string","minLength":1,"x-extensible-enum":["expired","failed","ineligible","not_verified","pending","pending_need_more_info","pending_submission","rejected","revoked","verified"],"description":"Whether Meta has verified the business behind this WhatsApp Business Account. Verification is one of the paths to a higher messaging limit, so a value other than `verified` is often the reason a limit has not moved. Values are Meta's own tokens, lower-cased. Open enum: treat an unrecognized value as a state Meta added rather than as an error.","example":"verified"},"WhatsAppBusinessAccountMarketingMessagesStatus":{"type":"string","minLength":1,"x-extensible-enum":["eligible","onboarded"],"description":"Whether this account can use WhatsApp's Marketing Messages API. `eligible` means WhatsApp would accept an onboarding request for it; `onboarded` means it has already been onboarded. Values are WhatsApp's own tokens, lower-cased. Open enum out of necessity. WhatsApp's onboarding guide names these two values and defers the rest to an API reference that does not document the field. Treat anything else as a state WhatsApp reports that this list has not caught up with.","example":"onboarded"},"WhatsAppBusinessPortfolioMarketingMessagesStatus":{"type":"string","minLength":1,"x-extensible-enum":["not_started","request_sent","term_of_service_signed"],"description":"How far the business portfolio has got through Meta's Marketing Messages\nterms of service.\n\n- `not_started`: the portfolio has not begun the process.\n- `request_sent`: a request is in.\n- `term_of_service_signed`: the terms are accepted.\n\nA portfolio property, so every account the portfolio owns reports the same\nvalue. Distinct from the account's own marketing-messages status, which Meta\nconfusingly gives the same name. Values are Meta's own tokens, lower-cased.\nOpen enum: treat an unrecognized value as a state Meta added rather than as\nan error.\n","example":"not_started"},"WhatsAppBusinessPortfolio":{"type":"object","additionalProperties":false,"readOnly":true,"required":["meta_id"],"description":"The Meta business portfolio that owns a WhatsApp Business Account. Bird holds no resource of its own for a portfolio, which is why the identifier is named `meta_id`: it is meaningful only against Meta's own tools, and it is not a Bird identifier.","properties":{"meta_id":{"type":"string","minLength":1,"readOnly":true,"description":"Meta's identifier for the portfolio. Treat it as an opaque string.","example":"178563218361309"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"The portfolio's name, as Meta reports it. Absent when Meta returned none.","example":"Acme Holdings"},"marketing_messages_onboarding_status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessPortfolioMarketingMessagesStatus"}],"readOnly":true,"description":"How far this portfolio has got through Meta's Marketing Messages terms of service. Absent until Meta has reported it. Distinct from the account's own `marketing_messages_onboarding_status`, which Meta gives the same field name but a different vocabulary: that one is the account's own eligibility, this one is the portfolio's Terms-of-Service progress."}}},"WhatsAppBusinessAccountBanState":{"type":"string","minLength":1,"enum":["disabled","scheduled_for_disable"],"description":"Whether WhatsApp has disabled a WhatsApp Business Account or scheduled it to be\ndisabled:\n\n- `disabled`: WhatsApp has disabled the account, and it cannot send.\n- `scheduled_for_disable`: WhatsApp has set a date to disable the account, which can\n  still send until then.\n\nAn account WhatsApp has reinstated reports no `ban` at all rather than a third value\nhere.\n","example":"disabled"},"WhatsAppBusinessAccountBan":{"type":"object","additionalProperties":false,"readOnly":true,"required":["state","occurred_at"],"description":"WhatsApp's ban on this account, as WhatsApp announced it. Absent when there is no ban, and also when there is one WhatsApp announced before Bird began recording bans, or whose notification never reached Bird, since WhatsApp does not replay them. This is what WhatsApp announced rather than the account's current state, so it is never the field to read to decide whether an account can send.","properties":{"state":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountBanState"}],"readOnly":true},"occurred_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When WhatsApp reported the ban, by WhatsApp's own clock. Bird can learn of a ban later than this, so it is not when Bird recorded it.","example":"2026-04-10T09:12:00Z"},"appeal_url":{"type":"string","format":"uri","readOnly":true,"description":"Where to appeal WhatsApp's decision with Meta Business Support, because neither Bird nor this API can lift one. Absent when Bird does not know the account's Meta business portfolio, since there is no support-home path to build without one.","example":"https://business.facebook.com/business-support-home/178563218361309/102290129340398"}}},"WhatsAppBusinessAccount":{"type":"object","additionalProperties":false,"required":["id","waba","name","status","created_at","updated_at"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountID"}],"readOnly":true,"description":"Unique identifier for the WhatsApp Business Account."},"waba":{"type":"string","minLength":1,"readOnly":true,"description":"Meta's own identifier for this WhatsApp Business Account. This is the value to send when creating a template on the account.\n","example":"102290129340398"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"The account's name, as WhatsApp reports it.","example":"Acme Inc"},"status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountStatus"}],"readOnly":true,"description":"WhatsApp's own state for this account as of `meta_synced_at`. The status is `active` until WhatsApp reports otherwise. WhatsApp already considers an account usable if Bird could connect a number under it. The absence of a reading is therefore not evidence of another state."},"account_review_status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountReviewStatus"}],"readOnly":true,"description":"How far WhatsApp's review of this account had got as of `meta_synced_at`. Absent until WhatsApp has reported it."},"business_verification_status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessVerificationStatus"}],"readOnly":true,"description":"Whether Meta had verified the business behind this account as of `meta_synced_at`. Absent until Meta has reported it."},"marketing_messages_onboarding_status":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountMarketingMessagesStatus"}],"readOnly":true,"description":"Whether this account can use WhatsApp's Marketing Messages API, as of `meta_synced_at`. Absent until WhatsApp has reported it. Distinct from the owning portfolio's `marketing_messages_onboarding_status` (`portfolio.marketing_messages_onboarding_status`), which Meta gives the same field name but a different vocabulary: this one is the account's own eligibility, that one is the portfolio's Terms-of-Service progress."},"portfolio":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessPortfolio"}],"readOnly":true,"description":"The Meta business portfolio that owns this account. Absent until Meta has reported it. The portfolio is where a messaging limit is set, so every account it owns shares one."},"ban":{"allOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountBan"}],"readOnly":true,"description":"WhatsApp's ban on this account, absent unless Bird was told of one. `status` is what the account said when Bird last read it; this is what WhatsApp announced, which arrives only on the webhook that announces it and is never re-read."},"meta_synced_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When Bird last read this account's state from WhatsApp. `status`, `account_review_status`, `business_verification_status`, `marketing_messages_onboarding_status` and `portfolio` are all that reading rather than live values; Bird re-reads roughly hourly. Absent for an account Bird has never read back."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this account was connected."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this account was last changed."}}},"WhatsAppBusinessAccountList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The WhatsApp Business Accounts your workspace has connected.","items":{"$ref":"#/components/schemas/WhatsAppBusinessAccount"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppSuppressionID":{"type":"string","minLength":1,"pattern":"^was_[0-9a-hjkmnp-tv-z]{26}$","example":"was_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppSuppression":{"type":"object","additionalProperties":false,"description":"One period during which an address is suppressed: when it started and, once it is over, what ended it. An address suppressed, ended and suppressed again has two of these on record rather than one current state. The list returns the periods in force; fetch one by ID to read one that has ended.\n","required":["id","address","reason","origin","applies_to","created_at"],"properties":{"id":{"readOnly":true,"description":"Unique identifier for the suppression record.","$ref":"#/components/schemas/WhatsAppSuppressionID"},"address":{"type":"string","minLength":1,"description":"The suppressed WhatsApp address. For a phone number this is canonical E.164 with a leading plus sign, such as `+5511977670804`.\n","example":"+5511977670804"},"waba":{"type":["string","null"],"readOnly":true,"description":"The WhatsApp Business Account the suppression is limited to, identified by its WhatsApp-issued account ID, or null when it covers the whole workspace.\n","example":null},"reason":{"type":"string","minLength":1,"x-extensible-enum":["manual"],"description":"Why the address is suppressed. `manual` means it was added directly rather than created automatically from a delivery outcome. This list grows over time, so treat an unknown value as informational rather than rejecting the record.\n"},"origin":{"type":"string","minLength":1,"x-extensible-enum":["api_key","user"],"description":"How the suppression came to exist: `api_key` (added through the API with an API key) or `user` (added by a user in the dashboard). This list grows over time, so treat an unknown value as informational rather than rejecting the record.\n"},"applies_to":{"type":"string","minLength":1,"x-extensible-enum":["all"],"description":"Blocking policy. `all` blocks every message category. Treat an unrecognized value as blocking.\n"},"source_whatsapp_id":{"description":"ID of the WhatsApp message that caused this address to be suppressed, when the suppression was created automatically. Omitted for addresses added manually.","allOf":[{"$ref":"#/components/schemas/WhatsAppMessageID"}]},"ended_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this stopped applying. Null while it is still stopping messages, which is the case for every record in the list.\n"},"ended_reason":{"type":["string","null"],"x-extensible-enum":["api_key","user"],"readOnly":true,"description":"What ended it: `api_key` (deleted through the API with an API key) or `user` (deleted by a user in the dashboard). Null while it is still stopping messages. This list grows over time, so treat an unknown value as informational rather than rejecting the record.\n"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the suppression was created."}}},"WhatsAppSuppressionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WhatsAppSuppression"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WhatsAppSuppressionCreate":{"type":"object","additionalProperties":false,"required":["address"],"properties":{"address":{"type":"string","minLength":1,"description":"WhatsApp address to suppress. For a phone number, supply canonical E.164 with a leading plus sign, such as `+5511977670804`. A value that is not a valid phone number returns a `422`.\n","example":"+5511977670804"},"waba":{"type":"string","minLength":1,"maxLength":64,"description":"Limit the suppression to messages sent from this WhatsApp Business Account, identified by its WhatsApp-issued account ID. Omit it to block the address for the whole workspace, whichever account sends.\n","example":"102290129340398"}}},"WhatsAppSuppressionBulkCreate":{"type":"object","additionalProperties":false,"required":["suppressions"],"properties":{"suppressions":{"type":"array","minItems":1,"maxItems":1000,"items":{"$ref":"#/components/schemas/WhatsAppSuppressionCreate"},"description":"Addresses to suppress, one row per address, in the same shape as `POST /v1/whatsapp/suppressions`. Each row is applied independently through the same idempotency rule as a single create (keyed on the address plus its optional `waba`), and reported at the same position in the response."}},"example":{"suppressions":[{"address":"+5511977670804"},{"address":"+5511988990011"}]}},"WhatsAppSuppressionBulkResultItem":{"type":"object","description":"One row's result. Which optional fields accompany the outcome is stated per field rather than encoded as a `oneOf`: `outcome` is an extensible enum, so a closed discriminated union would turn every future outcome into a breaking change.","additionalProperties":false,"required":["outcome"],"properties":{"outcome":{"$ref":"#/components/schemas/SuppressionBulkOutcome"},"suppression":{"$ref":"#/components/schemas/WhatsAppSuppression","description":"The created or already-existing suppression. Absent when the row errored."},"error":{"$ref":"#/components/schemas/BulkRowError","description":"Why this row errored. Absent for every other outcome."}}},"WhatsAppSuppressionBulkResult":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WhatsAppSuppressionBulkResultItem"},"description":"One entry per address in the request, in submission order."}}},"WhatsAppKeywordOperation":{"type":"string","minLength":1,"x-extensible-enum":["opt_in","opt_out"],"description":"What Bird does when an inbound message matches the rule.\n\n- `opt_out` records that the sender no longer consents to receive any messages from your\n  WhatsApp Business Account, including transactional ones. Typing the word is the person's\n  own statement, so it covers everything, unlike WhatsApp's built-in marketing opt-out\n  control, which stops marketing alone.\n- `opt_in` records that they consent again.\n\nA rule's operation is fixed once created, and a keyword belongs to exactly one operation, so\na keyword Bird ships for `opt_out` cannot be reused for `opt_in`.\n\nThis is an open enum. Accept unrecognized values: SMS already answers `help`, `info`, `confirm`\nand `custom`, and WhatsApp gains an operation without a new API version. Sending one Bird does\nnot answer yet is refused with `E15082`.\n","example":"opt_out"},"WhatsAppKeywordRuleScope":{"type":"string","minLength":1,"enum":["system","workspace"],"description":"Whether the rule is one Bird ships (`system`) or one your workspace created (`workspace`). Both kinds carry a `wkr_` ID and can be read; only a `workspace` rule can be changed or deleted. A `workspace` rule takes precedence over Bird's at the same grain, so it is how you replace a reply without losing the keywords Bird ships.\n","example":"workspace"},"WhatsAppKeywordRuleID":{"type":"string","minLength":1,"pattern":"^wkr_[0-9a-hjkmnp-tv-z]{26}$","example":"wkr_01krdgeqcxet5s7t44vh8rt9mg"},"WhatsAppKeywordRule":{"type":"object","additionalProperties":false,"readOnly":true,"required":["id","scope","operation","keywords","effective_keywords","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/WhatsAppKeywordRuleID"},"scope":{"$ref":"#/components/schemas/WhatsAppKeywordRuleScope"},"operation":{"$ref":"#/components/schemas/WhatsAppKeywordOperation"},"country":{"type":["string","null"],"minLength":2,"maxLength":2,"description":"The country the rule applies in, as an ISO 3166-1 alpha-2 code. It is the country of the person who messaged you, worked out from their phone number, not the country of the account they messaged. Null means the rule applies worldwide, which is what Bird's own rules do. A rule for a country outranks a worldwide rule for the people it covers.\n","example":"US"},"waba":{"type":["string","null"],"description":"The WhatsApp Business Account the rule is limited to, identified by its WhatsApp-issued account ID, or null when it covers every account in your workspace. Bird's own rules are always null.\n","example":"102290129340398"},"keywords":{"type":"array","description":"The keywords this rule adds. For one of Bird's own rules this is the full set Bird ships. For a rule you created it is only what you added on top: it never restates or removes Bird's keywords, so `effective_keywords` is what actually matches.\n","items":{"type":"string","minLength":1},"example":["no more texts","remove me"]},"effective_keywords":{"type":"array","description":"Every keyword that matches this rule: Bird's keywords for the same operation and country, plus the ones you added. This is what an inbound message is compared against, and the whole message has to equal one of them. Keywords Bird adds later join it without you changing anything.\nFor a rule of **yours** with no `country`, this list is not the whole set it matches: such a rule compares against Bird's keywords for the sender's country, which the list cannot show because it does not know who is writing, so it shows Bird's worldwide keywords instead. Which rule answers decides whether that matters. Yours with no `country` and no `waba` sits below Bird's own country rule, so a sender in a country Bird ships a rule for is answered by that rule and your reply is not used. Yours with a `waba` and no `country` sits above it, so those senders match that country's keywords and get your reply, which is more keywords than this list names. Set a `country` on your own rule to see and extend exactly the set those senders match. A `system` rule is unaffected: each matches only its own keywords, and the ladder checks Bird's country rules separately from its worldwide one.\n","items":{"type":"string","minLength":1},"example":["stop","unsubscribe","optout","no more texts","remove me"]},"reply":{"type":["string","null"],"minLength":1,"description":"The message sent back when one of the keywords matches, or null when no reply is sent. The reply goes out on the conversation the inbound message opened.\n","example":"You're off the list. ACME Courier won't message you again."},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"When the rule was created. On one of Bird's own rules this is when Bird last shipped a change to it.","example":"2026-09-15T10:04:00Z"},"updated_at":{"type":"string","format":"date-time","minLength":1,"description":"When the rule was last changed. On one of Bird's own rules this is when Bird last shipped a change to it.","example":"2026-09-15T10:04:00Z"}}},"WhatsAppKeywordRuleList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The keyword rules that apply to your workspace, Bird's own included. Ordered most specific first, so the first rule whose keywords match an inbound message is the one that runs. The set is small and returned in full; this list is not paginated.\n","items":{"$ref":"#/components/schemas/WhatsAppKeywordRule"}}}},"WhatsAppKeywordOperationWrite":{"type":"string","minLength":1,"enum":["opt_in","opt_out"],"x-enum-varnames":["WhatsAppKeywordOperationWriteOptIn","WhatsAppKeywordOperationWriteOptOut"],"description":"What Bird does when an inbound message matches the rule.\n\n- `opt_out` records that the sender no longer consents to receive any messages from your\n  WhatsApp Business Account, including transactional ones. Typing the word is the person's\n  own statement, so it covers everything, unlike WhatsApp's built-in marketing opt-out\n  control, which stops marketing alone.\n- `opt_in` records that they consent again.\n\nA rule's operation is fixed once created, and a keyword belongs to exactly one operation, so\na keyword Bird ships for `opt_out` cannot be reused for `opt_in`.\n\nClosed on the write side: an operation Bird does not answer is rejected here rather than\nstored as a rule that never fires. The read side is open, because Bird can gain an operation\nwithout a new API version.\n","example":"opt_out"},"WhatsAppKeywordRuleCreate":{"type":"object","additionalProperties":false,"required":["operation"],"properties":{"operation":{"$ref":"#/components/schemas/WhatsAppKeywordOperationWrite"},"country":{"type":"string","minLength":2,"maxLength":2,"description":"The country this rule applies in, as an ISO 3166-1 alpha-2 code. It matches the country of the person who messaged you, worked out from their phone number. Omit it to cover everyone, which is what Bird's own rules do.\n","example":"US"},"waba":{"type":"string","minLength":1,"description":"Limit the rule to one WhatsApp Business Account, identified by its WhatsApp-issued account ID or by the `waa_` ID Bird gives it. Either form resolves to the same account, and the rule stores and returns the WhatsApp-issued one. Omit it to cover every account in your workspace. The account must be one of yours.\n","example":"102290129340398"},"keywords":{"type":"array","description":"Extra keywords to match, on top of the ones Bird already ships for this operation. Omit to keep Bird's keywords and change only the reply, including keywords Bird adds later. You cannot remove one of Bird's keywords, and a keyword Bird has bound to the other operation cannot be reused here.\n","items":{"type":"string","minLength":1},"example":["no more texts","remove me"]},"reply":{"type":"string","minLength":1,"description":"The message to send back when a keyword matches. Omit it to send nothing.\n","example":"You're off the list. ACME Courier won't message you again."}}},"WhatsAppKeywordRuleUpdate":{"type":"object","additionalProperties":false,"description":"Changes the reply and the added keywords. What a rule applies to (its operation, country and WhatsApp Business Account) is fixed once created: those decide which inbound messages reach it, so changing one would make it a different rule. Delete it and create the one you want.\n","properties":{"keywords":{"type":"array","description":"Replaces the extra keywords this rule matches, on top of the ones Bird ships. Send an empty array to keep Bird's keywords only. Omit to leave the current ones unchanged.\n","items":{"type":"string","minLength":1},"example":["no more texts"]},"reply":{"type":["string","null"],"minLength":1,"description":"Replaces the message sent back when a keyword matches. Set it to null to send nothing. Omit to leave it unchanged.\n","example":"You're off the list. ACME Courier won't message you again."}}},"EmailInboxInsightsGroupBy":{"type":"string","minLength":1,"description":"The bucket size a series is grouped by. Day suits the product's charts; wider grains suit long ranges.\n","enum":["day","week","month"],"example":"day"},"EmailInboxInsightsCompare":{"type":"string","minLength":1,"description":"Set to `previous_period` to include the immediately preceding window of equal length in the same response, so deltas need no second request.\n","enum":["previous_period"],"example":"previous_period"},"EmailInboxInsightsWeightingSource":{"type":"string","minLength":1,"description":"Where the audience mix behind the placement weighting came from: `account` when it was configured for this account, `global` when a general default was used instead.\n","enum":["account","global"],"example":"account"},"EmailInboxInsightsWeighting":{"type":"object","additionalProperties":false,"description":"How the placement figures in this response were weighted, so a number is\nself-describing wherever it is quoted or screenshotted.\n\nPlacement rates are a weighted average of per-provider rates against an\naudience mix (the share of recipients expected at each mailbox provider)\nrather than a share of delivered volume.\n","required":["weight_set_id","source","basis"],"properties":{"weight_set_id":{"type":"string","minLength":1,"readOnly":true,"description":"The measurement's own identifier for the audience mix, carried through so a client can tell two weightings apart without comparing `basis` strings. No operation accepts it.\n","example":"12"},"source":{"oneOf":[{"$ref":"#/components/schemas/EmailInboxInsightsWeightingSource"},{"type":"null"}],"readOnly":true,"description":"Which audience mix the weighting used. Null when the measurement weighted these figures by a method this API does not model: the enum is closed so that a client can branch on it exhaustively, which means an unfamiliar method has to answer \"not one of these\" rather than be passed through. `basis` usually still describes the method in words when that happens.\n"},"basis":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The weighting method behind the rates, as the measurement names it. A slug rather than a sentence, so render it as a label and do not expect it to read as English. Null when the measurement did not state one, which pairs with `source`: both describe the method, so neither can claim to know it when the measurement was silent.\n","example":"weighted-mean-of-per-isp-rates"}}},"EmailInboxInsightsMeasurement":{"type":"object","additionalProperties":false,"description":"How the figures in this response were measured, so a number is self-describing in a screenshot or a bug report.\n","required":["sources"],"properties":{"sources":{"type":"array","readOnly":true,"description":"Identifiers of the measurement systems that contributed to these figures. The set grows as measurement coverage does, so treat the values as labels rather than a closed list.\n","items":{"type":"string","minLength":1,"x-extensible-enum":["panel","intelliseed_public","intelliseed_private","eds"]},"example":["panel","intelliseed_public"]},"weighting":{"$ref":"#/components/schemas/EmailInboxInsightsWeighting","readOnly":true,"description":"How the figures were weighted. Present on figures weighted against an audience mix, which is placement's method; measurements that weight nothing carry no weighting block.\n"}}},"EmailInboxInsightsFreshness":{"type":"object","additionalProperties":false,"description":"How current the figures are. Freshness differs per resource (authentication data can lag a day or more while blocklist lookups are near real time), so any \"as of\" label binds from this field, never from a fixed string.\n","required":["as_of","lag_hint"],"properties":{"as_of":{"type":["string","null"],"format":"date","readOnly":true,"description":"The most recent UTC day the figures include, or null for a live lookup that has no measurement window.\n","example":"2026-08-17"},"lag_hint":{"type":["string","null"],"readOnly":true,"description":"How far behind real time this resource usually runs. A lowercase\nidentifier rather than a display label, so pick your own wording for it,\nand treat the set as open: the measurement names a hint per resource and\ncan add one without notice.\n\nNull when the measurement reports no hint, which several resources do:\nshow the figures without an age rather than inventing one.\n","x-extensible-enum":["daily","nightly","near_real_time"],"example":"daily"}}},"EmailInboxInsightsEnvelopeBase":{"type":"object","description":"The meta every Inbox Insights resource carries, whatever it measures, so one client adapter serves them all.\n","required":["resource","domain","generated_at","freshness"],"properties":{"resource":{"type":"string","minLength":1,"readOnly":true,"description":"Which resource this response is, echoed for self-description.","example":"placement"},"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The sending domain the figures describe.","example":"mail.acme.com"},"measurement":{"$ref":"#/components/schemas/EmailInboxInsightsMeasurement","readOnly":true,"description":"How the figures were measured. Present only where a figure was weighted or drawn from a named set of sources, which today means placement and the industry benchmark. Absent on the reputation resources and on a live lookup, neither of which weights anything.\n"},"generated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the measurement service computed these figures.","example":"2026-08-18T09:34:00Z"},"freshness":{"$ref":"#/components/schemas/EmailInboxInsightsFreshness","readOnly":true},"cached_at":{"type":"string","format":"date-time","readOnly":true,"description":"Present when the response was served from a short-lived copy rather than fetched for this request: when that copy was fetched.\n","example":"2026-08-18T09:40:02Z"}}},"EmailInboxInsightsWindow":{"type":"object","additionalProperties":false,"description":"The period every figure in the response covers: whole UTC calendar days,\ninclusive on both ends. The same window convention the email statistics\nendpoints use, so figures from the two sources describe the same days and\ncan be combined without adjustment.\n","required":["start","end"],"properties":{"start":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"First UTC day of the period, inclusive.","example":"2026-08-12"},"end":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"Last UTC day of the period, inclusive.","example":"2026-08-18"},"group_by":{"$ref":"#/components/schemas/EmailInboxInsightsGroupBy","readOnly":true,"description":"The bucket size any series in this response is grouped by. Absent on resources with no series."}}},"EmailInboxInsightsComparedTo":{"type":"object","additionalProperties":false,"description":"The prior equal-length period the delta figures compare against. Present only when the request asked for a comparison.\n","required":["start","end"],"properties":{"start":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"First UTC day of the prior period, inclusive.","example":"2026-06-19"},"end":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"Last UTC day of the prior period, inclusive.","example":"2026-07-18"}}},"EmailInboxInsightsEnvelope":{"type":"object","description":"The meta a windowed Inbox Insights resource carries: the common fields plus the period the figures cover and how they were measured.\n","allOf":[{"$ref":"#/components/schemas/EmailInboxInsightsEnvelopeBase"},{"type":"object","required":["window"],"properties":{"window":{"$ref":"#/components/schemas/EmailInboxInsightsWindow"},"compared_to":{"$ref":"#/components/schemas/EmailInboxInsightsComparedTo"}}}]},"EmailInboxInsightsPlacementCounts":{"type":"object","additionalProperties":false,"description":"Raw measured placements behind a set of rates, before any weighting. A measured placement is one message whose mailbox destination the measurement observed.\n","required":["inbox","spam","missing","measured"],"properties":{"inbox":{"type":"integer","minimum":0,"readOnly":true,"description":"Measured placements observed in the inbox.","example":418211},"spam":{"type":"integer","minimum":0,"readOnly":true,"description":"Measured placements observed in spam.","example":60233},"missing":{"type":"integer","minimum":0,"readOnly":true,"description":"Measured sends that arrived in neither folder.","example":0},"measured":{"type":"integer","minimum":0,"readOnly":true,"description":"Total measured placements the rates were computed over.","example":478444}}},"EmailInboxInsightsPlacementDeltaPts":{"type":"object","additionalProperties":false,"readOnly":true,"description":"How the domain-wide rates moved against the prior period, in percentage points. Present only when the request asked for a comparison and the prior period had data; absence means no comparable prior data, never zero change.\n","required":["inbox","spam"],"properties":{"inbox":{"type":"number","readOnly":true,"description":"Inbox-rate movement in percentage points; negative means it fell.","example":-3.1},"spam":{"type":"number","readOnly":true,"description":"Spam-rate movement in percentage points.","example":3.1}}},"EmailInboxInsightsSectionStatus":{"type":"string","minLength":1,"description":"Whether a section of the response carries figures, and when it does not, why.\n\n`ok` means the section is populated. `no_data` means the measurement ran and\nobserved nothing to report for this domain in the period. `not_configured`\nmeans the section needs a setup step that has not been completed yet, such as\nconnecting Google Postmaster Tools; treat it as an invitation to finish\nsetup rather than a fault. `unavailable` means the figures could not be retrieved this time and\nthe same request may well succeed on a retry; the rest of the response is\nunaffected. `not_applicable` means the section is meaningless for this domain\nin this period, so there is nothing to show or fix.\n\nA successful response never implies every section is populated; read each\nsection's status rather than assuming figures are present.\n","enum":["ok","no_data","not_configured","unavailable","not_applicable"],"example":"ok"},"EmailInboxInsightsPlacementSummary":{"type":"object","additionalProperties":false,"description":"The domain-wide placement figures for the period.\n\nThese rates are weighted against the audience mix in `measurement.weighting`,\nso they can legitimately differ from any single provider row, which has no\nmix to weight. Rates are percentages of measured placements, never of\ndelivered volume.\n","required":["inbox_rate_percent","spam_rate_percent","missing_rate_percent","raw_counts","read_rate_percent","status"],"properties":{"inbox_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Estimated share of measured placements that landed in the inbox, as a percentage.","example":87.4},"spam_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Estimated share of measured placements that landed in spam, as a percentage.","example":12.6},"missing_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Estimated share of measured sends that arrived in neither folder, as a percentage.","example":0},"raw_counts":{"oneOf":[{"$ref":"#/components/schemas/EmailInboxInsightsPlacementCounts"},{"type":"null"}],"readOnly":true,"description":"The measured placements the rates above were computed over, or null when the summary has none: a period with no measured mail reports null here rather than four zeros, because a zero count is a real measurement and would read as \"we looked and found nothing\" for a domain nothing looked at. Read `status` alongside it.\n"},"read_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Estimated share of inbox-placed mail that was read, as a percentage, measured by the panel's dwell time. This is not an open rate; the two count different things and are not interchangeable.\n","example":21.4},"delta_pts":{"$ref":"#/components/schemas/EmailInboxInsightsPlacementDeltaPts","readOnly":true},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsMailboxProvider":{"type":"string","minLength":1,"readOnly":true,"description":"A mailbox provider, as the measurement identifies it. A lowercase identifier rather than\na display name, so pick your own label for it, and treat the set as open: this is a long\ntail rather than a handful of household names, and some entries are domains\n(`fastmail.com`, `seznam.cz`) rather than brands.\n\nThe measurement places mail into its own seed lists, so its buckets are not the ones the\n[mailbox-provider stats breakdown](/docs/api/reference/get-email-stats-by-mailbox-provider)\nreports: Microsoft's properties appear here as `hotmail` rather than `microsoft`, and\n`apple` appears here where the Competitive Insights panel has no measurement for it at\nall. None of the three is a joinable dimension against the others.\n","example":"gmail"},"EmailInboxInsightsPlacementProvider":{"type":"object","additionalProperties":false,"description":"One mailbox provider's placement for the period. Unlike the domain-wide summary, a single provider's rates are unweighted: there is no audience mix to weight within one provider.\n","required":["mailbox_provider","inbox_rate_percent","spam_rate_percent","raw_counts","read_rate_percent"],"properties":{"mailbox_provider":{"$ref":"#/components/schemas/EmailInboxInsightsMailboxProvider","readOnly":true,"description":"The provider whose placement this row describes."},"inbox_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this provider's measured placements that landed in the inbox, as a percentage.","example":89.2},"spam_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this provider's measured placements that landed in spam, as a percentage.","example":10.8},"raw_counts":{"$ref":"#/components/schemas/EmailInboxInsightsPlacementCounts","readOnly":true},"delta_pts":{"type":"number","readOnly":true,"description":"Inbox-rate movement against the prior period, in percentage points. Present only when the request asked for a comparison and the prior period had data for this provider; absence is not zero change.\n","example":0.4},"read_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this provider's inbox-placed mail that was read, as a percentage.","example":24.3}}},"EmailInboxInsightsPlacementProviders":{"type":"object","additionalProperties":false,"description":"The per-provider placement table.","required":["items","status"],"properties":{"items":{"type":"array","readOnly":true,"description":"One row per mailbox provider the measurement observed for this domain in the period.","items":{"$ref":"#/components/schemas/EmailInboxInsightsPlacementProvider"}},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsPlacementSeriesPoint":{"type":"object","additionalProperties":false,"description":"One bucket of the placement series.","required":["date","mailbox_provider","inbox_rate_percent","spam_rate_percent","inbox_raw_count","spam_raw_count"],"properties":{"date":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"First UTC day of the bucket.","example":"2026-07-19"},"mailbox_provider":{"oneOf":[{"$ref":"#/components/schemas/EmailInboxInsightsMailboxProvider"},{"type":"null"}],"readOnly":true,"description":"The provider this point describes, or null on the domain-wide line. Per-provider points appear only when the request named providers.\n","example":null},"inbox_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Inbox share of the bucket's measured placements, as a percentage.","example":90.6},"spam_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Spam share of the bucket's measured placements, as a percentage.","example":9.4},"inbox_raw_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Measured placements observed in the inbox in this bucket.","example":14201},"spam_raw_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Measured placements observed in spam in this bucket.","example":1473}}},"EmailInboxInsightsPlacementSeries":{"type":"object","additionalProperties":false,"description":"The placement time series, at the grain named in `window.group_by`.\n\nThe series is sparse: buckets with no measured placement are omitted rather\nthan returned as zeros, because an invented zero would be indistinguishable\nfrom a measured one. Index by date, never by position.\n","required":["items","status"],"properties":{"items":{"type":"array","readOnly":true,"description":"One point per bucket with measured placements. With providers named in the request, one point per bucket per provider.\n","items":{"$ref":"#/components/schemas/EmailInboxInsightsPlacementSeriesPoint"}},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsGmailTab":{"type":"string","minLength":1,"readOnly":true,"description":"A Gmail tab, as the measurement identifies it. A lowercase identifier rather than a\ndisplay name, so pick your own label for it, and treat the set as open: these are\nGmail's own tabs, and the measurement reports whichever one it saw.\n\n`none` is a value rather than an absence: Gmail delivered the mail under no tab at all,\nwhich is an ordinary outcome and not a gap in the measurement.\n","x-extensible-enum":["primary","promotions","updates","forums","social","none"],"example":"promotions"},"EmailInboxInsightsGmailTabCategory":{"type":"object","additionalProperties":false,"description":"How the domain's Gmail-placed mail split across one Gmail tab.","required":["category","overall_percent","inbox_percent","spam_percent"],"properties":{"category":{"$ref":"#/components/schemas/EmailInboxInsightsGmailTab","readOnly":true,"description":"The tab this row describes."},"overall_percent":{"type":["number","null"],"readOnly":true,"description":"Share of the domain's Gmail-placed mail that landed under this tab, as a percentage.","example":34},"inbox_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this tab's mail that placed in the inbox, as a percentage.","example":94},"spam_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this tab's mail that placed in spam, as a percentage.","example":6}}},"EmailInboxInsightsGmailTabs":{"type":"object","additionalProperties":false,"description":"Where the domain's Gmail-placed mail landed across Gmail's tabs. The status is `not_applicable` when the domain had no Gmail placement in the period; hide the section rather than showing an empty split.\n","required":["categories","status"],"properties":{"categories":{"type":"array","readOnly":true,"description":"One entry per Gmail tab that received mail.","items":{"$ref":"#/components/schemas/EmailInboxInsightsGmailTabCategory"}},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsPlacementIpDetail":{"type":"object","additionalProperties":false,"description":"One sending IP's placement and authentication pass rates for the period.","required":["ip","inbox_rate_percent","raw_counts","spf_pass_rate_percent","dkim_pass_rate_percent"],"properties":{"ip":{"type":"string","minLength":1,"readOnly":true,"description":"The sending IP address.","example":"147.253.40.16"},"inbox_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this IP's measured placements that landed in the inbox, as a percentage.","example":89.9},"raw_counts":{"$ref":"#/components/schemas/EmailInboxInsightsPlacementCounts","readOnly":true},"spf_pass_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this IP's measured mail that passed SPF, as a percentage.","example":99.8},"dkim_pass_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this IP's measured mail that passed DKIM, as a percentage.","example":99.9}}},"EmailInboxInsightsPlacementIpDetails":{"type":"object","additionalProperties":false,"description":"Per-IP placement detail for the domain's sending infrastructure. Returned only when the request asked for IP detail.\n","required":["items","status"],"properties":{"items":{"type":"array","readOnly":true,"description":"One row per sending IP the measurement observed for this domain in the period.","items":{"$ref":"#/components/schemas/EmailInboxInsightsPlacementIpDetail"}},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsPlacement":{"description":"Where a sending domain's measured mail landed over the period: the\ndomain-wide summary, the per-provider table, the time series, the Gmail tab\nsplit, and optionally per-IP detail.\n\nPlacement figures are estimates from a measurement panel of real mailboxes,\nand every rate is a percentage of measured placements, never of delivered\nvolume. Each section carries its own status; a successful response never\nimplies every section is populated.\n","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/EmailInboxInsightsEnvelope"},{"type":"object","required":["summary","providers","series","gmail_tabs","measurement"],"properties":{"summary":{"$ref":"#/components/schemas/EmailInboxInsightsPlacementSummary"},"providers":{"$ref":"#/components/schemas/EmailInboxInsightsPlacementProviders"},"series":{"$ref":"#/components/schemas/EmailInboxInsightsPlacementSeries"},"gmail_tabs":{"$ref":"#/components/schemas/EmailInboxInsightsGmailTabs"},"ip_details":{"$ref":"#/components/schemas/EmailInboxInsightsPlacementIpDetails"}}}]},"EmailInboxInsightsAuthPassRate":{"type":"object","additionalProperties":false,"description":"One authentication check's pass rate over the period.","required":["pass_rate_percent","source","status"],"properties":{"pass_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of the domain's measured mail that passed this check, as a percentage.","example":99.8},"delta_pts":{"type":"number","readOnly":true,"description":"How the pass rate moved against the prior period, in percentage points. Present only when the request asked for a comparison and the prior period had data; absence is not zero change.\n","example":0.1},"source":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Where this figure comes from. `dmarc_rua` is authoritative aggregate\nreporting and covers every sender of the domain, forwarders included;\n`google_postmaster` is a fallback covering only mail Google received. It\ncan differ from the source of the DMARC figures, so surface it per check\nrather than once per response.\n\nNull on a check that reports no figure at all, which is what a\n`not_configured` status means: there is no measurement, so there is no\nsource to name.\n","x-extensible-enum":["dmarc_rua","google_postmaster"],"example":"dmarc_rua"},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsDmarcPolicy":{"type":"string","minLength":1,"description":"The DMARC policy published in the domain's DNS record: what receivers are asked to do with mail that fails DMARC.\n","enum":["none","quarantine","reject"],"example":"quarantine"},"EmailInboxInsightsDmarcReadinessReason":{"type":"string","minLength":1,"description":"Why the domain is not yet ready to move its DMARC policy to `reject`. `source_below_threshold` means at least one legitimate sender is not authenticating well enough yet; `data_too_stale` means the reporting is too old to judge; `no_rua_data` means no aggregate reports have arrived at all; `no_policy` means the domain publishes no DMARC record to tighten. The reporting decides this set and can add to it, so show an unrecognised value rather than treating it as no reason at all.\n","x-extensible-enum":["source_below_threshold","data_too_stale","no_rua_data","no_policy"],"example":"source_below_threshold"},"EmailInboxInsightsDmarc":{"type":"object","additionalProperties":false,"description":"The domain's DMARC standing over the period.","required":["aligned_rate_percent","policy","ready_for_reject","readiness_reasons","source","status"],"properties":{"aligned_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of the domain's measured mail that passed DMARC alignment, as a percentage.","example":98.6},"policy":{"oneOf":[{"$ref":"#/components/schemas/EmailInboxInsightsDmarcPolicy"},{"type":"null"}],"readOnly":true,"description":"The policy published in the domain's DNS record, or null when the domain publishes no DMARC record at all. Null is not `none`: `none` is a policy, asking receivers to take no action while the domain monitors its reporting, and a domain that has one is already set up. A null asks for a record to be published, which is a different first step.\n"},"ready_for_reject":{"type":["boolean","null"],"readOnly":true,"description":"Whether the domain's authentication is consistent enough to move the policy to `reject` without losing legitimate mail. Deliberately conservative: false whenever the data is insufficient to be sure. Null when the measurement reached no verdict, which is what a `status` other than `ok` means here: false would read as a considered \"not yet\" rather than as no assessment having been made.\n","example":false},"readiness_reasons":{"type":["array","null"],"readOnly":true,"description":"Why `ready_for_reject` is false, so the answer is actionable rather than a bare refusal. Empty when nothing is holding the domain back, and null when readiness was not assessed, which pairs with `ready_for_reject`: an empty list alongside a null verdict would say the opposite of what was measured. Render these rather than a plain \"not ready\": the fix differs per reason, and a domain held back only by stale reporting needs no configuration change at all.\n","items":{"$ref":"#/components/schemas/EmailInboxInsightsDmarcReadinessReason"}},"delta_pts":{"type":"number","readOnly":true,"description":"How the aligned rate moved against the prior period, in percentage points. Present only when the request asked for a comparison and the prior period had data; absence is not zero change.\n","example":-0.2},"source":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Where the DMARC figures come from. `dmarc_rua` is authoritative\naggregate reporting and covers every sender of the domain, forwarders\nincluded; `google_postmaster` is a fallback covering only mail Google\nreceived. The two are not equivalent, so surface which one is shown.\n\nNull when the section reports no figures, which is what a\n`not_configured` status means for a domain with no aggregate reporting\nand no Postmaster connection.\n","x-extensible-enum":["dmarc_rua","google_postmaster"],"example":"dmarc_rua"},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsDmarcVerdict":{"type":"string","minLength":1,"description":"How a sending source's mail authenticates against the domain's DMARC policy. `aligned` passes with both SPF and DKIM aligned; `dkim_only` and `spf_only` pass on one mechanism; `fails_policy` passes neither. The reporting decides this set and can add to it, so treat an unrecognised value as a label to show rather than a case to exhaust. A source whose verdict is new still belongs in the table.\n","x-extensible-enum":["aligned","dkim_only","spf_only","fails_policy"],"example":"aligned"},"EmailInboxInsightsAuthSource":{"type":"object","additionalProperties":false,"description":"One system observed sending as this domain, with how its mail authenticates.","required":["name","category","volume","spf_aligned_rate_percent","dkim_aligned_rate_percent","dmarc_pass_rate_percent","verdict","qualifies_for_readiness"],"properties":{"name":{"type":"string","minLength":1,"readOnly":true,"description":"The sending source as the reporting identifies it. Not a fixed list: unidentified senders, mostly forwarders, appear as a real category.\n","example":"Bird (mail.acme.com)"},"category":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"A coarse classification of the source. The set can grow; treat values as labels. Null when the measurement did not classify this sender.\n","x-extensible-enum":["esp","unknown"],"example":"esp"},"volume":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Messages the reporting attributes to this source over the period.","example":4820000},"spf_aligned_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this source's mail that passed SPF with alignment, as a percentage.","example":99.8},"dkim_aligned_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this source's mail that passed DKIM with alignment, as a percentage.","example":99.9},"dmarc_pass_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this source's mail that passed DMARC, as a percentage.","example":99.9},"verdict":{"$ref":"#/components/schemas/EmailInboxInsightsDmarcVerdict","readOnly":true},"qualifies_for_readiness":{"type":"boolean","readOnly":true,"description":"Whether this source counts toward the reject recommendation. A source that does not is excluded from that judgement, which is what lets this table explain a conservative recommendation instead of contradicting it.\n","example":true}}},"EmailInboxInsightsAuthSources":{"type":"object","additionalProperties":false,"description":"Every system observed sending as this domain, with how each authenticates. This is the table that shows who else sends under the domain's name.\n","required":["items","latest_data_date","status"],"properties":{"items":{"type":"array","readOnly":true,"description":"One row per observed sending source.","items":{"$ref":"#/components/schemas/EmailInboxInsightsAuthSource"}},"latest_data_date":{"type":["string","null"],"format":"date","readOnly":true,"description":"The most recent UTC day the source reporting includes. Aggregate DMARC reports arrive on reporters' own schedules, routinely a day or more behind, so the newest days look sparse; label from this date rather than treating the dip as a regression.\n","example":"2026-08-15"},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsAuthentication":{"description":"Whether the domain's mail authenticates, and who sends as the domain: SPF\nand DKIM pass rates, the DMARC standing with its published policy, and the\nper-source table that shows every system observed sending under the\ndomain's name.\n\nWithout a completed Google Postmaster connection and without aggregate\nDMARC reporting, sections report `not_configured`: an invitation to finish\nsetup rather than a fault. Each section carries its own status.\n","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/EmailInboxInsightsEnvelope"},{"type":"object","required":["spf","dkim","dmarc","sources"],"properties":{"spf":{"$ref":"#/components/schemas/EmailInboxInsightsAuthPassRate","description":"The domain's SPF pass rate."},"dkim":{"$ref":"#/components/schemas/EmailInboxInsightsAuthPassRate","description":"The domain's DKIM pass rate."},"dmarc":{"$ref":"#/components/schemas/EmailInboxInsightsDmarc"},"sources":{"$ref":"#/components/schemas/EmailInboxInsightsAuthSources"}}}]},"EmailInboxInsightsComplaintPeak":{"type":"object","additionalProperties":false,"description":"The worst day for complaints in the period.","required":["date","value_percent"],"properties":{"date":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"The UTC day the highest rate fell on.","example":"2026-08-02"},"value_percent":{"type":"number","readOnly":true,"description":"The rate on that day, as a percentage.","example":0.34}}},"EmailInboxInsightsComplaintRate":{"type":"object","additionalProperties":false,"description":"The rate at which the domain's mail is reported as spam, as Google Postmaster measures it.","required":["gmail_postmaster_spam_rate_percent","status"],"properties":{"gmail_postmaster_spam_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of the domain's Gmail-received mail that recipients reported as spam, as a percentage, from Google Postmaster.\n","example":0.11},"delta_pts":{"type":"number","readOnly":true,"description":"How the rate moved against the prior period, in percentage points. Present only when the request asked for a comparison and the prior period had data; absence is not zero change.\n","example":0.03},"peak":{"$ref":"#/components/schemas/EmailInboxInsightsComplaintPeak","readOnly":true,"description":"The worst day in the period, so a spike can be named without scanning the series. Absent when there is no rate to peak.\n"},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsComplaintSeriesPoint":{"type":"object","additionalProperties":false,"description":"One bucket of the complaint-rate series.","required":["date","gmail_postmaster_spam_rate_percent"],"properties":{"date":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"First UTC day of the bucket.","example":"2026-07-19"},"gmail_postmaster_spam_rate_percent":{"type":["number","null"],"readOnly":true,"description":"The bucket's Google Postmaster spam rate, as a percentage.","example":0.08}}},"EmailInboxInsightsComplaintSeries":{"type":"object","additionalProperties":false,"description":"The complaint-rate series, at the grain named in `window.group_by`. Index by date, never by position.\n","required":["items","status"],"properties":{"items":{"type":"array","readOnly":true,"description":"One point per bucket.","items":{"$ref":"#/components/schemas/EmailInboxInsightsComplaintSeriesPoint"}},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsComplaints":{"description":"How often the domain's mail is reported as spam, as Google Postmaster\nmeasures it. This is Google's number for Gmail-received mail only; the\nfeedback-loop complaint rate for all providers is a Bird-measured figure\nserved by the email statistics endpoints, and the two are different\nmeasurements of different mail.\n\nFor a domain without a completed Google Postmaster connection every section\nreports `not_configured`: an invitation to finish setup rather than a fault.\n","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/EmailInboxInsightsEnvelope"},{"type":"object","required":["rate","series"],"properties":{"rate":{"$ref":"#/components/schemas/EmailInboxInsightsComplaintRate"},"series":{"$ref":"#/components/schemas/EmailInboxInsightsComplaintSeries"}}}]},"EmailInboxInsightsTrapType":{"type":"string","minLength":1,"description":"What kind of spam trap was hit. `pristine` addresses were never used by a real person and never subscribed to anything, so a hit means the address was harvested or guessed rather than collected. `recycled` addresses belonged to a real person once and were retired, so hits point at stale list data. `typo` addresses catch misspellings of real domains, `parked` addresses sit on domains that are registered but not used for real mail, and `mixed` covers hits the trap network reports without a single kind. The trap network decides this set and can add to it, so treat an unrecognised value as a label to show rather than a case to exhaust. A hit whose kind is new is still a hit worth acting on.\n","x-extensible-enum":["pristine","recycled","typo","parked","mixed"],"example":"recycled"},"EmailInboxInsightsSpamTrapTypeCount":{"type":"object","additionalProperties":false,"description":"Trap hits of one kind.","required":["type","hits"],"properties":{"type":{"$ref":"#/components/schemas/EmailInboxInsightsTrapType","readOnly":true},"hits":{"type":"integer","minimum":0,"readOnly":true,"description":"Hits of this kind over the period. A zero is a measured zero, not missing data: no pristine hits is a genuinely good result rather than an empty state.\n","example":3}}},"EmailInboxInsightsTrapSource":{"type":"string","minLength":1,"description":"The trap network that observed a hit. The set grows as coverage does, so treat the values as labels rather than a closed list.\n","x-extensible-enum":["cloudmark","abusix"],"example":"cloudmark"},"EmailInboxInsightsSpamTrapSourceCount":{"type":"object","additionalProperties":false,"description":"Trap hits attributed to one trap network.","required":["source","hits"],"properties":{"source":{"$ref":"#/components/schemas/EmailInboxInsightsTrapSource","readOnly":true},"hits":{"type":"integer","minimum":0,"readOnly":true,"description":"Hits this network observed over the period.","example":2}}},"EmailInboxInsightsSpamTrapHit":{"type":"object","additionalProperties":false,"description":"One trap address this domain's mail reached, with enough detail to trace where the address came from. A row can represent several hits on the same trap, so read `hit_count` rather than counting rows.\n","required":["first_seen","last_seen","ip_address","source","type","trap_age_days"],"properties":{"first_seen":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the trap network first observed mail from this domain at this trap.","example":"2026-08-14T06:21:00Z"},"last_seen":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The most recent sighting, or null when the trap was seen only once. On a row with several hits this is the far end of the period they span.\n","example":"2026-08-16T11:04:00Z"},"ip_address":{"type":"string","minLength":1,"readOnly":true,"description":"The sending IP the message came from.","example":"147.253.40.16"},"source":{"$ref":"#/components/schemas/EmailInboxInsightsTrapSource","readOnly":true},"type":{"$ref":"#/components/schemas/EmailInboxInsightsTrapType","readOnly":true},"hit_count":{"type":"integer","minimum":1,"readOnly":true,"description":"How many times this trap was hit over the period, so rows do not sum to `total` on their own: one repeatedly hit trap is one row. Absent when the trap network does not break the count out, which is not the same as one hit. A row exists because the trap was reached at least once either way.\n","example":2},"trap_age_days":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"How long the trap address has been a trap, in days, or null when the network does not say. A high age on a recycled trap suggests the address has been dead in the list for a long time.\n","example":430}}},"EmailInboxInsightsSpamTrapHits":{"type":"object","additionalProperties":false,"description":"The individual trap hits behind the totals. A sample rather than a guaranteed complete list, and its rows do not count hits: one row is one trap address, carrying a `hit_count` for how many times that address was reached. Neither the number of rows nor the sum of `hit_count` reconstructs `total`, because that field is absent wherever the trap network does not break the figure out. Read `truncated_types` for what the measurement capped rather than inferring completeness by comparing counts.\n","required":["items","truncated_types","status"],"properties":{"items":{"type":"array","readOnly":true,"description":"One entry per trap reached, newest first.","items":{"$ref":"#/components/schemas/EmailInboxInsightsSpamTrapHit"}},"truncated_types":{"type":"array","readOnly":true,"description":"Trap kinds whose hits the measurement capped, so the rows shown for them are incomplete by design rather than by chance. Typo-trap hits, for instance, only ever cover the last seven days. An empty array means nothing was capped.\n","items":{"$ref":"#/components/schemas/EmailInboxInsightsTrapType"}},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsSpamTraps":{"description":"Whether the domain's mail is reaching spam traps: addresses that exist only\nto catch senders mailing lists they should not be mailing.\n\nZero hits is a measured zero and a good result, so the totals read as real\nfigures rather than as an empty state. The kind of trap matters more than\nthe count: pristine hits point at harvested or guessed addresses, while\nrecycled hits point at stale list data.\n","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/EmailInboxInsightsEnvelope"},{"type":"object","required":["total","by_type","by_source","hit_rows"],"properties":{"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Trap hits observed over the period, across every trap network. The authoritative count: `hit_rows` holds a sample of the rows behind it.\n","example":3},"delta":{"type":"integer","readOnly":true,"description":"How the hit count moved against the prior period, as a change in the number of hits rather than in percentage points. Negative is an improvement. Present only when the request asked for a comparison and the prior period had data; absence is not zero change.\n","example":-2},"by_type":{"type":"array","readOnly":true,"description":"Hits split by kind, one entry per kind the trap network reported. Read counts from here rather than assuming a fixed set of kinds: the set can grow, and an entry that is absent was not reported rather than being a measured zero. These sum to `total`.\n","items":{"$ref":"#/components/schemas/EmailInboxInsightsSpamTrapTypeCount"}},"by_source":{"type":"array","readOnly":true,"description":"Hits split by the trap network that observed them.","items":{"$ref":"#/components/schemas/EmailInboxInsightsSpamTrapSourceCount"}},"hit_rows":{"$ref":"#/components/schemas/EmailInboxInsightsSpamTrapHits","readOnly":true}}}]},"EmailInboxInsightsBlocklistListing":{"type":"object","additionalProperties":false,"description":"One listing of a target on one blocklist.","required":["provider","is_active","reason_code","reason","first_detected","last_detected"],"properties":{"is_active":{"type":"boolean","readOnly":true,"description":"Whether this listing is in force now. A false entry is history: it shows the target was listed and has since cleared, which is why the target's `is_listed` can be false while listings are present.\n","example":false},"reason_code":{"type":["string","null"],"readOnly":true,"description":"The provider's own short code for the listing reason, or null when it gives none. Stable where the prose in `reason` is not, so branch on this and display that.\n","example":"CSS"},"provider":{"type":"string","minLength":1,"readOnly":true,"description":"The blocklist that carries the listing. Providers publishing several lists are reported per list rather than under one combined name, because what a listing means and how it is cleared differ per list.\n","example":"Spamhaus CSS"},"reason":{"type":["string","null"],"readOnly":true,"description":"The reason the provider gives for the listing, or null when it publishes none.","example":"Automated listing of a suspected snowshoe range"},"first_detected":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this listing was first observed.","example":"2026-07-31T00:00:00Z"},"last_detected":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this listing was most recently observed, or null while the listing is still in force. A provider records a last sighting only once one exists, so a null here reads as \"still listed\" rather than \"never seen\".\n","example":"2026-08-04T00:00:00Z"}}},"EmailInboxInsightsBlocklistTarget":{"type":"object","additionalProperties":false,"description":"One target lookup result, its current status, and the listings seen against it.\n\nRead `status` before `is_listed`. Each target is looked up independently and\nany one of them can fail while the rest succeed, so a target whose status is\nnot `ok` was not checked and `is_listed: false` on it means nothing. Rendering\nthat as \"clear\" is the one outcome this resource must never produce.\n","required":["target","target_type","is_listed","status","checked_at","listings"],"properties":{"target":{"type":"string","minLength":1,"readOnly":true,"description":"The sending IP or domain selected for lookup.","example":"147.253.40.18"},"target_type":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Whether this target is an IP address or a hostname. Null when the measurement did not report a kind for it, which is possible on a target whose check did not complete.\n","x-extensible-enum":["ip","domain"],"example":"ip"},"is_listed":{"type":"boolean","readOnly":true,"description":"Whether the target is on at least one blocklist right now. Meaningful only when `status` is `ok`: on any other status this target was not checked, so the value carries no finding either way.\n","example":false},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true,"description":"Whether this target was actually checked. `unavailable` means the lookup failed or timed out for this target while others may have succeeded, so the honest rendering is \"could not check\" rather than a result.\n"},"checked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this target was looked up, or null when it was not. Per target rather than per response, because each is a separate live lookup.\n","example":"2026-08-20T09:12:04Z"},"listings":{"type":"array","readOnly":true,"description":"Listings seen against this target, including ones that have since cleared, so a recent history is visible even when nothing is active. Read each listing's `is_active` rather than assuming every entry is current.\n","items":{"$ref":"#/components/schemas/EmailInboxInsightsBlocklistListing"}}}},"EmailInboxInsightsBlocklists":{"description":"Whether the domain's sending infrastructure is on any blocklist, checked\nwhen the request is made.\n\nThis is a live lookup rather than a measurement over a period, so it carries\nno window: `freshness.as_of` is null and only the lag hint applies.\n\nInspect each returned target's `status` before `is_listed`. A target whose\nlookup did not complete can report `is_listed: false`; that value carries no\nfinding. Partial failures are represented by the individual target statuses.\n\n`active_count` is null when the lookup service supplies no count. Zero reports\nno active target listings, but does not establish coverage: the response can\ncontain an empty `targets` array. Use the returned targets and their statuses\nto determine which addresses were checked. A failed request provides no\nlookup result.\n","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/EmailInboxInsightsEnvelopeBase"},{"type":"object","required":["active_count","targets"],"properties":{"active_count":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Number of successfully checked targets reported with an active listing. A target on three blocklists counts once. Null when the lookup service supplies no count; do not treat null as zero. Zero does not establish that the domain or its IPs were checked. Inspect `targets` and each target's `status` for lookup coverage, including partial failures.\n","example":0},"targets":{"type":"array","readOnly":true,"description":"Returned sending IP or domain lookup results, including failed lookups. An empty array does not establish that the domain or its IPs are clear.","items":{"$ref":"#/components/schemas/EmailInboxInsightsBlocklistTarget"}}}}]},"EmailInboxInsightsIndustry":{"type":"object","additionalProperties":false,"description":"The industry a sending domain was classified into.","required":["id","name"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"The measurement's own identifier for this industry, carried through so a client can tell two cohorts apart without comparing labels. No operation accepts it.\n","example":"44"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the industry. The classification is broad, so bind this label rather than assuming a finer category exists.\n","example":"Apparel"}}},"EmailInboxInsightsIndustryBenchmark":{"description":"How senders in a domain's industry place, as a median across the industry's\nmeasured senders.\n\nThe benchmark describes the industry, not the domain, so it carries no\ncomparison of its own: compute that against the domain's own placement rate.\nIts weighting is a general default rather than any one account's audience\nmix, which is a deliberate asymmetry with the placement figure it is\ncompared against.\n\nThe status is `no_data` when too few measured senders share the industry for\na median to be meaningful, when the domain's industry is not classified, or\nbefore the industry figures have been computed. Handle that state from the\nstart: it is the normal state for a young industry cohort rather than an\nedge case.\n","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/EmailInboxInsightsEnvelopeBase"},{"type":"object","required":["industry","median_inbox_rate_percent","cohort_size","status"],"properties":{"industry":{"oneOf":[{"$ref":"#/components/schemas/EmailInboxInsightsIndustry"},{"type":"null"}],"readOnly":true,"description":"The cohort the median describes, or null when the domain is not classified into an industry. This description already names that as a `no_data` cause and a normal state for a young cohort, so it needs a representation: without one the only way to report an unclassified domain is a cohort with a blank name.\n"},"median_inbox_rate_percent":{"type":["number","null"],"readOnly":true,"description":"The industry's median inbox rate, as a percentage.","example":92.1},"window_days":{"type":"integer","minimum":1,"readOnly":true,"description":"How many days the cohort figure covers. Reported rather than assumed because the period is the one the nightly computation produced, not one the caller chose, so a label built from a requested window would be wrong. Absent when the computation does not report it, in which case a label must not name a period at all.\n","example":30},"cohort_size":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"How many measured senders the median was computed across.","example":214},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}}]},"EmailInboxInsightsSeedListType":{"type":"string","minLength":1,"description":"Which pool of seed addresses a test uses. Which pools an account can use depends on what has been provisioned for it, so read the usable set from the seed-test options rather than assuming these are the only values.\n","x-extensible-enum":["private","public","exclusive"],"example":"private"},"EmailInboxInsightsSeedEngagementProfile":{"type":"string","minLength":1,"description":"Which engagement behaviour the seed addresses simulate. `all` mixes engaged and dormant seeds, which is what makes an engagement split measurable; single-cohort profiles exist too, and the usable set comes from the seed-test options rather than from this list.\n","x-extensible-enum":["all","engaging","non_engaging"],"example":"all"},"EmailInboxInsightsSeedTestSummary":{"type":"object","description":"The fields every view of a seed test carries.\n\nThere is no status field. The measurement reports none for a test it has\nalready run, and nothing else on the surface stands in for one, so a test\nthat is still waiting for its send says so by having no subject and no\ntested-at date rather than by being labelled.\n","required":["test_id","subject","tested_at","list_type","engagement_profile","seed_count","inbox_rate_percent"],"properties":{"test_id":{"type":"string","minLength":1,"readOnly":true,"description":"The test's identifier. It is a string and needs to stay one: the values are long enough that any language storing every number as a floating point value will round them, and a rounded identifier matches no test.\n","example":"91901"},"subject":{"type":["string","null"],"readOnly":true,"description":"Subject line of the tested send, or null before the send goes out. With `tested_at`, this is what distinguishes a test that has run from one still waiting for its send.\n","example":"Fall Preview, first look"},"tested_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the tested send went out, or null while the test is still awaiting it.","example":"2026-08-13T09:12:00Z"},"list_type":{"oneOf":[{"$ref":"#/components/schemas/EmailInboxInsightsSeedListType"},{"type":"null"}],"readOnly":true,"description":"The seed pool the test used, or null on a test that predates the recording of it.\n"},"engagement_profile":{"oneOf":[{"$ref":"#/components/schemas/EmailInboxInsightsSeedEngagementProfile"},{"type":"null"}],"readOnly":true,"description":"The engagement behaviour the seeds simulated, or null on a test that predates the recording of it.\n"},"seed_count":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"How many seed addresses the test used.","example":212},"inbox_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of the test's seed addresses that received the message in the inbox, as a percentage. Null until results arrive.\n","example":89.2}}},"EmailInboxInsightsSeedTestRow":{"description":"One seed test as it appears in the history list.","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/EmailInboxInsightsSeedTestSummary"},{"type":"object","required":["domain","regions","delta_pts_vs_prior"],"properties":{"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The sending domain the test was run for.","example":"mail.acme.com"},"regions":{"type":["array","null"],"readOnly":true,"description":"The regions the test placed seeds in, as the seed-test options name them. Null on a test that predates registration, whose regions were never recorded (the same unknown `list_type` and `engagement_profile` carry), and not an empty list, which would claim a test placed seeds in no region at all.\n","items":{"type":"string","minLength":1},"example":["North America - US","Europe - UK"]},"delta_pts_vs_prior":{"type":["number","null"],"readOnly":true,"description":"How this test's inbox rate compares with the previous test for the same domain, in percentage points. Null when there is no earlier test to compare against.\n","example":-3.1}}}]},"EmailInboxInsightsSeedTestList":{"type":"object","additionalProperties":false,"description":"The domain's seed tests over the period, newest first, up to 100 of them. The list is capped rather than paged, so `truncated` says whether older tests in the period were left out.\n","required":["items","truncated","status"],"properties":{"items":{"type":"array","readOnly":true,"description":"One row per seed test, newest first.","items":{"$ref":"#/components/schemas/EmailInboxInsightsSeedTestRow"}},"truncated":{"type":"boolean","readOnly":true,"description":"True when the period holds more than the 100 tests returned, so the list is the newest of them rather than all of them. Request an earlier period to reach the tests left out.\n","example":false},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsGmailCategory":{"type":"object","additionalProperties":false,"description":"Which Gmail tab the test's Gmail seeds mostly landed under.","required":["category","share_percent"],"properties":{"category":{"$ref":"#/components/schemas/EmailInboxInsightsGmailTab","readOnly":true,"description":"The tab the test's Gmail seeds mostly landed under."},"share_percent":{"type":["number","null"],"readOnly":true,"description":"Share of the test's Gmail seeds that landed under this tab, as a percentage.","example":61}}},"EmailInboxInsightsSeedTestProvider":{"type":"object","additionalProperties":false,"description":"How one mailbox provider treated the test, with the seed counts behind the rates. Seed counts are small by nature, so a single seed moves a rate noticeably.\n","required":["mailbox_provider","inbox_rate_percent","spam_rate_percent","inbox_seeds","spam_seeds","total_seeds"],"properties":{"mailbox_provider":{"$ref":"#/components/schemas/EmailInboxInsightsMailboxProvider","readOnly":true,"description":"The provider whose treatment of the test this row describes."},"inbox_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this provider's seeds that received the message in the inbox, as a percentage.","example":95},"spam_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of this provider's seeds that received the message in spam, as a percentage.","example":5},"inbox_seeds":{"type":"integer","minimum":0,"readOnly":true,"description":"Seed addresses at this provider that received the message in the inbox.","example":61},"spam_seeds":{"type":"integer","minimum":0,"readOnly":true,"description":"Seed addresses at this provider that received the message in spam.","example":3},"total_seeds":{"type":"integer","minimum":0,"readOnly":true,"description":"Seed addresses at this provider included in the test.","example":64},"gmail_category":{"$ref":"#/components/schemas/EmailInboxInsightsGmailCategory","readOnly":true,"description":"Which Gmail tab the seeds landed under. Present only on the Gmail row, since no other provider sorts mail into tabs.\n"}}},"EmailInboxInsightsSeedTestProviders":{"type":"object","additionalProperties":false,"description":"The per-provider grid for one seed test.","required":["items","status"],"properties":{"items":{"type":"array","readOnly":true,"description":"One row per mailbox provider the test placed seeds at.","items":{"$ref":"#/components/schemas/EmailInboxInsightsSeedTestProvider"}},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsSeedTestAuth":{"type":"object","additionalProperties":false,"description":"How the tested send authenticated, measured on the seed mail itself rather than on reporting from receivers.\n","required":["spf_pass_rate_percent","dkim_pass_rate_percent","dmarc_aligned_rate_percent","status"],"properties":{"spf_pass_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of the test's seed mail that passed SPF, as a percentage.","example":100},"dkim_pass_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of the test's seed mail that passed DKIM, as a percentage.","example":100},"dmarc_aligned_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Share of the test's seed mail that passed DMARC alignment, as a percentage.","example":100},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsSeedEngagementSplitRow":{"type":"object","additionalProperties":false,"description":"How one provider treated engaged seeds against dormant ones. A gap means the provider is sorting the same mail differently by how the recipient behaves.\n","required":["mailbox_provider","engaged_inbox_rate_percent","dormant_inbox_rate_percent","gap_pts","engaged_seeds","dormant_seeds"],"properties":{"mailbox_provider":{"$ref":"#/components/schemas/EmailInboxInsightsMailboxProvider","readOnly":true,"description":"The provider whose treatment of engaged and dormant seeds this row compares."},"engaged_inbox_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Inbox rate across seeds simulating engaged recipients, as a percentage.","example":96},"dormant_inbox_rate_percent":{"type":["number","null"],"readOnly":true,"description":"Inbox rate across seeds simulating dormant recipients, as a percentage.","example":71},"gap_pts":{"type":["number","null"],"readOnly":true,"description":"Engaged inbox rate minus dormant inbox rate, in percentage points. The value can be negative, which means dormant seeds placed better, and is reported as measured rather than floored at zero.\n","example":25},"engaged_seeds":{"type":"integer","minimum":0,"readOnly":true,"description":"Seeds simulating engaged recipients at this provider.","example":32},"dormant_seeds":{"type":"integer","minimum":0,"readOnly":true,"description":"Seeds simulating dormant recipients at this provider.","example":32}}},"EmailInboxInsightsSeedEngagementSplit":{"type":"object","additionalProperties":false,"description":"Engaged against dormant placement, per provider. The status is `not_applicable` for a test run with a single-cohort engagement profile, where there is no second group to compare: hide the comparison rather than showing a zero gap.\n","required":["items","status"],"properties":{"items":{"type":"array","readOnly":true,"description":"One row per provider where both cohorts placed seeds.","items":{"$ref":"#/components/schemas/EmailInboxInsightsSeedEngagementSplitRow"}},"status":{"$ref":"#/components/schemas/EmailInboxInsightsSectionStatus","readOnly":true}}},"EmailInboxInsightsSeedTestDetail":{"type":"object","description":"One seed test with its full results.","allOf":[{"$ref":"#/components/schemas/EmailInboxInsightsSeedTestSummary"},{"type":"object","required":["providers","auth","engagement_split"],"properties":{"providers":{"$ref":"#/components/schemas/EmailInboxInsightsSeedTestProviders"},"auth":{"$ref":"#/components/schemas/EmailInboxInsightsSeedTestAuth"},"engagement_split":{"$ref":"#/components/schemas/EmailInboxInsightsSeedEngagementSplit"}}}]},"EmailInboxInsightsSeedTestQuota":{"type":"object","additionalProperties":false,"description":"The workspace's seed-test allowance for the current month. Registering a test spends one of the allowance whether or not its send goes out, and a test that expires unused does not return it.\n","required":["used","limit","resets_at"],"properties":{"used":{"type":"integer","minimum":0,"readOnly":true,"description":"Seed tests registered so far this month.","example":112},"limit":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Seed tests included per month, or null when no monthly cap applies to this workspace.\n","example":300},"resets_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the monthly allowance next resets.","example":"2026-09-01T00:00:00Z"}}},"EmailInboxInsightsSeedTests":{"description":"The domain's seed tests over the period, with the newest test's full results\nand the workspace's monthly allowance.\n\nA seed test sends to a panel of real mailboxes across many providers and\nreports exactly where each copy landed, which is a direct measurement rather\nthan an estimate. Seed counts are small by nature, so a single seed moves a\nrate noticeably.\n","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/EmailInboxInsightsEnvelope"},{"type":"object","required":["tests","quota"],"properties":{"tests":{"$ref":"#/components/schemas/EmailInboxInsightsSeedTestList"},"latest":{"$ref":"#/components/schemas/EmailInboxInsightsSeedTestDetail","description":"The newest test in the period with its full results, so the summary panels need no second request. Absent when the period holds no tests.\n"},"quota":{"$ref":"#/components/schemas/EmailInboxInsightsSeedTestQuota"}}}]},"EmailInboxInsightsSeedTestCreate":{"type":"object","additionalProperties":false,"description":"How to configure a seed test. The choices available for a domain come from the seed-test configuration.\n","required":["sending_domain","list_type","engagement_profile","regions"],"properties":{"sending_domain":{"type":"string","minLength":1,"description":"The sending domain the test measures: one of the workspace's verified sending domains, exactly as it appears there.\n","example":"mail.acme.com"},"list_type":{"$ref":"#/components/schemas/EmailInboxInsightsSeedListType","description":"Which seed pool to draw addresses from. Use a value the seed-test configuration reports as available for this account.\n"},"engagement_profile":{"$ref":"#/components/schemas/EmailInboxInsightsSeedEngagementProfile","description":"Which engagement behaviour the seeds should simulate. Mixing both behaviours is what makes the engaged-against-dormant comparison measurable.\n"},"regions":{"type":"array","minItems":1,"description":"The regions to place seeds in, as the seed-test configuration names them.","items":{"type":"string","minLength":1},"example":["North America - US","Europe - UK"]},"label":{"type":"string","minLength":1,"maxLength":200,"description":"A name for the test, for your own reference in the test history.","example":"Fall preview send"}}},"EmailInboxInsightsSeedAddress":{"type":"object","additionalProperties":false,"description":"One seed address to include in the tested send.","required":["address","mailbox_provider","region","engaging"],"properties":{"address":{"type":"string","minLength":1,"readOnly":true,"description":"The address to add to the send's recipients. Include it exactly as given; an altered address is not a seed and will not be measured.\n","example":"sd8241.hk@example-seeds.net"},"mailbox_provider":{"$ref":"#/components/schemas/EmailInboxInsightsMailboxProvider","readOnly":true,"description":"The provider this address is hosted at."},"region":{"type":"string","minLength":1,"readOnly":true,"description":"The region this address sits in, as the seed-test choices name it.","example":"North America - US"},"engaging":{"type":"boolean","readOnly":true,"description":"Whether this address simulates a recipient who engages with mail. There are two behaviours rather than a scale, so a test either mixes both or uses one of them.\n","example":true}}},"EmailInboxInsightsSeedTestRegistration":{"type":"object","additionalProperties":false,"description":"A registered seed test and the addresses it measures.\n\nRegistering a test does not put the addresses into a send. Add them to the\nrecipients of the send you want measured, and the test appears in the\nseed-test list for the domain once its seed mail has been measured.\n\nThere is no status field. The measurement reports none for a seed test at any\npoint, so read the test's own fields instead: a registered test has addresses\nand an expiry date, and it gains a subject and a tested-at date once its send\ngoes out.\n","required":["registration_id","seed_addresses","seed_count","expires_at"],"properties":{"registration_id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifies this registration. It is not the identifier the seed-test list\nreports for the resulting test.\n\nIt is here so a registration can be quoted in a support conversation, and\nso a client can tell two registrations apart. To read the results, find\nthe test in the seed-test list for this domain.\n","example":"43ea66c8-6837-48a4-b81b-26fdf5cc8cd8"},"seed_addresses":{"type":"array","readOnly":true,"description":"Every address to include in the tested send. Copy them into the send's recipients; results are measured from mail these addresses receive.\n","items":{"$ref":"#/components/schemas/EmailInboxInsightsSeedAddress"}},"seed_count":{"type":"integer","minimum":0,"readOnly":true,"description":"How many seed addresses the test issued.","example":212},"expires_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the test expires if no seed mail has arrived. An expired test never produces results, and the allowance it spent is not returned, so send before this time.\n","example":"2026-08-27T09:12:00Z"}}},"EmailInboxInsightsSeedListTypeOption":{"type":"object","additionalProperties":false,"description":"One seed pool a test can be run against, and whether this account can use it.","required":["value","available"],"properties":{"value":{"$ref":"#/components/schemas/EmailInboxInsightsSeedListType","readOnly":true,"description":"The value to send when registering a test against this pool."},"available":{"type":"boolean","readOnly":true,"description":"Whether this pool is provisioned for the account. An unavailable pool is one the account has not been set up for rather than one its plan forbids, and there is no self-serve way to enable one, so leave it out of the choices you offer rather than showing it unpickable.\n","example":true}}},"EmailInboxInsightsSeedRegionOption":{"type":"object","additionalProperties":false,"description":"One region seeds can be placed in.","required":["value"],"properties":{"value":{"type":"string","minLength":1,"readOnly":true,"description":"The value to send when registering a test against this region. Free text rather than an enumeration: the set belongs to the measurement and is wider than the continents it looks like, so send one of these back verbatim rather than composing your own.\n","example":"North America - US"}}},"EmailInboxInsightsSeedEngagementProfileOption":{"type":"object","additionalProperties":false,"description":"One engagement behaviour a test can simulate, and whether this account can use it.","required":["value","available"],"properties":{"value":{"$ref":"#/components/schemas/EmailInboxInsightsSeedEngagementProfile","readOnly":true,"description":"The value to send when registering a test against this behaviour."},"available":{"type":"boolean","readOnly":true,"description":"Whether this behaviour is provisioned for the account. As with the seed pools, an unavailable behaviour is one the account has not been set up for rather than one its plan forbids, so leave it out of the choices you offer rather than showing it unpickable.\n","example":true}}},"EmailInboxInsightsSeedTestConfiguration":{"type":"object","additionalProperties":false,"description":"The choices available when registering a seed test for a domain: which seed\npools the account can use, which regions seeds can be placed in, and which\nengagement behaviours the seeds can simulate.\n\nThis is reference data rather than a measurement, so it carries no period and\nno measurement detail. It changes only when an account's provisioning does.\n","required":["resource","domain","list_types","regions","engagement_profiles"],"properties":{"resource":{"type":"string","minLength":1,"readOnly":true,"description":"Which resource this response is, echoed for self-description.","example":"seed-test-configuration"},"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The sending domain these choices apply to.","example":"mail.acme.com"},"list_types":{"type":"array","readOnly":true,"description":"The seed pools, each flagged with whether the account can use it.","items":{"$ref":"#/components/schemas/EmailInboxInsightsSeedListTypeOption"}},"regions":{"type":"array","readOnly":true,"description":"The regions seeds can be placed in. Objects rather than bare strings, to match the two lists beside it: the measurement reports no availability for a region today, and an object can carry one later without a second array.\n","items":{"$ref":"#/components/schemas/EmailInboxInsightsSeedRegionOption"}},"engagement_profiles":{"type":"array","readOnly":true,"description":"The engagement behaviours the seeds can simulate, each flagged with whether the account can use it.\n","items":{"$ref":"#/components/schemas/EmailInboxInsightsSeedEngagementProfileOption"}}}},"EmailInboxInsightsDomainSort":{"type":"string","minLength":1,"description":"Field used to sort owned domains.","enum":["domain"],"default":"domain","example":"domain"},"EmailInboxInsightsDomain":{"type":"object","additionalProperties":false,"description":"One of the workspace's verified sending domains, and whether Inbox Insights is switched on for it.\n","required":["domain","monitored"],"properties":{"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The sending domain, lowercased, as it appears in your sending domains.","example":"mail.acme.com"},"monitored":{"type":"boolean","readOnly":true,"description":"Whether Inbox Insights reports on this domain. Switching it off stops the reporting and keeps the measurement history, so switching it back on restores the full history rather than starting again.\n","example":true}}},"EmailInboxInsightsDomains":{"description":"A page of sending domains this workspace can report on, and which of them Inbox Insights is switched on for.\n","allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","readOnly":true,"description":"One entry per verified domain in this page, whether or not it is switched on. A domain that has not been verified does not appear, because verification is what proves the domain is yours to report on.\n","items":{"$ref":"#/components/schemas/EmailInboxInsightsDomain"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}],"unevaluatedProperties":false},"EmailInboxInsightsDomainUpdate":{"type":"object","additionalProperties":false,"description":"The Inbox Insights setting to change for a sending domain.","required":["monitored"],"properties":{"monitored":{"type":"boolean","description":"Whether the workspace wants this domain monitored. Enabling enrolls it with eDataSource; disabling removes only the workspace preference and preserves vendor enrollment and measurement history. Verified ownership governs report access.\n","example":true}}},"EmailInboxInsightsDomainMonitoringOutcome":{"type":"string","minLength":1,"description":"What switching on the main sending domain did.\n\n- `enabled`: Inbox Insights is now switched on for the domain named alongside this.\n- `already_on`: at least one domain was already switched on, so nothing changed.\n- `choice_required`: the main sending domain could not be identified, most often\n  because the workspace has several verified domains and no sending to rank them\n  by. Ask the customer to choose.\n- `no_verified_domains`: the workspace has no verified sending domain, so there is\n  nothing to report on until one is verified.\n","enum":["enabled","already_on","choice_required","no_verified_domains"],"example":"enabled"},"EmailInboxInsightsDomainMonitoringResult":{"type":"object","additionalProperties":false,"description":"What switching on the workspace's main sending domain did. There are four outcomes, because each one leaves the customer somewhere different: one domain is now reporting, one already was, we could not tell which domain is the main one, or there is no verified domain to report on at all.\n","required":["outcome","domain"],"properties":{"outcome":{"readOnly":true,"$ref":"#/components/schemas/EmailInboxInsightsDomainMonitoringOutcome"},"domain":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The sending domain this call switched on, lowercased. The server sends a domain with the `enabled` outcome and null with the other three, `already_on` included: that outcome says only that the workspace had already made its choice, not which domain it chose. Read the domain list for that. Check `outcome` first rather than treating a domain as present.\n","example":"mail.acme.com"}}},"EmailStatsSeriesPeriod":{"type":"object","additionalProperties":false,"description":"The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to.\n","required":["from","to","grain"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window. A calendar day (YYYY-MM-DD, in the requested `timezone`) on the day grain. On the hour grain, an RFC 3339 UTC instant marking the start of the first hour bucket, which falls on a local hour boundary when `timezone` is set.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window. A calendar day (YYYY-MM-DD, in the requested `timezone`) on the day grain. On the hour grain, an RFC 3339 UTC instant marking the start of the last hour bucket, which falls on a local hour boundary when `timezone` is set.","example":"2026-05-31"},"grain":{"$ref":"#/components/schemas/StatsGrain","readOnly":true},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The instant the statistics in this response are current to: events recorded up to roughly this time are reflected, while more recent events may not be yet. Statistics are served from a rolling aggregation that refreshes every few seconds, so a response reflects data from up to a few seconds ago. Use this field to label data freshness rather than assuming the numbers are to-the-second. Null when the freshness boundary is not being reported.\n","example":"2026-05-25T14:03:10Z"}}},"EmailBounceStatsWithRates":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Breakdown of `bounced` by failure type, with each rate as a fraction of `bounced`. Counts are distinct bounced recipients of that type; the five types approximately partition `bounced`, so the five rates sum to roughly 1.0 when `bounced` is non-zero.\n","required":["hard","soft","admin","block","undetermined","hard_rate","soft_rate","admin_rate","block_rate","undetermined_rate"],"properties":{"hard":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients with a permanent delivery failure (invalid address or non-existent domain).","example":12410},"soft":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients with a transient delivery failure (mailbox full or server temporarily unavailable).","example":14290},"admin":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients refused by a policy at the receiving end, such as relaying denied or a blocklisted domain.","example":410},"block":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients bounced because the receiving mail server blocked the sending IP for reputation reasons.","example":920},"undetermined":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients bounced where the receiving server's response did not allow precise classification.","example":80},"hard_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Fraction of bounced recipients that hard bounced, computed as `hard / bounced`. Null when `bounced` is zero.\n","example":0.454},"soft_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Fraction of bounced recipients that soft bounced, computed as `soft / bounced`. Null when `bounced` is zero.\n","example":0.523},"admin_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Fraction of bounced recipients that admin bounced, computed as `admin / bounced`. Null when `bounced` is zero.\n","example":0.015},"block_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Fraction of bounced recipients that block bounced, computed as `block / bounced`. Null when `bounced` is zero.\n","example":0.0337},"undetermined_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Fraction of bounced recipients with undetermined classification, computed as `undetermined / bounced`. Null when `bounced` is zero.\n","example":0.0029}}},"EmailDeliveryStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Delivery counts and rates for the scope of the containing row (a time bucket, a breakdown dimension, or the whole period). Every count is the number of distinct recipients that reached the named lifecycle stage in scope. On the period summary, each count is the sum of the per-bucket distinct counts. Event time determines attribution; send time does not. A recipient delivered on Monday counts in Monday's row. A recipient who bounced and then succeeded on a retry can appear in both `bounced` and `delivered`. Very large counts are close estimates rather than exact tallies.\n\nThese counts are successive lifecycle stages, so a recipient can appear in more than one:\n\n- `rejected`: Happens before any send attempt, from suppression, policy, or a generation failure.\n- `deferred`: A temporary in-flight delay that is still being retried.\n- `bounced`: A delivery failure, with its own hard, soft, admin, block, and undetermined sub-types.\n- `complained`: Post-delivery spam feedback.\n\nEach rate is a fraction in the range 0 to 1 and is null when its denominator is zero. `accepted` is reported only where it can be attributed (time buckets and the period summary). Breakdown rows omit it.\n","required":["processed","delivered","bounced","complained","deferred","rejected","oob_bounces","effective_delivered","all_bounces","oob_rate","bounces","delivery_rate","bounce_rate","complaint_rate"],"properties":{"accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients accepted for delivery after suppression filtering. Reported on time buckets and the period summary. Breakdown rows leave it out, because their rollups do not have it.","example":14820},"processed":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients whose message was processed and handed off for delivery.","example":14810},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients whose message the receiving mail server accepted.","example":14720},"bounced":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients whose delivery failed. This is approximately the sum of the five `bounces.*` sub-counts (hard, soft, admin, block, undetermined). The two totals are worked out independently, so they can differ slightly.\n","example":90},"bounces":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailBounceStatsWithRates"}]},"complained":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients who reported the message as spam via a feedback loop.","example":3},"deferred":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients whose delivery the receiving server temporarily delayed and is still being retried.\n","example":14},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients rejected before any delivery attempt. Includes recipients on the workspace suppression list, transmissions that could not be completed, message-generation failures, and recipients refused by sending policy. The per-recipient `rejection_reason` field on `GET /v1/email/messages/{message_id}/recipients` surfaces the specific cause.\n","example":10},"oob_bounces":{"type":"integer","minimum":0,"readOnly":true,"description":"Out-of-band bounce events: distinct failure notifications received after the receiving server had initially confirmed delivery. The count represents deduplicated events rather than unique recipients.\n","example":2},"effective_delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Recipients who remain delivered after all bounce signals resolve, computed as `delivered - oob_bounces`. Use this as the base for engagement-rate denominators. Clamped to 0 when `oob_bounces` exceeds `delivered`.","example":14718},"all_bounces":{"type":"integer","minimum":0,"readOnly":true,"description":"Total recipients in this scope who did not receive the message, computed as `bounced + oob_bounces`.","example":92},"oob_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of this scope's delivery attempts that resulted in an out-of-band bounce, computed as `oob_bounces / (delivered + bounced)`. Null when there were no attempts.","example":0.00014},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of this scope's delivery attempts that remained delivered after all bounce signals, computed as `effective_delivered / (delivered + bounced)`. Null when there were no attempts.\n","example":0.9939},"bounce_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of this scope's delivery attempts that ultimately failed (inband or out-of-band), computed as `all_bounces / (delivered + bounced)`. Because `oob_bounces` counts events rather than recipients, `all_bounces` can exceed the attempt count. The rate is clamped to 1. Null when there were no attempts.\n","example":0.0061},"complaint_rate":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Spam complaints in this scope relative to effectively delivered recipients, computed as `complained / effective_delivered`. Complaints are attributed by event time, so a scope can record more of them than it effectively delivered, pushing the rate above 1. Null when `effective_delivered` is zero.\n","example":0.0002}}},"EmailEngagementStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Engagement counts and rates for the scope of the containing row (a time bucket, a breakdown dimension, or the whole period). `opens`, `opens_non_prefetched` and `clicks` count distinct engagement events (deduplicated occurrences). The `unique_*` fields count distinct recipients. `unsubscribes` counts distinct unsubscribe events. An event counts in the time bucket when it occurs, even if the message was sent in an earlier bucket. Counts are deduplicated with a scalable approximate counting method, so very large counts are close estimates rather than exact tallies. Each rate divides the counts in this scope and is null when its denominator is zero.\n","required":["opens","opens_non_prefetched","unique_opens","unique_opens_non_prefetched","clicks","unique_clicks","unsubscribes","open_rate","click_rate","unsubscribe_rate"],"properties":{"opens":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct open events, counting repeat opens from the same recipient and opens auto-fetched by inbox privacy features (such as Apple Mail Privacy Protection and the Gmail image proxy).\n","example":5420},"opens_non_prefetched":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct open events excluding those auto-fetched by inbox privacy features. Same event-counting semantics as `opens` (repeat opens from the same recipient count separately), with prefetched opens removed.\n","example":3210},"unique_opens":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients who opened at least once, including opens auto-fetched by inbox privacy features.","example":3640},"unique_opens_non_prefetched":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients who opened at least once, excluding opens auto-fetched by inbox privacy features. This is the numerator used for open rate, so iOS-heavy audiences (Apple Mail Privacy Protection and similar) do not inflate it.\n","example":2480},"clicks":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct click events, counting repeat clicks from the same recipient.","example":924},"unique_clicks":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients who clicked at least once.","example":621},"unsubscribes":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct unsubscribe events, recorded via the list-unsubscribe header or the footer link.","example":12},"open_rate":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Distinct non-prefetched openers relative to effectively delivered recipients in the same scope, computed as `unique_opens_non_prefetched / delivery.effective_delivered`; on rows without an `effective_delivered` field (the mailbox-provider breakdowns) the denominator equals `delivery.delivered`. The numerator excludes opens auto-fetched by inbox privacy features. Opens are attributed by event time, so engagement earned by earlier deliveries can push the rate above 1. Null when the denominator is zero.\n","example":0.1683},"click_rate":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Distinct clickers relative to effectively delivered recipients in the same scope, computed as `unique_clicks / delivery.effective_delivered` (`delivery.delivered` on rows without an `effective_delivered` field). Clicks are attributed by event time, so engagement earned by earlier deliveries can push the rate above 1. Null when the denominator is zero.\n","example":0.0422},"unsubscribe_rate":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Unsubscribe events relative to effectively delivered recipients in the same scope, computed as `unsubscribes / delivery.effective_delivered` (`delivery.delivered` on rows without an `effective_delivered` field). Unsubscribes are attributed by event time, so the rate can exceed 1. Null when the denominator is zero.\n","example":0.0009}}},"EmailLatencyQuantiles":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family over the bucket. All three are null when no qualifying event contributed a measurement.\n","required":["p50_ms","p95_ms","p99_ms"],"properties":{"p50_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement.","example":420},"p95_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.","example":1820},"p99_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.","example":4920}}},"EmailLatencyStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Latency percentiles (p50, p95, p99) in milliseconds for the bucket. On the summary endpoint these are computed across the whole period rather than per bucket. Three families are reported:\n\n- `processing`: Time from accepting the send to handing the message off for delivery. Measured per processed recipient; null when no recipient in the bucket has reached the processed stage.\n- `delivery`: Time from handoff to the receiving mail server accepting the message, dominated by recipient-side delivery behavior. Measured per delivered recipient; null when no deliveries occurred in the bucket.\n- `total`: End-to-end time from accepting the send to delivery, and the number most worth watching against your own delivery targets. Measured per delivered recipient; null when no deliveries occurred in the bucket.\n\nEach family is reported independently. A family is omitted when no qualifying\nevent contributed a latency measurement in the bucket. This also applies when\nthe workspace has not recorded latency for that stage yet. The `processing`\nfamily can therefore be present while `delivery` and `total` are absent. A\nclient must handle a missing family, and a null p50/p95/p99 within a present\nfamily, by rendering a placeholder rather than assuming a number.\n","properties":{"processing":{"$ref":"#/components/schemas/EmailLatencyQuantiles"},"delivery":{"$ref":"#/components/schemas/EmailLatencyQuantiles"},"total":{"$ref":"#/components/schemas/EmailLatencyQuantiles"}}},"EmailStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Aggregate stats for one time bucket (a calendar day or hour, per the requested grain, in the requested `timezone` or UTC by default), bucketed by event time. Buckets with no activity are included with zero counts and null latency percentiles, so the series charts continuously without client-side gap handling.\n","required":["bucket","sends_accepted","delivery","engagement","latency"],"properties":{"bucket":{"type":"string","minLength":1,"readOnly":true,"description":"The day (YYYY-MM-DD, in the requested `timezone`) or hour this point covers, matching the period's grain. An hour bucket is an RFC 3339 UTC instant marking the start of the hour. It falls on a local hour boundary when `timezone` is set, which is on the UTC hour only for whole-hour offsets.","example":"2026-05-25"},"sends_accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct email messages accepted in this bucket, counted at the message level (one per accepted send regardless of how many recipients it addresses). Every other metric in `delivery` and `engagement` is recipient-level or event-level.\n","example":412},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]}}},"EmailStatsResponse":{"type":"object","additionalProperties":false,"description":"Time-series stats payload. `period` echoes the range and bucket grain actually computed against. `data` is one row per bucket in chronological order.\n","required":["period","data"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsSeriesPeriod"},"data":{"type":"array","readOnly":true,"description":"One row per bucket (day or hour, per the grain) in the period, in chronological order. Buckets with no activity are included with zero counts.","items":{"$ref":"#/components/schemas/EmailStatsPoint"}}}},"EmailStatsSortMetric":{"type":"string","default":"processed","description":"Metric to rank breakdown rows by, applied descending. Shared by the breakdowns whose rows have the full delivery, engagement, and latency block: tags, sending domains, categories, recipient domains, templates, and broadcasts. Any count or rate can be used. A row whose rate is undefined, because its denominator was zero, sorts last. A bounce sub-type is nested under `bounces` in each row, so its sort name reflects that, for example `bounces.hard` and `bounces.hard_rate`. `oob_bounces` is distinct from `bounced`: it counts out-of-band bounces, failure notifications that arrive after delivery was already confirmed.\n","enum":["processed","delivered","bounced","complained","deferred","rejected","oob_bounces","bounces.hard","bounces.soft","bounces.admin","bounces.block","bounces.undetermined","opens","opens_non_prefetched","unique_opens","unique_opens_non_prefetched","clicks","unique_clicks","unsubscribes","delivery_rate","bounce_rate","complaint_rate","open_rate","click_rate","unsubscribe_rate","bounces.hard_rate","bounces.soft_rate","bounces.admin_rate","bounces.block_rate","bounces.undetermined_rate"]},"EmailStatsPeriod":{"type":"object","additionalProperties":false,"description":"The date range this response was actually computed against. Echoed back so clients can render the period without tracking it themselves and so cached responses can be keyed by what was queried.\n","required":["from","to"],"properties":{"from":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"Inclusive start date the response covers (YYYY-MM-DD).","example":"2026-05-01"},"to":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"Inclusive end date the response covers (YYYY-MM-DD).","example":"2026-05-25"},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The instant the statistics in this response are current to: events recorded up to roughly this time are reflected, while more recent events may not be yet. Statistics are served from a rolling aggregation that refreshes every few seconds, so a response reflects data from up to a few seconds ago. Use this field to label data freshness (for example \"as of 14:03\") rather than assuming the numbers are to-the-second. Null when the freshness boundary is not being reported.\n","example":"2026-05-25T14:03:10Z"}}},"EmailStatsSeriesPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One point in a breakdown row's trend series: the headline delivery and engagement rates for that row's dimension value over a single day or hour. Returned only when `include_trend=true`. The bucket grain (day or hour) follows the `trend_grain` parameter. Counts and rates are approximate at scale.\n","required":["bucket","delivered","bounced","delivery_rate","bounce_rate","complaint_rate","open_rate","click_rate"],"properties":{"bucket":{"type":"string","minLength":1,"readOnly":true,"description":"The day (YYYY-MM-DD) or hour (ISO 8601, on the hour) this point covers, matching the requested `trend_grain`.","example":"2026-05-12"},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Delivered recipients in this bucket."},"bounced":{"type":"integer","minimum":0,"readOnly":true,"description":"Bounced recipients in this bucket."},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Delivery rate for this bucket, as a fraction. Null when nothing was delivered or bounced."},"bounce_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Bounce rate for this bucket, as a fraction. Null when nothing was delivered or bounced."},"complaint_rate":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Complaint rate for this bucket, as a fraction. Event-time attribution can push it above 1 when complaints outrun the bucket's deliveries. Null when nothing was delivered in the bucket. On a sending-IP row complaints are not attributed to the IP, so this reads 0 in buckets that had deliveries and null in buckets that had none."},"open_rate":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Open rate for this bucket, as a fraction. Event-time attribution can push it above 1 when opens outrun the bucket's deliveries. Null when nothing was delivered in the bucket. On a sending-IP row engagement is not attributed to the IP, so this reads 0 in buckets that had deliveries and null in buckets that had none."},"click_rate":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Click rate for this bucket, as a fraction. Event-time attribution can push it above 1 when clicks outrun the bucket's deliveries. Null when nothing was delivered in the bucket. On a sending-IP row engagement is not attributed to the IP, so this reads 0 in buckets that had deliveries and null in buckets that had none."}}},"EmailTagStatsPoint":{"type":"object","additionalProperties":false,"description":"Aggregate delivery and engagement stats for a single tag name-and-value pair over the requested period.","required":["tag","delivery","engagement","latency"],"properties":{"tag":{"type":"string","minLength":1,"readOnly":true,"description":"The tag this row aggregates, formatted as `name:value` from the tag set at send time (for example `campaign:welcome-series`). Each distinct name-and-value pair is its own row.\n","example":"campaign:welcome-series"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket rate series for this tag over the window. Present only when `include_trend=true`.","items":{"$ref":"#/components/schemas/EmailStatsSeriesPoint"}}}},"EmailStatsTagsResponse":{"type":"object","additionalProperties":false,"description":"Per-tag breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Tag breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no tagged sends occurred in the period.","items":{"$ref":"#/components/schemas/EmailTagStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct tags (name and value pairs) with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":173}}},"EmailStatsSummaryPeriod":{"type":"object","additionalProperties":false,"description":"The window this response was actually computed against. The summary serves two window grains: calendar days (bounds are YYYY-MM-DD) and hours (bounds are RFC 3339 instants). The grain of `from` and `to` mirrors the grain of the request's bounds. Days and hour boundaries follow the requested `timezone` (UTC when omitted).\n","required":["from","to"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window the response covers. A calendar day (YYYY-MM-DD, in the requested `timezone`) for day windows. For hour windows, an RFC 3339 UTC instant marking the start of the first hour, which falls on a local hour boundary when `timezone` is set.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window the response covers. A calendar day (YYYY-MM-DD, in the requested `timezone`) for day windows. For hour windows, an RFC 3339 UTC instant marking the start of the last hour, which falls on a local hour boundary when `timezone` is set.","example":"2026-05-25"},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The instant the statistics in this response are current to: events recorded up to roughly this time are reflected, while more recent events may not be yet. Statistics are served from a rolling aggregation that refreshes every few seconds, so a response reflects data from up to a few seconds ago. Use this field to label data freshness (for example \"as of 14:03\") rather than assuming the numbers are to-the-second. Null when the freshness boundary is not being reported.\n","example":"2026-05-25T14:03:10Z"}}},"EmailStatsComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The change in each headline metric from the preceding period to the requested one. A `*_pct_change` field is a signed relative change in a count, computed as `(current - previous) / previous`, so `0.5` means 50% higher and `-0.2` means 20% lower. It is null when the previous period's count was zero because a relative change cannot be computed. A `*_rate_pp` field is the signed difference between the two periods' rate values. Each value is expressed as a fraction. A value of `0.012` means the rate rose by 1.2 percentage points. A value of `-0.003` means it fell by 0.3 points. The field is null when either period's rate is undefined, because its denominator was zero. `delivery_rate_pp` and `bounce_rate_pp` range from `-1` to `1`, because the rates behind them cannot exceed 1. The engagement deltas have no fixed bound, because events are counted when they arrive rather than when the message was sent, which can push their rate above 1.\n","required":["sends_accepted_pct_change","delivered_pct_change","bounced_pct_change","complained_pct_change","opened_pct_change","delivery_rate_pp","open_rate_pp","click_rate_pp","bounce_rate_pp","complaint_rate_pp","unsubscribe_rate_pp"],"properties":{"sends_accepted_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in accepted messages (the `sends_accepted` count) versus the previous period, as a signed fraction. Null when the previous period accepted none.","example":0.508},"delivered_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in effectively delivered recipients (`delivery.effective_delivered`, the delivery-rate numerator) versus the previous period, as a signed fraction. Null when the previous period effectively delivered none.","example":0.122},"bounced_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in total bounces including out-of-band (`delivery.all_bounces`, the bounce-rate numerator) versus the previous period, as a signed fraction. Null when the previous period had none.","example":-0.031},"complained_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in spam complaints (`delivery.complained`) versus the previous period, as a signed fraction. Null when the previous period had none.","example":0.018},"opened_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in unique non-prefetched opens (`engagement.unique_opens_non_prefetched`, the same count the open rate uses) versus the previous period, as a signed fraction. Null when the previous period had none.","example":-0.046},"delivery_rate_pp":{"type":["number","null"],"minimum":-1,"maximum":1,"readOnly":true,"description":"Signed difference between this period's and the previous period's delivery rate, both fractions in [0,1] (multiply by 100 for percentage points). Null when either period's delivery rate is undefined.","example":0.004},"open_rate_pp":{"type":["number","null"],"readOnly":true,"description":"Signed difference between this period's and the previous period's open rate, both fractions (multiply by 100 for percentage points). Null when either period's open rate is undefined.","example":0.005},"click_rate_pp":{"type":["number","null"],"readOnly":true,"description":"Signed difference between this period's and the previous period's click rate, both fractions (multiply by 100 for percentage points). Null when either period's click rate is undefined.","example":0.002},"bounce_rate_pp":{"type":["number","null"],"minimum":-1,"maximum":1,"readOnly":true,"description":"Signed difference between this period's and the previous period's bounce rate, both fractions in [0,1] (multiply by 100 for percentage points). Null when either period's bounce rate is undefined.","example":-0.0008},"complaint_rate_pp":{"type":["number","null"],"readOnly":true,"description":"Signed difference between this period's and the previous period's complaint rate, both fractions (multiply by 100 for percentage points). Null when either period's complaint rate is undefined.","example":0.0001},"unsubscribe_rate_pp":{"type":["number","null"],"readOnly":true,"description":"Signed difference between this period's and the previous period's unsubscribe rate, both fractions (multiply by 100 for percentage points). Null when either period's unsubscribe rate is undefined.","example":-0.0001}}},"EmailStatsComparison":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The same statistics for the equal-length, inclusive period ending the day immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request.\n","required":["period","sends_accepted","delivery","engagement","latency","delta"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsSummaryPeriod","description":"The preceding window these comparison figures cover, the equal-length window ending immediately before the requested start (the prior day for day windows, the prior hour for hour windows). For a request covering 2026-05-01 to 2026-05-31, this is 2026-03-31 to 2026-04-30, both inclusive."},"sends_accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct email messages accepted in the preceding period, counted at the message level.","example":8230},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"delta":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailStatsComparisonDelta"}]}}},"EmailStatsSummary":{"type":"object","additionalProperties":false,"description":"A single row that aggregates delivery and engagement counts, plus derived\nrates, across the whole requested period. Use this endpoint for KPI\ntiles, campaign reporting, and anywhere you need a rate with a meaningful\ndenominator. The daily and hourly endpoints report the same rates, but\nper bucket, each one dividing that bucket's own counts.\n\nEvery count is a sum of per-bucket counts across the window (per day for\nday windows, per hour for hour windows). A recipient, or a message, that\nis active in two buckets contributes to each of them, so it is counted\ntwice in the period total. This matches how most mailbox providers report\ntheir own numbers. The effect to plan for is that the total is a sum of\nper-bucket activity rather than a count of distinct recipients or messages\nacross the whole period. Latency percentiles work differently: they are computed\nonce across the whole period rather than summed from the buckets. A rate\nis null when its denominator is zero.\n","required":["period","sends_accepted","delivery","engagement","latency"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsSummaryPeriod","description":"The window the response covers (echoed back from the request, day or hour grain), plus `data_as_of`, the freshness boundary the data is current to."},"sends_accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct email messages accepted, counted at the message level (one per accepted send regardless of recipient count) and summed per bucket across the period. This field counts messages. `delivery.accepted` counts recipients, so the two values are not comparable (a single message to 500 recipients is 1 here and up to 500 there).","example":12410},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"comparison":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailStatsComparison"}]}}},"EmailInboundStatsSummary":{"type":"object","additionalProperties":false,"description":"A single-row received-email total across the full requested period. It is the sum of the per-bucket counts across the window, and because each message is received exactly once, it is the same as the number of distinct messages received in the period.\n","required":["period","received"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsSummaryPeriod","description":"The window the response covers, echoed back from the request as a day or hour grain. It also includes `data_as_of`, the freshness boundary the data is current to."},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct emails received in the requested period.","example":1204}}},"EmailInboundStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Received-email counts for one time bucket, a calendar day or hour depending on the requested grain, bucketed by when the message was received. Buckets with no activity are included with zero counts, so the series charts continuously without extra handling on your end.\n","required":["bucket","received"],"properties":{"bucket":{"type":"string","minLength":1,"readOnly":true,"description":"The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the period's grain.","example":"2026-07-21"},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct emails received in this bucket.","example":87}}},"EmailInboundStatsResponse":{"type":"object","additionalProperties":false,"description":"Time-series received-email stats payload. `period` echoes the range and bucket grain used to compute this response. `data` is one row per bucket in chronological order.\n","required":["period","data"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsSeriesPeriod"},"data":{"type":"array","readOnly":true,"description":"One row per bucket (day or hour, per the grain) in the period, in chronological order. Buckets with no activity are included with zero counts.","items":{"$ref":"#/components/schemas/EmailInboundStatsPoint"}}}},"IPPoolID":{"type":"string","minLength":1,"pattern":"^ipp_[0-9a-hjkmnp-tv-z]{26}$","example":"ipp_01krdgeqcxet5s7t44vh8rt9mg"},"EmailSendingIpDeliveryStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Delivery counts and rates for messages attributed to a single sending IP. Per-IP results omit `accepted` and `processed` counts. The sending IP becomes known only after a message is delivered, bounced, deferred, or bounced late. Those earlier lifecycle states cannot be attributed to a specific IP. Spam complaints and out-of-band bounce notifications also lack per-IP attribution on this breakdown. The `complained` and `oob_bounces` fields therefore read 0. Their rates read 0 when the denominator is non-zero and null when it is zero. The `effective_delivered` field equals `delivered`, and `all_bounces` equals `bounced`.\n","required":["delivered","bounced","complained","deferred","oob_bounces","effective_delivered","all_bounces","oob_rate","bounces","delivery_rate","bounce_rate","complaint_rate"],"properties":{"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients whose message the receiving mail server accepted.","example":8290},"bounced":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients whose delivery failed. This is approximately the sum of the five `bounces.*` sub-counts (hard, soft, admin, block, undetermined). The two are computed independently, so they can differ slightly.","example":131},"complained":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients who reported the message as spam. Complaints are not attributed to a sending IP, so this reads 0 on this breakdown. Read complaint counts from the summary or time-series statistics instead.","example":8},"deferred":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients in transient delivery deferral that is still being retried.","example":4},"oob_bounces":{"type":"integer","minimum":0,"readOnly":true,"description":"Out-of-band bounce events: failure notifications received after the receiving server had initially confirmed delivery. Not attributed to a sending IP on this breakdown, so this reads 0. Workspace-wide out-of-band counts are on the summary and time-series statistics.\n","example":3},"effective_delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Recipients on this IP who remain delivered after all bounce signals resolve, computed as `delivered - oob_bounces`. Clamped to 0 when `oob_bounces` exceeds `delivered`.","example":8287},"all_bounces":{"type":"integer","minimum":0,"readOnly":true,"description":"Total recipients on this IP who did not receive the message, computed as `bounced + oob_bounces`.","example":134},"oob_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of this IP's delivery attempts that resulted in an out-of-band bounce, computed as `oob_bounces / (delivered + bounced)`. Null when `delivered + bounced` is zero (no attempts).","example":0.00036},"bounces":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailBounceStatsWithRates"}]},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of this IP's delivery attempts that remained delivered after all bounce signals, computed as `effective_delivered / (delivered + bounced)`. Null when `delivered + bounced` is zero.\n","example":0.9844},"bounce_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of this IP's delivery attempts that ultimately failed (inband or out-of-band), computed as `all_bounces / (delivered + bounced)`. Null when `delivered + bounced` is zero (no attempts).\n","example":0.0156},"complaint_rate":{"type":["number","null"],"minimum":0,"readOnly":true,"description":"Share of effectively delivered recipients on this IP who reported the message as spam, computed as `complained / effective_delivered`. Null when `effective_delivered` is zero.\n","example":0.00096}}},"EmailDeliveryLatencyStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Latency percentiles (p50, p95, p99) in milliseconds for the messages in this breakdown row, for breakdowns whose dimension is known only from delivery onward (sending IP, mailbox provider).\n\n- `delivery`: Time from handing the message off to the receiving mail server accepting it. Null when no deliveries occurred for this row in the period.\n- `total`: End-to-end time from accepting the send to delivery. Null when no deliveries occurred for this row in the period.\n\nThese breakdowns have no `processing` latency family. A message's row identifies which sending IP carried it or which mailbox provider received it. This becomes known only after the receiving mail server reports a delivery, bounce, deferral, or late bounce. The accept-to-processed phase ends before that attribution is known, preventing row-level processing latency. Use `GET /v1/email/stats/daily` for processing-latency percentiles across the whole workspace.\n","required":["delivery","total"],"properties":{"delivery":{"$ref":"#/components/schemas/EmailLatencyQuantiles"},"total":{"$ref":"#/components/schemas/EmailLatencyQuantiles"}}},"EmailSendingIpStatsPoint":{"type":"object","additionalProperties":false,"description":"Delivery and latency stats for messages sent from a single IP address over the requested period. Per-IP attribution begins only after a message is processed: we learn which IP a message used only from its delivery, bounce, deferral, and late-bounce events. Acceptance and processing events do not contribute to per-IP attribution. As a result, per-IP rows omit the `accepted` and `processed` counts and the `processing` latency family. Those fields never appear on a per-IP row. They are not returned as null.\n","required":["sending_ip","delivery","latency"],"properties":{"sending_ip":{"type":"string","minLength":1,"readOnly":true,"description":"The IP address used to send messages aggregated in this row.","example":"192.0.2.55"},"ip_pool_id":{"readOnly":true,"description":"The dedicated IP pool this address sent through, or null when the messages went through the shared pool. Recorded when each message was sent, so it reflects the pool used at send time even if the IP has since moved between pools or been released.\n","oneOf":[{"$ref":"#/components/schemas/IPPoolID"},{"type":"null"}]},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailSendingIpDeliveryStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket delivery-rate series for this IP over the window. Present only when `include_trend=true`. Engagement is not attributed to a sending IP, so each point's open and click rates read 0 in buckets with deliveries and null in buckets without.","items":{"$ref":"#/components/schemas/EmailStatsSeriesPoint"}}}},"EmailStatsBySendingIpResponse":{"type":"object","additionalProperties":false,"description":"Per-sending-IP breakdown for the requested period, ranked by the `sort` metric (default `delivered`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Sending-IP breakdown rows, ranked by the `sort` metric (default `delivered`) descending. Empty when no per-IP-attributable activity (delivery, bounce, deferral, or late bounce) occurred in the period.","items":{"$ref":"#/components/schemas/EmailSendingIpStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct sending IP addresses with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":6}}},"EmailSendingDomainStatsPoint":{"type":"object","additionalProperties":false,"description":"Aggregate delivery, engagement, and latency stats for messages sent from a single sending domain over the requested period.","required":["sending_domain","delivery","engagement","latency"],"properties":{"sending_domain":{"type":"string","minLength":1,"readOnly":true,"description":"The sending domain (the portion of the `From` address after the `@`), normalized to lowercase.","example":"mail.acme.com"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket rate series for this sending domain over the window. Present only when `include_trend=true`.","items":{"$ref":"#/components/schemas/EmailStatsSeriesPoint"}}}},"EmailStatsBySendingDomainResponse":{"type":"object","additionalProperties":false,"description":"Per-sending-domain breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Sending-domain breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no eligible activity occurred in the period.","items":{"$ref":"#/components/schemas/EmailSendingDomainStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct sending domains with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":12}}},"EmailCategoryStatsPoint":{"type":"object","additionalProperties":false,"description":"Aggregate delivery and engagement stats for a single category over the requested period.","required":["category","delivery","engagement","latency"],"properties":{"category":{"type":"string","minLength":1,"readOnly":true,"description":"The category this row aggregates, as set at send time. `transactional` is one-to-one mail triggered by a user action. `marketing` is bulk sending. New categories may be added over time.","example":"transactional"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket rate series for this category over the window. Present only when `include_trend=true`.","items":{"$ref":"#/components/schemas/EmailStatsSeriesPoint"}}}},"EmailStatsByCategoryResponse":{"type":"object","additionalProperties":false,"description":"Per-category breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Category breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no sends occurred in the period.","items":{"$ref":"#/components/schemas/EmailCategoryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct categories with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":2}}},"EmailMailboxProviderSortMetric":{"type":"string","default":"delivered","description":"Metric to rank rows by, applied descending. Shared by every breakdown whose attribution begins at delivery, so `processed`, `rejected`, and `oob_bounces` are not part of those rows and are not sortable. Any count or rate on the row can be used; rows whose rate is undefined (zero denominator) sort last. Bounce sub-types use their nested location in each row, for example `bounces.hard` and `bounces.hard_rate`.\n","enum":["delivered","bounced","complained","deferred","bounces.hard","bounces.soft","bounces.admin","bounces.block","bounces.undetermined","opens","opens_non_prefetched","unique_opens","unique_opens_non_prefetched","clicks","unique_clicks","unsubscribes","delivery_rate","bounce_rate","complaint_rate","open_rate","click_rate","unsubscribe_rate","bounces.hard_rate","bounces.soft_rate","bounces.admin_rate","bounces.block_rate","bounces.undetermined_rate"]},"EmailMailboxProviderDeliveryStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Delivery counts and rates for messages attributed to a single recipient mailbox provider. Per-provider results do not include `accepted` or `processed` counts, because we only learn the recipient's mailbox provider once the receiving mail server reports delivery, a bounce, a deferral, or a late bounce. Earlier lifecycle states (accepted, processed) cannot be attributed to a specific provider.\n","required":["delivered","bounced","complained","deferred","bounces","delivery_rate","bounce_rate","complaint_rate"],"properties":{"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients whose message the receiving mail server accepted.","example":8290},"bounced":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients whose delivery failed. Approximately the sum of the five `bounces.*` sub-counts (hard, soft, admin, block, undetermined); the totals are computed independently so they may differ slightly at the approximation error.","example":131},"complained":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients who reported the message as spam.","example":8},"deferred":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients in transient delivery deferral that is still being retried.","example":4},"bounces":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailBounceStatsWithRates"}]},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of attempted recipients on this mailbox provider that were delivered, computed as `delivered / (delivered + bounced)`. Null when `delivered + bounced` is zero (no attempts).\n","example":0.9844},"bounce_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of attempted recipients on this mailbox provider that bounced, computed as `bounced / (delivered + bounced)`. Null when `delivered + bounced` is zero (no attempts).\n","example":0.0156},"complaint_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of delivered recipients on this mailbox provider who reported the message as spam, computed as `complained / delivered`. Null when `delivered` is zero.\n","example":0.00096}}},"EmailMailboxProviderStatsPoint":{"type":"object","additionalProperties":false,"description":"Delivery, engagement, and deliverability stats for messages grouped by a single recipient mailbox provider (`gmail`, `microsoft`, `yahoo`, `apple`, ...) over the requested period. We learn a recipient's mailbox provider from the receiving mail server. Per-provider rows therefore cover the delivery stage onward. They omit the `accepted` and `processed` counts and the `processing` latency family. These fields are absent rather than null. Engagement (opens and clicks, and their rates) is included because those events happen after delivery, once the mailbox provider is already known.\n","required":["mailbox_provider","delivery","engagement","latency"],"properties":{"mailbox_provider":{"type":"string","minLength":1,"readOnly":true,"description":"The recipient mailbox provider this row aggregates, as a lowercase classifier such as `gmail`, `yahoo`, `microsoft`, or `apple`. New classifiers may be added over time.","example":"gmail"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailMailboxProviderDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket rate series for this mailbox provider over the window. Present only when `include_trend=true`.","items":{"$ref":"#/components/schemas/EmailStatsSeriesPoint"}}}},"EmailStatsByMailboxProviderResponse":{"type":"object","additionalProperties":false,"description":"Per-mailbox-provider breakdown for the requested period, ranked by the `sort` metric (default `delivered`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Mailbox-provider breakdown rows, ranked by the `sort` metric (default `delivered`) descending. Empty when no eligible activity occurred in the period.","items":{"$ref":"#/components/schemas/EmailMailboxProviderStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct mailbox providers with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":14}}},"EmailMailboxProviderForSendingDomainStatsPoint":{"type":"object","additionalProperties":false,"description":"Delivery, engagement, and deliverability figures for one sending domain's mail at a single recipient mailbox provider (`gmail`, `microsoft`, `yahoo`, `apple`, ...) over the requested period.\n\nA recipient's mailbox provider is known only once the receiving mail server reports an outcome, so these rows cover the delivery stage onward. They omit the `accepted` and `processed` counts and the `processing` latency family, and those fields are absent rather than null. Opens and clicks are included, because they happen after delivery when the provider is already known.\n","required":["mailbox_provider","delivered_share","deferral_rate","delivery","engagement","latency"],"properties":{"mailbox_provider":{"type":"string","minLength":1,"readOnly":true,"description":"The recipient mailbox provider this row aggregates, as a lowercase classifier such as `gmail`, `yahoo`, `microsoft`, or `apple`. New classifiers may be added over time.","example":"gmail"},"delivered_share":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"This provider's share of everything the sending domain delivered in the period, as a fraction between 0 and 1. It divides this row's `delivered` count by the total delivered across every provider, so you can check it against the counts on the row.\n\nThe denominator spans every provider with activity in the period, not only the rows returned, so when `limit` caps the ranking the shares you can see add up to less than 1. Mail that never reached the delivery stage has no provider to attribute it to and is counted on neither side of this figure, so this is a share of delivered volume rather than of everything sent. Null when the domain delivered nothing in the period.\n","example":0.4218},"deferral_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of attempted recipients at this provider that the provider asked us to retry at least once, computed as `deferred / (delivered + bounced)`. Rising deferrals are an early sign a provider is throttling your mail.\n\nA deferral is a temporary refusal rather than a final outcome, so a recipient counted here can still go on to be delivered or to bounce, and this figure does not partition the period's attempts the way `delivery_rate` and `bounce_rate` do. For the same reason it reports 1 rather than more when a window ends with more recipients still being retried than finished. Null when there were no attempts.\n","example":0.0312},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailMailboxProviderDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryLatencyStats"}]}}},"EmailStatsByMailboxProviderForSendingDomainResponse":{"type":"object","additionalProperties":false,"description":"Per-mailbox-provider breakdown for one sending domain over the requested period, ranked by the `sort` metric (default `delivered`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total","delivered_total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers, echoed back from the request. These figures are read live rather than from a periodic aggregation, so `data_as_of` is null here."},"data":{"type":"array","readOnly":true,"description":"Mailbox-provider rows for this sending domain, ranked by the `sort` metric (default `delivered`) descending. Empty when the domain had no eligible activity in the period, which is also the answer for a domain this workspace has not verified.","items":{"$ref":"#/components/schemas/EmailMailboxProviderForSendingDomainStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct mailbox providers with activity for this sending domain in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":9},"delivered_total":{"type":"integer","minimum":0,"readOnly":true,"description":"Recipients this sending domain delivered to across every mailbox provider in the period, which is the denominator behind each row's `delivered_share`. Published so a capped ranking stays checkable: subtract the rows you received to size the providers the `limit` left out.\n","example":48210}}},"EmailMailboxProviderRegionStatsPoint":{"type":"object","additionalProperties":false,"description":"Delivery, engagement, and deliverability stats for messages grouped by a single mailbox provider and provider region pair over the requested period, for example `gmail` in `NA` or `microsoft` in `EU`. The provider region is the regional pod the receiving mail system reports for the recipient's provider; pairing it with the provider disambiguates a region label that several providers share. Like the mailbox-provider breakdown, rows cover the delivery stage onward: the `accepted` and `processed` counts and the `processing` latency family are omitted (a provider region cannot be attributed before delivery).\n","required":["mailbox_provider","mailbox_provider_region","delivery","engagement","latency"],"properties":{"mailbox_provider":{"type":"string","minLength":1,"readOnly":true,"description":"The recipient mailbox provider this row aggregates, as a lowercase classifier such as `gmail`, `yahoo`, `microsoft`, or `apple`.","example":"gmail"},"mailbox_provider_region":{"type":"string","minLength":1,"readOnly":true,"description":"The provider region this row aggregates, as reported by the receiving mail system (for example `NA`, `EU`, `APAC`). The set is open and provider-specific.","example":"NA"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailMailboxProviderDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket rate series for this provider region over the window. Present only when `include_trend=true`.","items":{"$ref":"#/components/schemas/EmailStatsSeriesPoint"}}}},"EmailStatsByMailboxProviderRegionResponse":{"type":"object","additionalProperties":false,"description":"Per-(mailbox provider, provider region) breakdown for the requested period, ranked by the `sort` metric (default `delivered`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Provider-region breakdown rows, ranked by the `sort` metric (default `delivered`) descending. Empty when no deliveries occurred in the period.","items":{"$ref":"#/components/schemas/EmailMailboxProviderRegionStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct mailbox provider and region pairs with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":31}}},"EmailRecipientDomainStatsPoint":{"type":"object","additionalProperties":false,"description":"Aggregate delivery, engagement, and latency stats for messages sent to a single recipient mailbox domain over the requested period.","required":["recipient_domain","delivery","engagement","latency"],"properties":{"recipient_domain":{"type":"string","minLength":1,"readOnly":true,"description":"The recipient mailbox domain this row aggregates (the part of the recipient address after the `@`), normalized to lowercase.","example":"gmail.com"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"Per-bucket rate series for this recipient domain over the window. Present only when `include_trend=true`.","items":{"$ref":"#/components/schemas/EmailStatsSeriesPoint"}}}},"EmailStatsByRecipientDomainResponse":{"type":"object","additionalProperties":false,"description":"Per-recipient-domain breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Recipient-domain breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no eligible activity occurred in the period.","items":{"$ref":"#/components/schemas/EmailRecipientDomainStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct recipient domains with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":412}}},"EmailTemplateStatsPoint":{"type":"object","additionalProperties":false,"description":"Delivery, engagement, and latency numbers for every message sent with one template over the requested period.","required":["template_id","delivery","engagement","latency"],"properties":{"template_id":{"allOf":[{"$ref":"#/components/schemas/EmailTemplateID"}],"readOnly":true,"description":"The template this row is about, using the same `id` the email template endpoints return. Only messages sent with a template appear in this breakdown at all. If the template was deleted after it was used to send, this row still appears, keyed by that same `id`.\n"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"trend":{"type":"array","readOnly":true,"description":"A short series of this template's delivery and engagement rates, one point per time bucket over the window. Only present when you set `include_trend=true` on the request.\n","items":{"$ref":"#/components/schemas/EmailStatsSeriesPoint"}}}},"EmailStatsByTemplateResponse":{"type":"object","additionalProperties":false,"description":"Per-template breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Template breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no messages were sent with a template in the period.","items":{"$ref":"#/components/schemas/EmailTemplateStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct templates with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":42}}},"EmailEngagementSortMetric":{"type":"string","default":"unique_opens","description":"Metric to rank rows by, applied descending. Shared by the engagement-only breakdowns (locations, email clients), which report open and click counts but no rates because delivery events provide no per-dimension denominator.\n","enum":["opens","opens_non_prefetched","unique_opens","unique_opens_non_prefetched","clicks","unique_clicks"]},"EmailEngagementCounts":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Open and click counts for a breakdown row whose dimension is resolved from engagement events only. `opens`, `opens_non_prefetched`, and `clicks` count each event. The same recipient opening or clicking more than once counts each time. The `unique_*` fields count distinct recipients instead, so a recipient who opened five times only counts once there. Rates and unsubscribe counts are not included here. A per-dimension delivered count is unavailable as a rate's denominator, and an unsubscribe event has none of the information this breakdown is grouped by, so it cannot be placed on a row.\n","required":["opens","opens_non_prefetched","unique_opens","unique_opens_non_prefetched","clicks","unique_clicks"],"properties":{"opens":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct open events, counting repeat opens from the same recipient and opens auto-fetched by inbox privacy features (such as Apple Mail Privacy Protection and the Gmail image proxy).\n","example":5420},"opens_non_prefetched":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct open events excluding those auto-fetched by inbox privacy features. Same event-counting semantics as `opens`, with prefetched opens removed.\n","example":3210},"unique_opens":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients who opened at least once, including opens auto-fetched by inbox privacy features.","example":3640},"unique_opens_non_prefetched":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients who opened at least once, excluding opens auto-fetched by inbox privacy features.","example":2480},"clicks":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct click events, counting repeat clicks from the same recipient.","example":924},"unique_clicks":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients who clicked at least once.","example":621}}},"EmailLocationStatsPoint":{"type":"object","additionalProperties":false,"description":"Open and click counts for messages engaged with from a single location over the requested period. Location is resolved from open and click events only, so this breakdown reports engagement activity: opens, clicks, and the recipients behind them. It has no delivery counts and no open or click rates, because the receiving mail server reports delivery without a recipient location, preventing a per-location delivered denominator and rates. Each row always includes all three of `country`, `region`, and `city`; the levels below the requested `group_by` are null.\n","required":["country","region","city","engagement"],"properties":{"country":{"type":"string","minLength":1,"readOnly":true,"description":"The country this row aggregates, as a two-letter country code (ISO 3166-1 alpha-2) resolved from the open or click event. Always present.","example":"US"},"region":{"type":["string","null"],"readOnly":true,"description":"The region (state or province) within the country. Populated when `group_by` is `region` or `city`; null at coarser groupings.","example":"California"},"city":{"type":["string","null"],"readOnly":true,"description":"The city within the region. Populated when `group_by` is `city`; null at coarser groupings.","example":"San Francisco"},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementCounts"}]}}},"EmailStatsByLocationResponse":{"type":"object","additionalProperties":false,"description":"Per-location engagement breakdown for the requested period, grouped at the requested `group_by` granularity, ranked by the `sort` metric (default `unique_opens`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Location breakdown rows, ranked by the `sort` metric (default `unique_opens`) descending. Empty when no opens or clicks with a resolved location occurred in the period.","items":{"$ref":"#/components/schemas/EmailLocationStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct locations at the requested `group_by` level with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":86}}},"EmailClientStatsPoint":{"type":"object","additionalProperties":false,"description":"Engagement counts for messages opened or clicked from a single email client, operating system, or device type over the requested period. The reading environment is resolved from open and click events only, so this breakdown reports engagement activity: opens, clicks, and the recipients behind them. It has no delivery counts and no open or click rates, because the receiving mail server reports delivery without a client or device, preventing a per-client delivered denominator and rates. Exactly one of `email_client`, `os`, and `device_type` is populated, selected by the request's `group_by`. The other two are null. Inbox-privacy prefetching also affects the detected client. As with open counts, `opens_non_prefetched` excludes opens auto-fetched by an inbox privacy feature. It includes opens caused by a person opening the message.\n","required":["email_client","os","device_type","engagement"],"properties":{"email_client":{"type":["string","null"],"readOnly":true,"description":"The mail client this row aggregates (for example `Gmail`, `Apple Mail`, `Outlook`). Populated only when `group_by=email_client`. Null otherwise.","example":"Apple Mail"},"os":{"type":["string","null"],"readOnly":true,"description":"The operating system this row aggregates (for example `iOS`, `Android`, `Windows`, `macOS`). Populated only when `group_by=os`. Null otherwise.","example":"iOS"},"device_type":{"type":["string","null"],"readOnly":true,"description":"The device type this row aggregates (for example `mobile`, `desktop`, `tablet`). Populated only when `group_by=device_type`. Null otherwise.","example":"mobile"},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementCounts"}]}}},"EmailStatsByClientResponse":{"type":"object","additionalProperties":false,"description":"Per-client engagement breakdown for the requested period, grouped by the requested `group_by` facet, ranked by the `sort` metric (default `unique_opens`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Client breakdown rows, ranked by the `sort` metric (default `unique_opens`) descending. Empty when no opens or clicks with a detected client occurred in the period.","items":{"$ref":"#/components/schemas/EmailClientStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct values of the requested `group_by` facet with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":9}}},"EmailBounceStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Breakdown of `bounced` by failure type. Each field counts distinct bounced recipients of that type in this row's scope; the five types approximately partition `bounced`.\n","required":["hard","soft","admin","block","undetermined"],"properties":{"hard":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients with a permanent delivery failure (invalid address or non-existent domain). The address is automatically added to the suppression list.\n","example":42},"soft":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients with a transient delivery failure (mailbox full or server temporarily unavailable). Delivery was retried.\n","example":48},"admin":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients refused by a policy at the receiving end, such as relaying denied or a blocklisted domain. Fix these by changing your content or sender configuration. Cleaning the recipient list does not usually help.\n","example":4},"block":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients bounced because the receiving mail server blocked the sending IP for reputation reasons (mail block, spam block, spam content). Triage usually focuses on IP reputation and sending volume.\n","example":6},"undetermined":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients bounced where the receiving server's response did not allow precise classification.\n","example":1}}},"EmailBounceCodeStatsPoint":{"type":"object","additionalProperties":false,"description":"Bounce counts for a single SMTP status code over the requested period, with the per-type breakdown. This is a deliverability-debugging view keyed on what the receiving mail server returned, so it only reports the failure side: bounced recipients, and their `hard`, `soft`, `admin`, `block`, and `undetermined` split. It has no delivered, open, or rate fields.\n","required":["smtp_error_code","bounced","bounces"],"properties":{"smtp_error_code":{"type":"string","minLength":1,"readOnly":true,"description":"The SMTP error code the receiving mail server returned for these bounces, as reported by that server (for example `5.1.1` for an unknown recipient, `4.2.2` for a full mailbox). The form varies by server, and the set of codes is open.","example":"5.1.1"},"bounced":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients whose delivery failed with this SMTP status code, approximately equal to the sum of the five `bounces.*` sub-counts. The two are computed independently, so they can differ slightly because of approximation.","example":1240},"bounces":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailBounceStats"}]}}},"EmailStatsByBounceCodeResponse":{"type":"object","additionalProperties":false,"description":"Per-SMTP-code bounce breakdown for the requested period, ranked by the `sort` metric (default `bounced`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Bounce-code breakdown rows, ranked by the `sort` metric (default `bounced`) descending. Empty when no bounces occurred in the period.","items":{"$ref":"#/components/schemas/EmailBounceCodeStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct SMTP error codes with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":17}}},"EmailComplaintTypeStatsPoint":{"type":"object","additionalProperties":false,"description":"Complaint counts for a single feedback-loop complaint type over the requested period. A complaint type is recorded only on spam-complaint events, so this breakdown reports the complained count for each type and nothing else. A complaint event has no delivery or engagement information attached to it, so no other count applies.\n","required":["feedback_type","complained"],"properties":{"feedback_type":{"type":"string","minLength":1,"readOnly":true,"description":"The complaint classification reported by the mailbox provider's feedback loop, in the abuse-reporting-format vocabulary (for example `abuse`, `fraud`, `virus`, `other`). The set is open.","example":"abuse"},"complained":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recipients who reported a message as spam with this complaint type at any point in the period.","example":47}}},"EmailStatsByComplaintTypeResponse":{"type":"object","additionalProperties":false,"description":"Per-complaint-type breakdown for the requested period, ranked by `complained` descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Complaint-type breakdown rows, ranked by `complained` descending. Empty when no complaints occurred in the period.","items":{"$ref":"#/components/schemas/EmailComplaintTypeStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct feedback types with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":4}}},"EmailBroadcastStatsPoint":{"type":"object","additionalProperties":false,"description":"Delivery, engagement and latency figures for one broadcast's messages over the period you asked for.","required":["broadcast_id","delivery","engagement","latency"],"properties":{"broadcast_id":{"type":"string","minLength":1,"pattern":"^eb_[0-9a-hjkmnp-tv-z]{26}$","readOnly":true,"description":"The broadcast this row covers, the same ID the broadcast endpoints return. Only mail sent as part of a broadcast has a broadcast ID, so one-off and transactional sends do not appear in this breakdown at all.","example":"eb_01krdgeqcxet5s7t44vh8rt9mg"},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]}}},"EmailStatsByBroadcastResponse":{"type":"object","additionalProperties":false,"description":"Per-broadcast breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200).","required":["period","data","total"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Broadcast breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no broadcast messages were active in the period.","items":{"$ref":"#/components/schemas/EmailBroadcastStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct broadcasts with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more.\n","example":57}}},"EmailHealthSignalThresholds":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The boundaries this signal's status was judged against. Use them to classify your own slices, such as per-domain or per-tag rates, against the same bands. The `direction` field identifies the risky side of the boundaries: `above` means the status degrades as the value rises past a boundary, as with bounce and complaint rates, and `below` means it degrades as the value falls, as with delivery rate. The boundaries are exclusive, so a value exactly on one keeps the better status. Omitted for a metric with no risk boundaries, such as open rate.\n","required":["direction","watching","throttled"],"properties":{"direction":{"type":"string","minLength":1,"readOnly":true,"description":"Which side of the boundaries is at risk. `above` for higher-is-worse rates (bounce, complaint), `below` for lower-is-worse rates (delivery).","enum":["above","below"],"x-enum-varnames":["EmailHealthSignalThresholdsDirectionAbove","EmailHealthSignalThresholdsDirectionBelow"],"example":"above"},"watching":{"type":"number","minimum":0,"maximum":1,"readOnly":true,"description":"Crossing this boundary in the risk direction moves the signal to `watching`, as a fraction.","example":0.004},"throttled":{"type":"number","minimum":0,"maximum":1,"readOnly":true,"description":"Crossing this boundary in the risk direction moves the signal to `throttled`, as a fraction.","example":0.006}}},"EmailHealthSignal":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The current value and verdict for a single sending-health metric over the window.","required":["metric","value","limit","status"],"properties":{"metric":{"type":"string","minLength":1,"readOnly":true,"description":"Which rate this signal reports.","enum":["delivery_rate","open_rate","bounce_rate","complaint_rate"],"example":"bounce_rate"},"value":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"The current rate over the window, as a fraction. Null when its denominator is zero.","example":0.004},"limit":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"The reference deliverability limit for this rate, as a fraction (for example `0.005` for a 0.5% bounce-rate limit). Null for metrics that have no limit, such as delivery rate and open rate. The verdict is classified using `thresholds`, which can differ from this reference limit.","example":0.005},"status":{"type":"string","minLength":1,"readOnly":true,"description":"This metric's individual verdict, ordered best to worst: `strong`, `healthy`, `watching`, `throttled`. `strong` applies only to `open_rate`, for an open rate well above typical. For the other rates, `healthy`, `watching`, and `throttled` indicate how close the rate is to a level that risks deliverability. The verdict follows the `thresholds` boundaries rather than the displayed reference `limit`. A signal whose `value` is null, because its denominator was zero in the window, is reported as `healthy`.\n","enum":["strong","healthy","watching","throttled"],"example":"healthy"},"thresholds":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailHealthSignalThresholds"}]}}},"EmailHealth":{"type":"object","additionalProperties":false,"description":"The workspace's current sending-health verdict over the requested window, plus reference deliverability limits and classification boundaries. Use it to render a health badge, the bounce-rate and complaint-rate limit labels, and the risk lines on deliverability charts without hard-coding any thresholds of your own.\n","required":["period","status","signals"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the verdict was computed over, echoed back from the request."},"status":{"type":"string","minLength":1,"readOnly":true,"description":"Overall sending-health verdict for the window, taken as the worst status among the bounce-rate, complaint-rate, and delivery-rate signals. The open-rate signal, which can be `strong`, is not part of this roll-up. The overall verdict is one of `healthy`, `watching`, or `throttled`. It is `healthy` when the other three signals are each healthy or better. It is `watching` when at least one is watching, and `throttled` when at least one is throttled. This verdict describes deliverability risk. It never pauses your sending on its own.\n","enum":["healthy","watching","throttled"],"example":"healthy"},"signals":{"type":"array","minItems":4,"readOnly":true,"description":"The per-rate signals include `delivery_rate`, `open_rate`, `bounce_rate`, and `complaint_rate`. Read a signal by matching on its `metric`. Each entry carries its current value, a reference deliverability limit (null where no limit applies), and its own verdict. Delivery rate, bounce rate, and complaint rate also carry the thresholds their verdict was classified against; open rate does not, because a high open rate is never a risk.\n","items":{"$ref":"#/components/schemas/EmailHealthSignal"}}},"example":{"period":{"data_as_of":null,"from":"2026-05-25","to":"2026-06-01"},"status":"watching","signals":[{"metric":"delivery_rate","value":0.995,"limit":null,"status":"healthy","thresholds":{"direction":"below","throttled":0.984,"watching":0.99}},{"metric":"open_rate","value":0.20100503,"limit":null,"status":"healthy"},{"metric":"bounce_rate","value":0.005,"limit":0.005,"status":"watching","thresholds":{"direction":"above","throttled":0.006,"watching":0.004}},{"metric":"complaint_rate","value":0.00010050251,"limit":0.003,"status":"healthy","thresholds":{"direction":"above","throttled":0.001,"watching":0.0006}}]}},"DomainSettings":{"type":"object","additionalProperties":false,"description":"Per-domain behavior toggles. Changes apply immediately to new sends.\n","properties":{"click_tracking":{"type":"boolean","default":false,"description":"Rewrite links in HTML email through your tracking domain to record clicks. You can enable this before your tracking domain has verified; it begins working once verification completes. A tracking domain must be configured; enabling it without one returns `409`.\n"},"open_tracking":{"type":"boolean","default":false,"description":"Insert a tracking pixel in HTML email to record opens. You can enable this before your tracking domain has verified: it begins working once verification completes. A tracking domain must be configured; enabling it without one returns `409`.\n"}}},"DomainDKIM":{"type":"object","additionalProperties":false,"description":"Active DKIM signing configuration for the domain.","required":["mode","selector","key_size"],"properties":{"mode":{"type":"string","readOnly":true,"minLength":1,"enum":["txt","delegated"],"description":"How the DKIM public key is published in your DNS. `txt`: you publish the key as a TXT record. `delegated`: you publish a single CNAME and we host and rotate the key.\n"},"selector":{"type":"string","readOnly":true,"minLength":1,"description":"DKIM selector used to sign mail from this domain.","example":"bird1"},"key_size":{"type":"integer","readOnly":true,"description":"RSA key size in bits.","example":2048}}},"DomainCapabilityPending":{"type":"object","additionalProperties":false,"description":"A staged configuration change awaiting DNS verification. The currently active configuration keeps serving until the staged one verifies, at which point it is promoted automatically. Submitting another change for the same capability replaces the staged value.\n","required":["domain","status"],"properties":{"domain":{"type":"string","readOnly":true,"minLength":1,"description":"Hostname the capability uses after the staged change verifies.","example":"rp.mail.acme.com"},"status":{"type":"string","readOnly":true,"minLength":1,"description":"Verification status of the staged change.\n\n- `pending`: the DNS records have not been detected yet.\n- `failed`: the records resolved with wrong values; correct them\n  or submit a different change.\n- `temporary_failure`: the DNS lookup failed transiently and is\n  queued for retry.\n","enum":["pending","failed","temporary_failure"],"example":"pending"}}},"DomainCapability":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","minLength":1,"readOnly":true,"description":"Capability verification status.\n\n- `pending`: verification has not run, or is currently running.\n- `verified`: all DNS records for this capability resolved with the\n  expected values.\n- `warning`: a record for this capability verified before and a recent\n  check no longer matches, but it is still within the grace period.\n  Sending is not yet affected; fix it before the grace period ends.\n- `failed`: DNS records resolved but at least one value is wrong.\n  Update your DNS to recover.\n- `temporary_failure`: DNS lookup failed transiently. Verification retries\n  automatically; do not change DNS records unless they are incorrect.\n- `not_configured`: the capability is not set up on this domain\n  (for example, no tracking domain configured).\n","enum":["pending","verified","warning","failed","temporary_failure","not_configured"],"example":"verified"},"domain":{"type":["string","null"],"readOnly":true,"description":"Hostname this capability is configured with: the return-path domain, the tracking domain, or the domain where the DMARC policy was found. `null` when not applicable or not configured.\n"},"pending":{"$ref":"#/components/schemas/DomainCapabilityPending"},"reason":{"type":["string","null"],"readOnly":true,"description":"Machine-readable reason code for a failed capability status. Only set when\n`status` is `failed`. Use this to display a specific message to users rather\nthan a generic failure message.\n\n- `tracking_domain_in_use`: the link tracking subdomain is already claimed\n  by another organization.\n"}}},"DomainCapabilities":{"type":"object","additionalProperties":false,"required":["sending","return_path","dmarc","tracking"],"properties":{"sending":{"$ref":"#/components/schemas/DomainCapability","description":"Overall authorization to send from this domain. Verified when the DKIM record, the return-path CNAME, and a DMARC policy are all in place. Required for live sends.\n"},"return_path":{"$ref":"#/components/schemas/DomainCapability","description":"Return-path (bounce) CNAME verification. The return-path domain receives bounce and complaint notifications and is what mailbox providers check for SPF: no separate SPF record is needed.\n"},"dmarc":{"$ref":"#/components/schemas/DomainCapability","description":"DMARC policy check. Satisfied by any valid DMARC record covering the sending domain: on the domain itself or on its registered (organizational) domain; `domain` reports where the policy was found. A minimal policy of `p=none` is sufficient.\n"},"tracking":{"$ref":"#/components/schemas/DomainCapability","description":"Branded open/click tracking domain. `not_configured` until a tracking domain is set. Tracked links are served over HTTPS once the CNAME verifies.\n"},"inbound":{"$ref":"#/components/schemas/DomainCapability","description":"Inbound mail receiving. `not_configured` until receiving is enabled on this domain (see `DomainUpdate.inbound`), then `pending` while the published MX records are checked, and `verified` once they resolve to us. The MX records to publish are always listed under `dns_records` (`purpose: inbound_mx`) as a regional reference, even while this is `not_configured`: enabling is what actually starts delivery.\n"}}},"DNSRecord":{"type":"object","additionalProperties":false,"required":["type","name","host","value","purpose","state","optional","status"],"properties":{"type":{"type":"string","minLength":1,"description":"The DNS record type to publish, determined by `purpose`.\n\n- `TXT`: used for the `dkim` and `dmarc` purposes.\n- `CNAME`: used for the `return_path` and `tracking` purposes.\n- `MX`: used for the `inbound_mx` purpose.\n","enum":["TXT","CNAME","MX"]},"name":{"type":"string","minLength":1,"description":"The record name: the part you enter in your DNS provider's `Name` or `Host` field, relative to the DNS zone the record belongs in (your registered domain). For a sending domain `mail.acme.com` the DKIM record name is `bird1._domainkey.mail`, entered in the `acme.com` zone. `@` for records at the zone apex.\n"},"host":{"type":"string","minLength":1,"description":"The fully qualified hostname for this record (for example, `bird1._domainkey.mail.acme.com`).\n"},"value":{"type":"string","minLength":1,"description":"The value to publish, as entered in your DNS provider's `Value` or `Content` field. For `TXT`, enter the full record content. For `CNAME`, enter the target hostname. For `MX`, enter the priority followed by the mail server hostname.\n"},"purpose":{"type":"string","minLength":1,"description":"What this record is for.\n\n- `dkim`: signs outbound mail and proves domain ownership.\n- `return_path`: identifies the return-path (bounce) CNAME for sending.\n- `tracking`: identifies the optional branded open/click tracking CNAME.\n- `inbound_mx`: identifies the MX record routing mail to us for receiving.\n  Always present wherever inbound is available, as a regional reference,\n  regardless of whether receiving is enabled; publishing it does not\n  enable receiving on its own: see `DomainUpdate.inbound`. It is\n  `optional` until receiving is enabled, and publishing it before then\n  is destructive: on a domain at the zone apex it replaces the MX\n  records that carry the domain's existing mail.\n- `dmarc`: identifies the advisory DMARC policy record.\n","enum":["dkim","return_path","tracking","inbound_mx","dmarc"]},"state":{"type":"string","minLength":1,"readOnly":true,"description":"Lifecycle state of this record.\n\n- `active`: the record backs the domain's current configuration.\n- `pending`: the record belongs to a staged configuration change;\n  publish it to complete the change.\n- `deprecated`: the record belonged to a previous configuration.\n  Keep it in DNS until `safe_to_remove` is `true`; in-flight mail and\n  previously sent tracked links may still resolve through it.\n","enum":["active","pending","deprecated"]},"optional":{"type":"boolean","readOnly":true,"description":"Whether this record can be skipped. An optional record enables extra functionality (branded tracking, or receiving) rather than sending, so publish one only when you want what it enables. The `inbound_mx` records are optional until you enable receiving on the domain, and publishing one before then changes where mail to the domain is delivered.\n"},"status":{"type":"string","minLength":1,"readOnly":true,"description":"Verification status of this record's most recent DNS check.\n\n- `pending`: the record has not verified yet; publish it (or correct it)\n  and it verifies on the next check.\n- `verified`: the most recent check matched the expected value.\n- `warning`: the record verified before and a recent check no longer\n  matched, but it is still within the grace period. Sending is not yet\n  affected; fix the record before the grace period ends to avoid it\n  being blocked.\n- `failed`: the record verified before but later checks kept failing\n  past the grace period; the configuration has regressed and needs\n  attention.\n","enum":["pending","verified","warning","failed"]},"error":{"type":["string","null"],"readOnly":true,"description":"Human-readable detail for a check that did not pass on this record: what was found in DNS and why it did not match. Also set while `pending` when the record is published but does not match the expected value, which is the case you can act on. `null` when the record is `verified`, when nothing is published at this name yet, or before the first check.\n"},"safe_to_remove":{"type":["boolean","null"],"readOnly":true,"description":"Only set on `deprecated` records: `true` once the record is no longer referenced by in-flight mail or live tracked links and can be deleted from your DNS. `null` on `active` and `pending` records.\n"}}},"Domain":{"type":"object","additionalProperties":false,"required":["id","workspace_id","domain","vendor","status","settings","dkim","capabilities","dns_records","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/DomainID"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The sending domain name. Set at creation and immutable.","example":"mail.acme.com"},"vendor":{"type":"string","minLength":1,"readOnly":true,"description":"The DNS provider hosting this domain's nameservers, so you know which provider's dashboard to manage the required DNS records in. Returns `other` when the provider has not been detected or is not recognized.\n","enum":["other","cloudflare","route53","godaddy","namecheap","google","azure","digitalocean","squarespace"]},"status":{"type":"string","minLength":1,"readOnly":true,"description":"Domain ownership verification, proven by the DKIM record. Readiness to\nsend or track is reported separately per capability under\n`capabilities.*.status`.\n\n- `pending`: the DKIM record has not been published yet.\n- `verified`: the DKIM record is in place; ownership is confirmed.\n- `failed`: a DKIM record exists but does not match the expected\n  value (for example a stale record from an earlier setup), or a\n  previously verified record was removed. Correct the record to\n  recover.\n- `temporary_failure`: DNS resolution failed transiently, such as from a\n  timeout or unreachable nameserver. Verification retries automatically;\n  do not change the DNS records unless they are incorrect.\n- `rejected`: the domain was refused for policy reasons and cannot be\n  used for sending. Contact support if you believe this is an error.\n","enum":["pending","verified","failed","temporary_failure","rejected"]},"settings":{"$ref":"#/components/schemas/DomainSettings"},"next":{"type":"array","readOnly":true,"description":"What to do next about this domain, given the state it is in. Each entry names one action and says\nwhy it is worth taking, so you can act on this response without working out the order\nyourself. Present on reads that compute it: an empty list means there is nothing to do,\nand the field is absent entirely on responses that do not report next actions.\n\nThis answers whether you own the domain, which is what `status` reports. What each\ncapability still needs before it can send or receive is reported separately under\n`capabilities`, so an empty list here does not on its own mean the domain is ready.\n","items":{"$ref":"#/components/schemas/NextAction"}},"dkim":{"readOnly":true,"$ref":"#/components/schemas/DomainDKIM"},"capabilities":{"$ref":"#/components/schemas/DomainCapabilities"},"dns_records":{"type":"array","readOnly":true,"description":"The domain's DNS records and their individual verification state, returned in full on both the list and single-domain responses. This is the complete set to publish across DKIM, return-path, DMARC, tracking, and inbound; records for a staged change carry `state: pending`. Inbound MX records are always included as a regional reference, even while receiving is off and `capabilities.inbound.status` is `not_configured`. Their presence alone does not mean receiving is enabled; see `DomainUpdate.inbound`.\n","items":{"$ref":"#/components/schemas/DNSRecord"}},"last_checked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When we last checked this domain's DNS records, whether or not the outcome changed. Updated on every verification: your manual refresh and the periodic automatic re-checks alike. `null` if the domain has never been checked.\n"},"verified_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the domain's ownership was confirmed: the moment `status` became `verified` via the DKIM record. Unchanged by later re-checks while it stays verified. `null` if the domain has never been verified.\n"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the domain was added."},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the domain's configuration was last changed (such as a settings or return-path change). Verification re-checks do not change this; see `last_checked_at` and `verified_at` for verification timing.\n"}}},"DomainList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of sending domains, newest first by default.","items":{"$ref":"#/components/schemas/Domain"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"DomainReturnPathConfig":{"type":"object","additionalProperties":false,"required":["name"],"description":"Return-path (bounce) domain configuration. The return-path domain receives bounce and complaint notifications for mail sent from this domain and is what mailbox providers check for SPF. Provide only the name part; we add the sending domain automatically.\n","properties":{"name":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$","description":"Name part to use for the return-path domain. For example, `send` on `mail.acme.com` becomes `send.mail.acme.com`. Defaults to `send` when omitted at creation.\n","example":"send"}}},"DomainTrackingConfig":{"type":"object","additionalProperties":false,"required":["name"],"description":"Tracking domain configuration for branded open and click tracking URLs. Provide only the name part; we add the sending domain automatically. A domain created with no tracking configuration defaults to `links`. Tracked links are served over HTTPS after the tracking record verifies.\n","properties":{"name":{"type":"string","minLength":1,"maxLength":63,"pattern":"^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$","description":"Name part to use for branded open and click tracking URLs. For example, `links` on `mail.acme.com` becomes `links.mail.acme.com`.\n","example":"links"}}},"DomainDKIMConfig":{"type":"object","additionalProperties":false,"description":"DKIM signing configuration.","properties":{"mode":{"type":"string","enum":["txt","delegated"],"default":"txt","description":"How the DKIM public key is published in your DNS.\n\n- `txt` (default): you publish the DKIM public key as a TXT record. Key\n  rotation requires updating the record.\n- `delegated`: you publish a CNAME that points to a DKIM key we host and\n  rotate. This mode is unavailable for new configurations; supplying it\n  returns `422`.\n"}}},"DomainCreate":{"type":"object","additionalProperties":false,"required":["domain"],"properties":{"domain":{"type":"string","format":"hostname","minLength":1,"description":"The domain you send from: the domain of your `from` addresses. Use a dedicated subdomain (for example, `mail.acme.com`) rather than your registered domain so sending reputation stays separate from other services on the domain.\n","example":"mail.acme.com"},"return_path":{"$ref":"#/components/schemas/DomainReturnPathConfig"},"tracking":{"$ref":"#/components/schemas/DomainTrackingConfig"},"dkim":{"$ref":"#/components/schemas/DomainDKIMConfig"},"settings":{"$ref":"#/components/schemas/DomainSettings"}},"example":{"domain":"mail.acme.com"}},"DomainInboundConfig":{"type":"object","additionalProperties":false,"required":["enabled"],"description":"Inbound (receiving) configuration. Enable inbound to receive email addressed to this domain. We return MX records to publish. After they verify, mail to any local-part at this domain is delivered as an inbound message and triggers the `email.received` webhook. Use a dedicated subdomain, such as `inbound.acme.com`, because using your apex domain would capture your corporate mail.\n","properties":{"enabled":{"type":"boolean","description":"Set `true` to enable receiving on this domain, `false` to disable it. Disabling tears receiving down and removes the MX records from `dns_records`; this is immediate in the normal case, and if a step needs retrying the capability clears as soon as teardown finishes.\n","example":true}}},"DomainUpdate":{"type":"object","additionalProperties":false,"description":"Partial update. `settings` changes apply immediately. Changes to `return_path`, `tracking`, or `dkim` on a verified capability are staged. The current configuration keeps serving until the new DNS records verify. The change is then promoted automatically and the old records are marked `deprecated`. The staged value is visible under `capabilities.*.pending` and can be replaced by submitting another change.\n","properties":{"settings":{"$ref":"#/components/schemas/DomainSettings"},"return_path":{"$ref":"#/components/schemas/DomainReturnPathConfig","description":"Change the return-path name part. Cannot be removed: the return-path is required for sending.\n"},"tracking":{"oneOf":[{"$ref":"#/components/schemas/DomainTrackingConfig"},{"type":"null"}],"description":"Set or change the tracking name part, or remove tracking by passing `null`. Removal requires `click_tracking` and `open_tracking` to be disabled first, and returns `409` otherwise. After removal, links in previously sent email keep resolving while the tracking records are reported as `deprecated`.\n"},"dkim":{"$ref":"#/components/schemas/DomainDKIMConfig","description":"Change how the DKIM key is published. The current key keeps signing until the new configuration verifies, so mail is never sent unsigned during the transition.\n"},"inbound":{"$ref":"#/components/schemas/DomainInboundConfig","description":"Enable or disable receiving on this domain. Enabling claims the domain for inbound and moves `capabilities.inbound.status` from `not_configured` to `pending`, then `verified` once the MX records resolve to us. The MX records to publish are always present under `dns_records` (`purpose: inbound_mx`) as a regional reference. Their presence does not mean receiving is enabled; enable the domain whenever `capabilities.inbound.status` is `not_configured`. Enabling requires the domain's DKIM to be verified first. A fresh enable on a domain whose DKIM is not verified returns `422` with `E05019` and claims nothing. A domain already receiving inbound for another organization returns `422` with `E05018`.\n"}},"example":{"settings":{"click_tracking":true,"open_tracking":true},"tracking":{"name":"links"}}},"DomainEventID":{"type":"string","minLength":1,"pattern":"^dev_[0-9a-hjkmnp-tv-z]{26}$","example":"dev_01krdgeqcxet5s7t44vh8rt9mg"},"DomainEvent":{"type":"object","additionalProperties":false,"required":["id","type","summary","metadata","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/DomainEventID","description":"Event ID."},"type":{"type":"string","minLength":1,"description":"Type of domain event. `domain.status_changed` tracks ownership verification through the domain-level `status`. `domain.sending_status_changed` tracks readiness to send through `capabilities.sending`. The remaining `*_status_changed` types each track one DNS record's verification. Open enum: new event types may be added over time, so treat any unrecognized value as a future event rather than an error. The values below are the types known at this version.","x-extensible-enum":["domain.registered","domain.settings_updated","domain.return_path_changed","domain.tracking_changed","domain.tracking_removed","domain.status_changed","domain.sending_status_changed","domain.dkim_status_changed","domain.dmarc_status_changed","domain.return_path_status_changed","domain.tracking_status_changed"],"example":"domain.status_changed"},"summary":{"type":"string","minLength":1,"description":"Human-readable summary of what changed.","example":"Domain verified — ownership confirmed."},"metadata":{"type":"object","description":"Structured details for the event. Status-change events carry `from` and `to`; record-level changes also carry `domain`, the affected hostname.","additionalProperties":true},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"When the event was recorded."}}},"DomainEventList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of domain events, newest first by default.","items":{"$ref":"#/components/schemas/DomainEvent"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"ShareDomainDnsRequest":{"type":"object","additionalProperties":false,"required":["emails"],"properties":{"emails":{"type":"array","minItems":1,"maxItems":3,"description":"Email recipients for the domain's current DNS records. The first address is the direct recipient and the rest are copied on the same email; duplicates are ignored. Any invalid address fails the whole request with `422`.","items":{"type":"string","format":"email","example":"alice@example.com"}}},"example":{"emails":["alice@example.com","bob@example.com"]}},"DedicatedIPID":{"type":"string","minLength":1,"pattern":"^dip_[0-9a-hjkmnp-tv-z]{26}$","example":"dip_01krdgeqcxet5s7t44vh8rt9mg"},"DedicatedIP":{"type":"object","additionalProperties":false,"required":["id","organization_id","address","hostname","status","warmup_progress","ip_pool_id","purchased_at","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/DedicatedIPID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"address":{"type":"string","readOnly":true,"minLength":1,"description":"The dedicated IP address.","example":"192.0.2.10"},"hostname":{"type":"string","readOnly":true,"minLength":1,"description":"The IP's reverse-DNS (PTR) hostname, used as the HELO/EHLO name. Add it to any allowlist that gates on sending hostname.","example":"mta12.delivery.example.net"},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["warming","active","suspended","pending_cancellation"],"description":"Where the IP stands.\n\n- `warming`: it is ramping up sending volume automatically; track\n  progress with `warmup_progress`.\n- `active`: warmup is complete and the IP sends at full volume.\n- `suspended`: sending from it is paused, for example for abuse or\n  a billing issue.\n- `pending_cancellation`: cancellation has been requested: the IP\n  stays in its pool and usable until `cancels_at`, when it is removed.\n"},"warmup_progress":{"type":"integer","readOnly":true,"minimum":0,"maximum":100,"description":"How far automatic warmup has progressed, from 0 to 100. Reaches 100 exactly when `status` becomes `active`."},"warmup_started_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When automatic warmup began for this IP."},"warmup_completed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When automatic warmup finished and the IP became active. `null` while warmup is still in progress."},"ip_pool_id":{"$ref":"#/components/schemas/IPPoolID","description":"The pool this IP belongs to. A dedicated IP is always in exactly one pool."},"purchased_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"cancels_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the IP will be released and removed, once cancellation has been requested. `null` while it is not pending cancellation."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"DedicatedIPList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DedicatedIP"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"IPPoolSummary":{"description":"A named group of dedicated IPs that share a sending reputation for email, without the member IP addresses. Fetch those with `GET /v1/organization/ip-pools/{ip_pool_id}`.\n","allOf":[{"type":"object","required":["id","organization_id","name","is_default","protected","ip_count","active_count","warming_count","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/IPPoolID","description":"IP pool ID."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"ID of the organization the pool belongs to."},"name":{"type":"string","minLength":1,"description":"The pool's name, unique within the organization.","example":"Transactional"},"is_default":{"type":"boolean","description":"Whether sends that do not specify a pool use this pool. Exactly one pool per organization is the default; when no dedicated pool is chosen, the shared pool is the default."},"protected":{"type":"boolean","readOnly":true,"description":"Whether this pool is managed by us. For a shared pool, only its default status can be changed. It cannot be renamed, deleted, or have dedicated IPs assigned to it."},"ip_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Total number of dedicated IPs assigned to this pool."},"active_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Number of dedicated IPs in this pool with active status."},"warming_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Number of dedicated IPs in this pool currently in warming status."}}},{"$ref":"#/components/schemas/Timestamps"}]},"IPPoolList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/IPPoolSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"IPPool":{"description":"A named group of dedicated IPs that share a sending reputation for email. Sends select a pool by name; sends that do not specify one use the organization's default.\n","allOf":[{"type":"object","required":["id","organization_id","name","is_default","protected","ips","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/IPPoolID","description":"IP pool ID."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"ID of the organization the pool belongs to."},"name":{"type":"string","minLength":1,"description":"The pool's name, unique within the organization. Returns `IPPoolNameTaken` (E11002) if another pool already uses it. Immutable on the protected shared pool.","example":"Transactional"},"is_default":{"type":"boolean","description":"Whether sends that do not specify a pool use this pool. Exactly one pool per organization is the default; when no dedicated pool is chosen, the shared pool is the default."},"protected":{"type":"boolean","readOnly":true,"description":"Whether this pool is managed by us. For a shared pool, only its default status can be changed. It cannot be renamed, deleted, or have dedicated IPs assigned to it."},"ips":{"type":"array","items":{"$ref":"#/components/schemas/DedicatedIP"},"description":"The dedicated IPs assigned to this pool."}}},{"$ref":"#/components/schemas/Timestamps"}]},"DedicatedIPCreate":{"type":"object","additionalProperties":false,"properties":{"quantity":{"type":"integer","minimum":1,"default":1,"description":"Number of IPs to provision. Defaults to 1 when omitted."},"ip_pool_id":{"$ref":"#/components/schemas/IPPoolID","description":"The pool to provision the new IPs into. Required when the organization already has IP pools; omit it on the first purchase to create one automatically."}},"example":{"quantity":2,"ip_pool_id":"ipp_01krdgeqcxet5s7t44vh8rt9mg"}},"IPResourcePurchaseResult":{"type":"object","additionalProperties":false,"required":["status","shortfall_amount","currency_code"],"properties":{"status":{"type":"string","enum":["pending_topup","requires_topup"],"minLength":1,"description":"`pending_topup`: an off-session card charge was initiated; the resource provisions automatically once it settles. `requires_topup`: top up the wallet to proceed (no card on file).\n"},"pending_operation_id":{"type":"string","description":"The parked operation. Subscribe to `organization.pending_operation.*` for its outcome."},"shortfall_amount":{"type":"string","minLength":1,"description":"Amount the wallet is short by, in units of `currency_code`. Pre-fill the top-up with at least this amount to proceed.\n","example":"12.34"},"currency_code":{"type":"string","minLength":1,"description":"ISO 4217 currency of the shortfall (the wallet currency)."}}},"DedicatedIPPurchaseResponse":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DedicatedIP"},"description":"The purchased dedicated IPs."}}},"DedicatedIPAssign":{"type":"object","additionalProperties":false,"required":["ip_pool_id"],"properties":{"ip_pool_id":{"$ref":"#/components/schemas/IPPoolID","description":"The pool to move the dedicated IP into."}},"example":{"ip_pool_id":"ipp_01krdgeqcxet5s7t44vh8rt9mg"}},"IPPoolCreate":{"type":"object","additionalProperties":false,"description":"Parameters for creating an IP pool.","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"The pool's name, unique within the organization. Returns `IPPoolNameTaken` (E11002) if another pool already uses it.","example":"Transactional"}},"example":{"name":"Transactional"}},"IPPoolUpdate":{"type":"object","additionalProperties":false,"description":"Fields to update on an IP pool. Omitted fields are unchanged.","properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"The pool's name, unique within the organization. Returns `IPPoolNameTaken` (E11002) if another pool already uses it. Immutable on the protected shared pool.","example":"Transactional"},"is_default":{"type":"boolean","description":"Make this pool the organization's default. The default moves to this pool; it is never cleared."}},"example":{"name":"Marketing"}},"SuppressionReasonFilter":{"type":"string","enum":["hard_bounce","complaint","unsubscribe","manual"]},"SuppressionScopeTypeFilter":{"type":"string","enum":["workspace","category","audience","topic","contact","domain"]},"SuppressionID":{"type":"string","minLength":1,"pattern":"^sup_[0-9a-hjkmnp-tv-z]{26}$","example":"sup_01krdgeqcxet5s7t44vh8rt9mg"},"SuppressionScopeType":{"type":"string","minLength":1,"enum":["workspace","category","audience","topic","contact","domain"],"description":"How widely the email suppression applies. Responses use `workspace`. The values `category`, `audience`, `topic`, `contact`, and `domain` are reserved and have no records. The record's `applies_to` field determines which message categories are blocked.\n"},"SuppressionScope":{"type":"object","additionalProperties":false,"required":["type","id"],"properties":{"type":{"$ref":"#/components/schemas/SuppressionScopeType"},"id":{"type":"string","minLength":1,"description":"Public ID or alias of the scoped resource. For workspace scope, this is the workspace ID.\n","example":"ws_01krdgeqcxet5s7t44vh8rt9mg"}}},"Suppression":{"type":"object","additionalProperties":false,"required":["id","email","scope","reason","origin","applies_to","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SuppressionID"},"email":{"type":"string","format":"email","minLength":5,"description":"The suppressed address, stored lowercase.","example":"user@example.com"},"scope":{"$ref":"#/components/schemas/SuppressionScope"},"reason":{"type":"string","minLength":1,"x-extensible-enum":["hard_bounce","complaint","manual","unsubscribe"],"description":"Why the address is suppressed:\n\n- `hard_bounce`: A delivery permanently failed.\n- `complaint`: The recipient reported a message as spam.\n- `manual`: Added through the API or dashboard.\n- `unsubscribe`: The recipient opted out. Deprecated, and no new record carries it: an opt-out is a messaging preference rather than a suppression. Legacy records remain visible until they are moved to messaging preferences.\n\nAn address can hold one record per reason. This list grows over time. Treat unknown values as informational rather than rejecting the record.\n"},"origin":{"type":"string","minLength":1,"x-extensible-enum":["bounce_event","complaint_event","api_key","user","unsubscribe_event","unsubscribe_link"],"description":"How the suppression came to exist:\n\n- `bounce_event`: Created automatically from a hard bounce.\n- `complaint_event`: Created from a spam complaint.\n- `api_key`: Added through the API with an API key.\n- `user`: Added by a user in the dashboard.\n- `unsubscribe_event`: The mailbox provider reported an opt-out. Deprecated with `reason: unsubscribe`.\n- `unsubscribe_link`: The recipient used a Bird unsubscribe link. Deprecated with `reason: unsubscribe`.\n\nThis list grows over time. Treat unknown values as informational rather than rejecting the record.\n"},"applies_to":{"type":"string","minLength":1,"x-extensible-enum":["all","non_transactional","category"],"description":"Which sends the suppression blocks.\n\n- `all`: blocks every message category, including transactional.\n- `non_transactional`: blocks marketing but allows transactional messages.\n  A recipient who complained can therefore still receive\n  mail such as password resets.\n- `category`: scopes the block to a preference category and blocks every\n  category until one is set.\n\nThis list grows over time, and any value other than `non_transactional`\nblocks every category, so treat an unknown value as blocking the send.\n"},"source_email_id":{"description":"ID of the email that triggered suppression. Null for manual additions.","oneOf":[{"$ref":"#/components/schemas/EmailID"},{"type":"null"}]},"source_recipient_id":{"description":"ID of the recipient event that triggered suppression. Null for manual additions.","oneOf":[{"$ref":"#/components/schemas/RecipientID"},{"type":"null"}]},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the address was suppressed."}}},"SuppressionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of suppression records.","items":{"$ref":"#/components/schemas/Suppression"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"SuppressionCreate":{"type":"object","additionalProperties":false,"required":["email"],"properties":{"email":{"type":"string","format":"email","minLength":5,"description":"The address to stop sending to. Normalized before storage and matching: lowercased and trimmed of surrounding whitespace.\n","example":"user@example.com"}}},"SuppressionBulkCreate":{"type":"object","additionalProperties":false,"required":["suppressions"],"properties":{"suppressions":{"type":"array","minItems":1,"maxItems":1000,"items":{"$ref":"#/components/schemas/SuppressionCreate"},"description":"Addresses to suppress, one row per address, in the same shape as `POST /v1/email/suppressions`. Each row is applied independently through the same idempotent-by-address rule as a single create, and reported at the same position in the response."}},"example":{"suppressions":[{"email":"jane@example.com"},{"email":"alice@example.com"}]}},"SuppressionBulkResultItem":{"type":"object","description":"One row's result. Which optional fields accompany the outcome is stated per field rather than encoded as a `oneOf`: `outcome` is an extensible enum, so a closed discriminated union would turn every future outcome into a breaking change.","additionalProperties":false,"required":["outcome"],"properties":{"outcome":{"$ref":"#/components/schemas/SuppressionBulkOutcome"},"suppression":{"$ref":"#/components/schemas/Suppression","description":"The created or already-existing suppression. Absent when the row errored, or on `already_suppressed` when the existing record predates address normalization and cannot be rendered; the outcome still stands."},"error":{"$ref":"#/components/schemas/BulkRowError","description":"Why this row errored. Absent for every other outcome."}}},"SuppressionBulkResult":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SuppressionBulkResultItem"},"description":"One entry per address in the request, in submission order."}}},"EmailUnsubscribePageColor":{"type":"string","minLength":1,"maxLength":7,"pattern":"^#[0-9a-fA-F]{6}$","description":"A color on the hosted unsubscribe page, as a hex value like #0f172a.\n"},"EmailUnsubscribePage":{"type":"object","additionalProperties":false,"required":["name","background_color","text_color","accent_color","example_url"],"description":"Workspace-wide settings for the hosted unsubscribe page shown to recipients of marketing email.\n","properties":{"name":{"type":["string","null"],"minLength":1,"maxLength":100,"description":"The sender name the page shows. It completes the sentence \"Stop receiving marketing emails from …\". Null means the page shows your company name instead.\n"},"background_color":{"description":"The page's background color as a hex value like #fafaf9. Null uses the built-in style. Setting any color gives the page one fixed palette and turns off its automatic dark theme.\n","oneOf":[{"$ref":"#/components/schemas/EmailUnsubscribePageColor"},{"type":"null"}]},"text_color":{"description":"The page's text color as a hex value like #100f0c. Null uses the built-in style.\n","oneOf":[{"$ref":"#/components/schemas/EmailUnsubscribePageColor"},{"type":"null"}]},"accent_color":{"description":"The color of the page's confirm button as a hex value like #100f0c. Null uses the built-in style.\n","oneOf":[{"$ref":"#/components/schemas/EmailUnsubscribePageColor"},{"type":"null"}]},"example_url":{"type":["string","null"],"format":"uri","minLength":1,"readOnly":true,"description":"A link that previews the live hosted page for the sample recipient `recipient@example.com` with the current settings. The preview is side-effect free: it never opts anyone out or changes the suppression list. Null when the workspace has no hosted page to preview.\n"}}},"EmailUnsubscribePageUpdate":{"type":"object","additionalProperties":false,"description":"Partial update of the unsubscribe-page settings. Omitted fields keep their current value.\n","properties":{"name":{"type":["string","null"],"minLength":1,"maxLength":100,"description":"Set the sender name the page shows. Pass null to clear it, so the page shows your company name again.\n"},"background_color":{"description":"Set the page's background color as a hex value like #fafaf9. Setting any color gives the page one fixed palette and turns off its automatic dark theme. Pass null to return to the built-in style.\n","oneOf":[{"$ref":"#/components/schemas/EmailUnsubscribePageColor"},{"type":"null"}]},"text_color":{"description":"Set the page's text color as a hex value like #100f0c. Pass null to return to the built-in style.\n","oneOf":[{"$ref":"#/components/schemas/EmailUnsubscribePageColor"},{"type":"null"}]},"accent_color":{"description":"Set the color of the page's confirm button as a hex value like #100f0c. Pass null to return to the built-in style.\n","oneOf":[{"$ref":"#/components/schemas/EmailUnsubscribePageColor"},{"type":"null"}]}}},"EmailCompetitivePeriod":{"type":"object","additionalProperties":false,"description":"The period every figure in the response covers, echoed back from the request.\n\nFigures are fetched when the request is made, so they are current as of `to`.\nThe period always ends at the moment of the request rather than at a cached\nboundary, which is why two requests a minute apart can differ slightly.\n","required":["days","from","to"],"properties":{"days":{"type":"integer","readOnly":true,"description":"Length of the period in days.","example":30},"from":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"Start of the period, inclusive.","example":"2026-07-13T09:00:00Z"},"to":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"End of the period, exclusive. Daily figures therefore run through the previous whole UTC day and never include the one in progress.\n","example":"2026-08-12T09:00:00Z"}}},"EmailCompetitiveWatchlistSummary":{"type":"object","additionalProperties":false,"description":"Where your sending sits against the brands you watch, over the same period as the\nrows.\n\nEvery figure here is derived from those rows rather than measured separately, so\nthe two always agree. As on a row, each is present and `null` when the rows cannot\nsupport it: the peer medians need at least one watched brand the panel reported\non, and the share figures need sending of your own to compare.\n","required":["share_of_volume_percent","share_of_volume_change_points","competitor_sends","competitor_sends_change_percent","peer_cadence_median_per_week","peer_inbox_placement_median_rate"],"properties":{"share_of_volume_percent":{"type":["number","null"],"readOnly":true,"description":"Your share of everything the watched set sent over the period, your own sending included in the total. Your half of the ratio is an exact count of your own sending while the rest is the panel's estimate, so the two sides are measured differently.\n","example":10.5},"share_of_volume_change_points":{"type":["number","null"],"readOnly":true,"description":"How that share moved against the period immediately before, in percentage points. A share that went from 11.7 to 10.5 reports -1.2.\n","example":-1.2},"competitor_sends":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Estimated volume the watched brands sent between them, excluding your own sending. A panel estimate, so read it as an order of magnitude rather than a count.\n","example":4240000},"competitor_sends_change_percent":{"type":["number","null"],"readOnly":true,"description":"Change in that volume against the period immediately before.","example":12},"peer_cadence_median_per_week":{"type":["number","null"],"readOnly":true,"description":"Median campaigns per week across the brands you watch, per sending domain. Your own row is excluded, since it is the figure being held against this one.\n","example":4.4},"peer_inbox_placement_median_rate":{"type":["number","null"],"readOnly":true,"description":"Median inbox placement across the brands you watch. Your own row is excluded, as with the cadence median.\n","example":0.892}}},"CompetitiveWatchlistBrandID":{"type":"string","minLength":1,"pattern":"^cwb_[0-9a-hjkmnp-tv-z]{26}$","example":"cwb_01krdgeqcxet5s7t44vh8rt9mg"},"EmailCompetitivePanelStatus":{"type":"string","minLength":1,"description":"Whether panel figures are available for a row, and when they are not, why.\n\n`ok` means the panel reported figures for the requested period. `not_in_panel`\nmeans the panel does not track the sending domain at all, which is common for\nsmaller and newer senders. `no_data` means the panel tracks the domain but\nobserved no mail from it in the period. `unavailable` means the figures could\nnot be retrieved this time and the same request may well succeed on a retry.\n","enum":["ok","not_in_panel","no_data","unavailable"],"example":"ok"},"EmailCompetitiveCampaignSummary":{"type":"object","description":"The most recent campaign observed for a brand in the period.","required":["id","subject","sent_at","image_url"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"The identifier for this campaign. Use it to fetch this one campaign on its own.\n\nIt is a string, and it needs to stay one. The values are long enough that\nJavaScript, and any other language that stores every number as a floating point\nvalue, will round them, and a rounded identifier matches no campaign at all.\nCompare it and pass it back as text.\n","example":"3914827265"},"subject":{"type":"string","minLength":1,"readOnly":true,"description":"The subject line the panel saw on this campaign.","example":"The Summer Sale: 40% off everything"},"sent_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the panel first saw this campaign arrive.","example":"2026-08-09T14:02:00Z"},"image_url":{"type":["string","null"],"format":"uri","readOnly":true,"description":"Where the panel's capture of the rendered email can be fetched, null when it captured none. Panels image only some of what they observe, so an absent creative is an ordinary outcome rather than a failed one. The image is served from the panel's own host rather than from ours, so a page embedding it has to allow that host.\n","example":"https://images.example.com/creatives/c154c8c4-6356-40e6-92d2-7c6727ec36ca.jpg"}}},"EmailCompetitiveFieldSource":{"type":"string","minLength":1,"description":"Where a figure came from. `measured` means it is counted from your own\nsending. `panel` means it is an estimate from an email panel, which observes a\nsample of real inboxes and scales what it sees up to a whole audience. `none`\nmeans there is no figure for this field on this row, so there is nothing to\nattribute a source to.\n\nOnly your own row carries `measured` figures, and only where the metric is counted\nrather than estimated. Everything about a competitor is a panel estimate.\n","enum":["measured","panel","none"],"example":"panel"},"EmailCompetitiveWatchlistRowProvenance":{"type":"object","additionalProperties":false,"description":"Where each figure on the row came from, so a comparison can be labelled\nhonestly. Every field on a competitor's row is a panel estimate. On your own\nrow the source varies by field: what is counted directly is reported as measured,\nfalls back to the panel for what is not, and reports `none` for a field this row\nnever carries at all.\n\nRead rate is a panel estimate even on your own row. Comparing a measured rate\nagainst a panel estimate of the same rate is not a like for like\ncomparison, because the two count an open differently, so both sides of the\ncomparison come from the panel.\n","required":["sends","cadence_per_week","inbox_placement_rate","read_rate","audience_overlap_rate","last_campaign"],"properties":{"sends":{"$ref":"#/components/schemas/EmailCompetitiveFieldSource","description":"Source of `sends` and of `sends_change_percent`, which is derived from it."},"cadence_per_week":{"$ref":"#/components/schemas/EmailCompetitiveFieldSource","description":"Source of `cadence_per_week`."},"inbox_placement_rate":{"$ref":"#/components/schemas/EmailCompetitiveFieldSource","description":"Source of `inbox_placement_rate`."},"read_rate":{"$ref":"#/components/schemas/EmailCompetitiveFieldSource","description":"Source of `read_rate`."},"audience_overlap_rate":{"$ref":"#/components/schemas/EmailCompetitiveFieldSource","description":"Source of `audience_overlap_rate`."},"last_campaign":{"$ref":"#/components/schemas/EmailCompetitiveFieldSource","description":"Source of `last_campaign`."}}},"EmailCompetitiveWatchlistRow":{"type":"object","additionalProperties":false,"description":"One brand on the watchlist, with its figures for the requested period. Your own\nworkspace appears as a row too, so the table can be read as a single ranking.\n\nEvery metric is present on every row and is `null` when it is unavailable for\nthat brand, so a `0` is always a real measurement rather than a gap. Check\n`panel_status` for why a metric is null.\n\n`esp` and `list_size` are the exception. They are populated only when you read a\nsingle brand, and are always `null` on the watchlist whatever `panel_status`\nreports.\n","required":["is_workspace","name","industry","sending_domains","esp","list_size","panel_status","sends","sends_change_percent","cadence_per_week","inbox_placement_rate","read_rate","audience_overlap_rate","last_campaign","provenance"],"properties":{"watchlist_brand_id":{"$ref":"#/components/schemas/CompetitiveWatchlistBrandID","readOnly":true,"description":"The watchlist entry, for removing the brand. Absent on your own row, which is not a watchlist entry."},"is_workspace":{"type":"boolean","readOnly":true,"description":"True on the row describing your own workspace's sending.","example":false},"name":{"type":"string","minLength":1,"readOnly":true,"description":"The brand's name as it was when the brand was added to the watchlist.","example":"Everlane"},"industry":{"type":["string","null"],"readOnly":true,"description":"The brand's industry as it was when the brand was added, or null when the brand is not classified.","example":"DTC Apparel"},"sending_domains":{"type":"array","minItems":1,"readOnly":true,"description":"The domains the brand's figures describe. Always one domain today: a brand is tracked by the single one the panel sees the most of its mail from, so a brand that splits its mail across several domains reports less than its full volume.\n","items":{"type":"string","minLength":1},"example":["everlane.com"]},"esp":{"type":["string","null"],"readOnly":true,"description":"A sending platform observed on the domain, or null when the panel has none on record. A brand sending through more than one platform reports one of them rather than the list. This is frequently unavailable and updates monthly at best, so treat its absence as normal rather than as pending. Populated only when you read a single brand; on the watchlist it is always null.\n","example":"Klaviyo"},"list_size":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Estimated number of addresses the brand mails, or null when the panel has no estimate. Populated only when you read a single brand; on the watchlist it is always null.\n","example":1240000},"panel_status":{"$ref":"#/components/schemas/EmailCompetitivePanelStatus","readOnly":true,"description":"Whether panel figures were available for this row, and when they were not, why."},"sends":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Messages sent in the period.","example":1240000},"sends_change_percent":{"type":["number","null"],"readOnly":true,"description":"Change in send volume against the period immediately before this one, as a percentage. Null when the earlier period has nothing to compare against.\n","example":18},"cadence_per_week":{"type":["number","null"],"readOnly":true,"description":"Average campaigns sent per week over the period.","example":5.2},"inbox_placement_rate":{"type":["number","null"],"readOnly":true,"description":"Share of the brand's observed mail that reached an inbox rather than a spam folder.\n","example":0.889},"read_rate":{"type":["number","null"],"readOnly":true,"description":"Share of delivered mail that was read.","example":0.192},"audience_overlap_rate":{"type":["number","null"],"readOnly":true,"description":"Share of your own audience the panel also sees receiving this brand's mail. Null on your own row, and null for a competitor the panel measured no overlap with, which is an answer rather than a gap.\n","example":0.24},"last_campaign":{"oneOf":[{"$ref":"#/components/schemas/EmailCompetitiveCampaignSummary"},{"type":"null"}],"unevaluatedProperties":false,"readOnly":true,"description":"The most recent campaign observed in the period, or null when none was. Always null on your own row.\n"},"provenance":{"$ref":"#/components/schemas/EmailCompetitiveWatchlistRowProvenance","readOnly":true,"description":"Where each figure on this row came from."}}},"EmailCompetitiveWatchlist":{"type":"object","additionalProperties":false,"description":"The workspace's competitor watchlist with its figures for the requested period.\n\nThe list is capped by the organization's competitor limit and is returned whole,\nso it is not paginated. Your own row is included and is always first.\n","required":["period","summary","data"],"properties":{"period":{"$ref":"#/components/schemas/EmailCompetitivePeriod","description":"The period every figure covers."},"summary":{"$ref":"#/components/schemas/EmailCompetitiveWatchlistSummary","description":"Where your sending sits against the brands you watch."},"data":{"type":"array","readOnly":true,"description":"Your own row first, then each watched brand in the order it was added. Your row is present once your workspace has sent email, since before that there is no sending of yours to compare against. Empty for a workspace that has neither sent nor added a brand.\n","items":{"$ref":"#/components/schemas/EmailCompetitiveWatchlistRow"}}}},"EmailCompetitiveCampaignSignal":{"type":"string","minLength":1,"description":"Why this campaign was surfaced. The set is open and grows as new signals are added.\n\nEvery signal describes the campaign against its own brand's history, never against the\nother brands you watch, so several brands can carry the same signal in one period and\nnone of them is the top of anything.\n\n`biggest_send` is a send far above that brand's own median: unusual for the brand, not\nmerely large. `read_rate_standout` is a campaign read unusually well for its brand.\n`landing_in_spam` is one heavily filed as spam at a single mailbox provider, named in\n`mailbox_provider`, which is worth seeing even when the brand's overall placement looks healthy.\n","x-extensible-enum":["biggest_send","read_rate_standout","landing_in_spam"],"example":"read_rate_standout"},"EmailCompetitiveNotableClaim":{"type":"object","additionalProperties":false,"description":"What the panel itself asserts about a campaign, in its own words. Present only on the campaigns the panel chose to make a claim about, which is a minority of them: a campaign can be surfaced as notable without the panel putting a headline on it, and that is an ordinary outcome rather than missing data.\n","required":["text"],"properties":{"text":{"type":"string","minLength":1,"readOnly":true,"description":"The panel's own phrasing, which may name the window the claim was measured over (\"Biggest send in 7 days\") or not (\"Best-read campaign\"). Show it as written rather than rebuilding it from the signal, and do not parse a window out of it.\n","example":"Biggest send in 7 days"}}},"EmailCompetitiveNotableEvidence":{"type":"object","additionalProperties":false,"description":"The figures behind a campaign's signal, for ordering or filtering the list yourself. Which field carries a value depends on the signal, and each is null both on the signals it does not describe and on a campaign of its own signal the panel published no figure for.\n","required":["ratio_to_median","read_rate_observations","mailbox_provider_spam_rate","mailbox_provider_observations"],"properties":{"ratio_to_median":{"type":["number","null"],"readOnly":true,"description":"How many times the brand's own median volume this send was. A value of 29 means the send was twenty-nine times the brand's typical volume for the period. Null on every signal other than `biggest_send`, and on a `biggest_send` campaign the panel published no ratio for.\n","example":29.61},"read_rate_observations":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"How many panel observations `campaign.read_rate` was measured over. A rate over thirty observations and one over a hundred and forty are not equally worth showing, and this is what separates them. Null on every signal other than `read_rate_standout`, and on a `read_rate_standout` campaign the panel published no denominator for.\n","example":66},"mailbox_provider_spam_rate":{"type":["number","null"],"readOnly":true,"description":"Share of this campaign filed as spam at the one provider named in `mailbox_provider`, as a value between 0 and 1. A different measurement from the campaign's overall `spam_rate`, and the one this signal is about. Null on every signal other than `landing_in_spam`, and on a `landing_in_spam` campaign whose provider counts the panel did not publish, so a spam entry can arrive without the rate behind it.\n","example":0.79},"mailbox_provider_observations":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"How many observations at that provider `mailbox_provider_spam_rate` was measured over. Null on the same terms.\n","example":199}}},"EmailCompetitivePanelMailboxProvider":{"type":"string","minLength":1,"readOnly":true,"description":"A mailbox provider, as the email panel identifies it. A lowercase identifier rather than a\ndisplay name, so pick your own label for it, and treat the set as open: the panel reports\nwhichever providers it observed, and `gmail`, `hotmail`, `yahoo`, `aol` and `comcast` are the\nones it returns most. Apple never appears, because the panel does not measure it, so a surface\noffering an Apple row has no measurement behind it.\n\nThe panel's buckets are not the same as the ones the [mailbox-provider stats\nbreakdown](/docs/api/reference/get-email-stats-by-mailbox-provider) reports: Microsoft's\nproperties appear here as `hotmail` rather than `microsoft`, and Apple is absent, so the two\nare not a joinable dimension.\n","example":"gmail"},"EmailCompetitiveCampaign":{"description":"One campaign an email panel observed a brand sending.","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/EmailCompetitiveCampaignSummary"},{"type":"object","required":["reach","read_rate","has_creative","discount_percent","inbox_rate","spam_rate"],"properties":{"reach":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Estimated recipients this campaign reached, null when the panel observed the campaign but published no estimate for it.\n","example":410000},"read_rate":{"type":["number","null"],"readOnly":true,"description":"Estimated share of recipients who read this campaign, null when the panel published no rate for it. Panel read rates count dwell time, so they do not move with the automatic opens that inflate a sender's own open rate.\n","example":0.228},"has_creative":{"type":"boolean","readOnly":true,"description":"Whether the panel captured the rendered email for this campaign.","example":true},"discount_percent":{"type":["number","null"],"readOnly":true,"description":"The discount the subject line leads with, null when it names none. Read from the subject text, so it finds a stated offer and not one revealed inside the email.\n","example":40},"inbox_rate":{"type":["number","null"],"readOnly":true,"description":"Share of this campaign that reached an inbox, null when the panel observed it without recording where it landed. It describes this send rather than the brand's domain, so a single bad campaign is visible against a brand whose overall placement still looks healthy.\n","example":0.879},"spam_rate":{"type":["number","null"],"readOnly":true,"description":"Share of this campaign that was filed as spam, null on the same terms.","example":0.121}}}]},"EmailCompetitiveNotableCampaign":{"type":"object","additionalProperties":false,"description":"A campaign the panel surfaced, and the reason it did.","required":["watchlist_brand_id","brand_name","signal","claim","evidence","mailbox_provider","campaign"],"properties":{"watchlist_brand_id":{"$ref":"#/components/schemas/CompetitiveWatchlistBrandID","readOnly":true,"description":"The watchlist entry that sent it."},"brand_name":{"type":"string","minLength":1,"readOnly":true,"description":"The brand's name.","example":"Allbirds"},"signal":{"$ref":"#/components/schemas/EmailCompetitiveCampaignSignal","readOnly":true},"claim":{"oneOf":[{"$ref":"#/components/schemas/EmailCompetitiveNotableClaim"},{"type":"null"}],"readOnly":true,"description":"The panel's own headline for this campaign, or null where it surfaced the campaign without making one. Null is the common case and is not a fault.\n"},"evidence":{"$ref":"#/components/schemas/EmailCompetitiveNotableEvidence","readOnly":true,"description":"The figures behind the signal, for ordering or filtering the list."},"mailbox_provider":{"oneOf":[{"$ref":"#/components/schemas/EmailCompetitivePanelMailboxProvider"},{"type":"null"}],"readOnly":true,"description":"The provider a `landing_in_spam` campaign was heavily filed as spam at: spam placement is measured per provider, and this campaign's problem is at one of them. Null on every other signal.\n"},"campaign":{"$ref":"#/components/schemas/EmailCompetitiveCampaign","description":"The campaign itself. Two of its fields behave differently here than on the brand's\ncampaign feed, because the panel sends less about a campaign it surfaced this way.\n\n`has_creative` reports whether a capture came back with this entry rather than whether\nthe panel ever captured the email, and captures are frequently absent here by design, so\nexpect `false` on campaigns the panel did image. `reach` is null on every entry, because\nthe panel does not estimate an audience for the campaigns it surfaces.\n"}}},"EmailCompetitiveNotableFeed":{"type":"object","additionalProperties":false,"description":"Campaigns worth a second look across the brands a workspace watches.","required":["period","panel_status","data","truncated"],"properties":{"period":{"$ref":"#/components/schemas/EmailCompetitivePeriod","description":"The period the campaigns were observed in, as the panel resolved it.\n\nTwo things differ from the other competitive reads. It ends at the last instant of\nthe previous whole day rather than at the moment of the request, so a campaign sent\nthis morning is never among these. And the panel holds its selection for a period\nonce it has made it, so two requests a minute apart return the same campaigns rather\nthan differing slightly.\n"},"panel_status":{"$ref":"#/components/schemas/EmailCompetitivePanelStatus","description":"Whether the panel could be read for this feed, and when it could not, why."},"data":{"type":"array","readOnly":true,"maxItems":100,"description":"Up to 100 campaigns selected across watched brands. Selection takes turns across\nbrands in watchlist order until the response is full, prioritizing spam placement,\nbiggest sends, then read-rate standouts within each brand. Within one signal, rows\ncompare the matching spam rate, volume ratio, or read rate descending; missing values\nsort last and ties retain tracked-domain and source order. Selected rows are returned\nin watchlist order, then biggest-send, read-rate, and spam signal order, followed by\ntracked-domain and source order.\n\nOne campaign may appear once per signal because each row carries different evidence.\nEmpty when nothing qualified; check `panel_status` to distinguish that from an\nunavailable panel.\n","items":{"$ref":"#/components/schemas/EmailCompetitiveNotableCampaign"}},"truncated":{"type":"boolean","readOnly":true,"description":"Whether Bird omitted eligible panel findings to keep this response to 100 rows. False does not promise that the panel observed every qualifying campaign in the period.\n","example":false}}},"EmailCompetitiveBrandID":{"type":"string","minLength":1,"maxLength":19,"pattern":"^[0-9]+$","description":"Identifier of the brand in the panel's catalog, used to add it to the watchlist. It is a string for the same reason a campaign id is: the values are wide enough that a client storing every number as a floating point value would round them, and a rounded identifier matches no brand at all.\n","example":"81531"},"EmailCompetitiveWatchlistBrandCreate":{"type":"object","additionalProperties":false,"description":"The brand to add to the watchlist. Obtained from a brand search, which only returns brands that can be watched.\n","required":["brand_id"],"properties":{"brand_id":{"$ref":"#/components/schemas/EmailCompetitiveBrandID"}}},"EmailCompetitiveWatchlistBrand":{"description":"A brand on the workspace's watchlist. This is the watchlist entry itself, with no figures on it; read the watchlist to get those.\n","unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","brand_id","name","industry","sending_domains"],"properties":{"id":{"$ref":"#/components/schemas/CompetitiveWatchlistBrandID","readOnly":true,"description":"The watchlist entry."},"brand_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailCompetitiveBrandID"}]},"name":{"type":"string","minLength":1,"readOnly":true,"description":"The brand's name when it was added. It is kept as it was so the row still reads correctly if the brand is later renamed or stops being tracked.\n","example":"Everlane"},"industry":{"type":["string","null"],"readOnly":true,"description":"The brand's industry when it was added, or null when the brand is not classified.","example":"DTC Apparel"},"sending_domains":{"type":"array","minItems":1,"readOnly":true,"description":"The domains this brand's figures describe. Always one domain today, chosen as the one the panel sees the most of its mail from.\n","items":{"type":"string","minLength":1},"example":["everlane.com"]}}}]},"EmailCompetitiveProviderPlacement":{"type":"object","additionalProperties":false,"description":"How one mailbox provider treated a brand's mail, beside your own.","required":["mailbox_provider","inbox_rate","spam_rate","workspace_inbox_rate"],"properties":{"mailbox_provider":{"$ref":"#/components/schemas/EmailCompetitivePanelMailboxProvider","readOnly":true,"description":"The provider whose treatment of the brand's mail this row describes."},"inbox_rate":{"type":"number","readOnly":true,"description":"Share of the brand's mail this provider put in the inbox. Recomputed from what the panel observed across every domain the brand sends from, so a small subdomain cannot move it as much as the brand's main one.\n","example":0.862},"spam_rate":{"type":"number","readOnly":true,"description":"Share of the brand's mail this provider put in spam.","example":0.091},"workspace_inbox_rate":{"type":["number","null"],"readOnly":true,"description":"Your own inbox rate at this provider, null when you have not sent or the panel has no breakdown for your sending domain. It is the panel's view of your sending rather than from our own measurement of it, because a measured rate and a rate the panel estimated are not comparable, and this figure exists to be compared with the brand's.\n","example":0.921}}},"EmailCompetitiveBrandProfile":{"type":"object","additionalProperties":false,"description":"One watched brand's figures for the period, with its placement broken out by mailbox provider.","required":["period","brand","providers"],"properties":{"period":{"$ref":"#/components/schemas/EmailCompetitivePeriod","description":"The period every figure covers."},"brand":{"$ref":"#/components/schemas/EmailCompetitiveWatchlistRow","description":"The figures the watchlist reports for this brand, derived the same way. Estimated\nvolume can differ very slightly between the two views, because each request asks the\npanel about a different set of domains and the panel scales its estimate per request.\n\n`esp` and `list_size` are populated here; the watchlist reports both as null.\n"},"providers":{"type":"array","readOnly":true,"description":"Placement per mailbox provider, in the order the panel returned them. Empty when the panel published no breakdown for the brand's domains.\n","items":{"$ref":"#/components/schemas/EmailCompetitiveProviderPlacement"}}}},"EmailCompetitiveCampaignSort":{"type":"string","minLength":1,"description":"Field used to sort campaigns.","enum":["sent_at"],"default":"sent_at","example":"sent_at"},"EmailCompetitiveCampaignFeed":{"description":"A page of campaigns returned for a watched brand over the period.","allOf":[{"type":"object","required":["period","panel_status","captured","promo_rate","truncated","data"],"properties":{"period":{"$ref":"#/components/schemas/EmailCompetitivePeriod","description":"The rolling period used for this request."},"panel_status":{"$ref":"#/components/schemas/EmailCompetitivePanelStatus","description":"For this campaign feed, no_data means the requested page is empty; it does not mean the whole period has no campaigns."},"captured":{"type":"integer","readOnly":true,"description":"Number of eligible campaigns in the first 300 newest panel rows for each tracked domain. This sampled value is independent of the returned page.\n","example":38},"promo_rate":{"type":["number","null"],"readOnly":true,"description":"Fraction of captured campaigns whose subject leads with a discount. Null when captured is zero. This sampled value is independent of the returned page.\n","example":0.64},"truncated":{"type":"boolean","readOnly":true,"description":"Whether the sampled statistics or returned page omit part of the requested collection. Use next_cursor to determine whether another page is available.\n","example":false},"data":{"type":"array","readOnly":true,"description":"Campaigns in this page, in the requested order.","items":{"$ref":"#/components/schemas/EmailCompetitiveCampaign"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}],"unevaluatedProperties":false},"EmailCompetitiveWeekday":{"type":"string","minLength":1,"description":"A day of the week. Named rather than numbered because the two common numberings disagree about which day the week starts on.\n","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"],"example":"tuesday"},"EmailCompetitiveSendTimeCell":{"type":"object","additionalProperties":false,"description":"One weekday and hour of a brand's sending week.","required":["weekday","hour","share_percent","intensity","sample_days"],"properties":{"weekday":{"$ref":"#/components/schemas/EmailCompetitiveWeekday","readOnly":true,"description":"The day of the week this hour falls on."},"hour":{"type":"integer","readOnly":true,"minimum":0,"maximum":23,"description":"The hour this cell covers, in the timezone the response reports. `13` covers 13:00 to 14:00.\n","example":13},"share_percent":{"type":"number","readOnly":true,"description":"Share of everything the brand sent over the period that fell in this hour. It is `0` for an hour the brand demonstrably did not send in, which on a disciplined sender is the most useful thing this grid says.\n","example":3.4},"intensity":{"type":"number","readOnly":true,"minimum":0,"maximum":1,"description":"How strongly the brand sends in this hour, against its own busiest hour at `1`.\nIt is this cell's sending per `sample_days` divided by the busiest cell's, so it\nis derivable from the two numbers beside it and reconciles with them rather than\ncompeting: it is published because that correction is easy to get wrong, not\nbecause it knows anything they do not.\n\nShade a cell by this rather than by `share_percent`: the period holds one more\nof some weekdays than others, so a share compares an hour that came round\nthirteen times against one that came round twelve.\n","example":0.55},"sample_days":{"type":"integer","readOnly":true,"description":"How many days of the period fell on this weekday, whether or not the brand sent on them. It is what separates an hour the brand is quiet in from one there was little chance to observe.\n","example":13}}},"EmailCompetitiveSendTimePeak":{"type":"object","additionalProperties":false,"description":"The hour of the day a brand sends most of its mail in.","required":["start_hour","end_hour","share_percent"],"properties":{"start_hour":{"type":"integer","readOnly":true,"minimum":0,"maximum":23,"description":"The first hour of the window, in the timezone the response reports.","example":13},"end_hour":{"type":"integer","readOnly":true,"minimum":0,"maximum":23,"description":"The hour the window ends at, exclusive: a window of `13` to `14` covers 13:00 to\n14:00. The window is always one hour wide on this endpoint, so this is always the\nhour after `start_hour`. The pair is kept rather than collapsed because the panel\ncomputes the window at whatever width it was asked for, and only this endpoint\npins that to an hour.\n\nIt can therefore be lower than `start_hour` in exactly one case: a peak at 23:00,\nwhose window runs past midnight and ends at `0`.\n","example":14},"share_percent":{"type":"number","readOnly":true,"description":"Share of everything the brand sent over the period that fell in this window.\n\nThis is the panel's own figure, while a cell's `share_percent` is recomputed from\nthe cells in the response. Adding up this hour's seven cells should therefore land\non this number but is not guaranteed to; where they disagree, this one is the\npanel's answer about its own peak and the cells are the arithmetic behind the grid.\n","example":13.1}}},"EmailCompetitiveSendTimeGrid":{"type":"object","additionalProperties":false,"description":"When a watched brand sends, by weekday and hour of the day.","required":["period","timezone","panel_status","cells","peak_send_window"],"properties":{"period":{"$ref":"#/components/schemas/EmailCompetitivePeriod","description":"The period the grid covers. It is always the last 90 days, whatever range the\nrest of the brand's figures are shown over: an hour of the week comes round\nabout thirteen times in 90 days and once in a week, and a pattern drawn from\none observation per cell is noise.\n\nTwo things differ from the other competitive reads. It ends at the start of a\nday rather than at the moment of the request, and the panel answers repeat\nrequests from a cache it holds for a day, so two requests a minute apart return\nidentical figures and this grid can be up to a day behind the figures shown\nbeside it.\n"},"timezone":{"$ref":"#/components/schemas/Timezone","readOnly":true,"description":"The timezone the hours are reported in. Label the grid from this rather than from what was requested: a response the panel could not answer reports UTC whatever was asked for.\n"},"panel_status":{"$ref":"#/components/schemas/EmailCompetitivePanelStatus","description":"Why the grid is empty, when it is."},"cells":{"type":"array","readOnly":true,"description":"Every weekday and hour of the week, Monday first and hour ascending: 168 in all, whether or not the brand sent in them, so the grid needs no filling in. Empty when there was nothing to read, which `panel_status` explains.\n","items":{"$ref":"#/components/schemas/EmailCompetitiveSendTimeCell"}},"peak_send_window":{"oneOf":[{"$ref":"#/components/schemas/EmailCompetitiveSendTimePeak"},{"type":"null"}],"readOnly":true,"description":"The hour of the day the brand sends most of its mail in, totalled across the whole week, or null when nothing was observed. It carries no weekday: for most brands the hour of the day is where the pattern is and the day of the week barely moves, so naming a busiest weekday would give a figure more meaning than it has. It is also not always the darkest cell, on the same reasoning: one busy Wednesday can outweigh the hour the brand mails in every single day.\n"}}},"EmailCompetitiveBrandMatch":{"type":"object","additionalProperties":false,"description":"A brand matching a search, ready to be added to the watchlist.","required":["brand_id","name","sending_domains"],"properties":{"brand_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailCompetitiveBrandID"}]},"name":{"type":"string","minLength":1,"readOnly":true,"description":"The brand's name.","example":"Everlane"},"sending_domains":{"type":"array","minItems":1,"readOnly":true,"description":"The domains this brand's figures would describe. Always one domain today, chosen as the one the panel sees the most of its mail from.\n","items":{"type":"string","minLength":1},"example":["everlane.com"]}}},"EmailCompetitiveBrandSearchResults":{"type":"object","additionalProperties":false,"description":"Brands matching the search. Ranked by how well they match, best first, and capped at 8 results because this backs a type-ahead. The panel's own answer is often shorter than the cap, in which case the cap was never the reason the list is short.\n","required":["data"],"properties":{"data":{"type":"array","readOnly":true,"description":"Matching brands. Empty when nothing matched, which for an unusual brand name means the panel does not track it rather than that the search failed.\n","items":{"$ref":"#/components/schemas/EmailCompetitiveBrandMatch"}}}},"EmailCompetitiveVolumePoint":{"type":"object","additionalProperties":false,"description":"One day of one line on the volume chart.","required":["date","sends"],"properties":{"date":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"The UTC day this point covers.","example":"2026-08-09"},"sends":{"type":"integer","format":"int64","readOnly":true,"description":"Volume for the day. An estimate for a competitor and an exact count for your own line; `source` on the series records which. A day nothing was observed is `0` rather than a missing point, so every line shares one axis.\n","example":41800}}},"EmailCompetitiveBrandSeries":{"type":"object","additionalProperties":false,"description":"One line on the volume chart: a watched brand's sending over time, or your own.\n","required":["is_workspace","name","sending_domains","panel_status","source","points"],"properties":{"watchlist_brand_id":{"$ref":"#/components/schemas/CompetitiveWatchlistBrandID","readOnly":true,"description":"The watchlist entry this line describes. Absent on your own line, which is not a watchlist entry."},"is_workspace":{"type":"boolean","readOnly":true,"description":"True on the line describing your own workspace's sending.","example":false},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Label for the line: the brand's name, or your sending domain on your own line.","example":"Everlane"},"sending_domains":{"type":"array","minItems":1,"readOnly":true,"description":"The sending domains the line's figures describe. Always one domain today.","items":{"type":"string","minLength":1},"example":["everlane.com"]},"panel_status":{"$ref":"#/components/schemas/EmailCompetitivePanelStatus","description":"Why a line has no volume in it. Always `ok` on your own line, which is counted rather than read from the panel.\n"},"source":{"$ref":"#/components/schemas/EmailCompetitiveFieldSource","description":"Where the line came from. Your own is an exact count of what was accepted for delivery; a competitor's is the panel's estimate of everything they sent. The two share an axis while resting on different measurements, so a chart that compares them should say so.\n"},"points":{"type":"array","readOnly":true,"description":"One point per day of the period, oldest first, ending with the last whole UTC day rather than the one in progress. A domain the panel tracks but observed nothing for plots as zeros, which is a measured silence rather than a missing measurement. Points are empty only when there was nothing to plot at all, reported by `panel_status` as `not_in_panel` or `unavailable`.\n","items":{"$ref":"#/components/schemas/EmailCompetitiveVolumePoint"}}}},"EmailCompetitiveVolumeSeries":{"type":"object","additionalProperties":false,"description":"Volume over time for the requested watched brands, plus your own sending, on one shared daily axis.\n","required":["period","data"],"properties":{"period":{"$ref":"#/components/schemas/EmailCompetitivePeriod","description":"The period every line covers."},"data":{"type":"array","readOnly":true,"description":"Your own line first, then the requested brands in the order they were asked for. Your line is present once your workspace has sent email. Every line carries the same days in the same order, so they can be plotted against one axis without aligning them first.\n","items":{"$ref":"#/components/schemas/EmailCompetitiveBrandSeries"}}}},"EmailTemplateCategory":{"type":"string","minLength":1,"enum":["transactional","marketing"],"description":"Whether the template is for `transactional` email or `marketing` email."},"EmailTemplateSource":{"type":"string","minLength":1,"x-extensible-enum":["html"],"description":"The authoring format the template is written in, fixed at creation. `html` is finished markup you provide, optionally personalized with Liquid."},"EmailTemplateThemeFilter":{"type":"string","enum":["arcane","barebone","matte","protocol","studio"],"description":"Filter by the visual theme a built-in template is designed in. Only built-in `system` templates have a theme, so naming one returns built-ins alone."},"EmailTemplateTheme":{"type":"string","minLength":1,"x-extensible-enum":["arcane","barebone","matte","protocol","studio"],"description":"The visual theme a built-in template is designed in. Each of the catalog's five themes ships its own set of eight emails, and the sets overlap only partly, so the theme is what you choose between once you know which email you want. Only our built-in `system` templates have one."},"EmailTemplateLanguageState":{"type":"object","additionalProperties":false,"description":"Where one of the template's languages stands: whether sends are using it, and whether its draft contains an unpublished edit.\n","required":["status"],"properties":{"status":{"$ref":"#/components/schemas/TemplateLanguageStatus"},"draft":{"type":"boolean","readOnly":true,"description":"Whether the draft holds an edit to this language that has not been published. If this is true and the status is `live`, sends are still using the older content, and your edit goes out the next time you submit.\n"}}},"EmailTemplateSummary":{"type":"object","additionalProperties":false,"required":["id","workspace_id","slug","name","scope","status","category","source","theme","draft_version_id","live_version_id","published_version_id","live_version_number","languages","default_language","available_languages","description","last_submitted_at","created_at","updated_at"],"properties":{"id":{"readOnly":true,"description":"Template ID.","$ref":"#/components/schemas/EmailTemplateID"},"workspace_id":{"readOnly":true,"description":"The workspace that owns the template. Null for a built-in `system` template, which no workspace owns.","oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}]},"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"readOnly":true,"description":"The name you send the template by. You can use either the slug or the id when you send. It never changes after the template is created. A built-in `system` template's slug always starts with `bird_`.","example":"welcome-email"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The template's display name, shown wherever the template is listed. You can change it any time. It defaults to the slug if you do not set one.","example":"Welcome email"},"description":{"type":["string","null"],"description":"What the template is for, in your own words. Null if you have not set one."},"scope":{"example":"workspace","$ref":"#/components/schemas/TemplateScope"},"status":{"$ref":"#/components/schemas/TemplateStatus"},"category":{"$ref":"#/components/schemas/EmailTemplateCategory"},"source":{"$ref":"#/components/schemas/EmailTemplateSource"},"theme":{"readOnly":true,"description":"The visual theme a built-in template is designed in, or null for a template your workspace authored (which has no theme).","example":null,"oneOf":[{"$ref":"#/components/schemas/EmailTemplateTheme"},{"type":"null"}]},"draft_version_id":{"readOnly":true,"description":"The current editable draft version. Null for a built-in `system` template, which has no draft.","oneOf":[{"$ref":"#/components/schemas/EmailTemplateVersionID"},{"type":"null"}]},"live_version_id":{"readOnly":true,"description":"The version a send resolves to, or null if the template has never been published.","oneOf":[{"$ref":"#/components/schemas/EmailTemplateVersionID"},{"type":"null"}]},"published_version_id":{"readOnly":true,"deprecated":true,"description":"Deprecated: use `live_version_id` instead, which carries the same value.\n","oneOf":[{"$ref":"#/components/schemas/EmailTemplateVersionID"},{"type":"null"}]},"live_version_number":{"type":["integer","null"],"minimum":1,"readOnly":true,"description":"The live version's sequential number (1, 2, 3…), the same one version history reports, or null if the template has never been published. A built-in `system` template is permanently published as version 1. A rollback moves it backwards, because it names the version that is live rather than how many exist.\n"},"available_languages":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/LanguageTag"},"description":"The languages this template currently supports for sending, as BCP-47 tags. Empty until the template is published, because sends serve published content. This set may shrink for reasons other than editing, so read it rather than assuming it matches what was published.\n","example":["en"]},"default_language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language the draft defaults to. Read this language's content when you have no particular preference for which one you want.\n"},"languages":{"type":"object","readOnly":true,"propertyNames":{"$ref":"#/components/schemas/LanguageTag"},"additionalProperties":{"$ref":"#/components/schemas/EmailTemplateLanguageState"},"description":"Every language this template has, keyed by language tag, each with its state. Enough to show which templates need attention in a list without a request per row.\n","example":{"en":{"status":"live"},"de":{"status":"draft"}}},"last_submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this template was last submitted. Null if it never has been. Only submitting moves this timestamp, so a rollback keeps reporting the last real submit.\n"},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was created. Null for a built-in `system` template."},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was last modified. Null for a built-in `system` template."}}},"EmailTemplateList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of email templates.","items":{"$ref":"#/components/schemas/EmailTemplateSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EmailTemplateSourceWrite":{"type":"string","minLength":1,"enum":["html"],"x-enum-varnames":["EmailTemplateSourceWriteHtml"],"description":"The authoring format to create the template in. `html` is finished markup you provide, optionally personalized with Liquid; a format the API cannot author yet is refused."},"EmailTemplateLanguageContent":{"type":"object","additionalProperties":false,"description":"One language's content for an email template. Each language carries its own subject, preview text and bodies, so a translation can differ in wording and length from every other language without affecting them.\n","properties":{"subject":{"type":"string","maxLength":998,"description":"The email subject line for this language.","example":"Welcome to Acme, {{ bird.contact.first_name }}!"},"preview_text":{"type":"string","maxLength":255,"description":"The line an inbox shows after the subject in the message list, for this language. Leave it out and the inbox shows the opening words of the body instead. A mail client only reads it from the message body, so publishing folds it into the top of the HTML, hidden from view once the message is open; write it here rather than hiding your own copy in the body.\n","example":"{{ bird.contact.first_name }}, your order is on its way"},"html":{"type":"string","maxLength":524288,"description":"The HTML body for this language.","example":"<h1>Hi {{ bird.contact.first_name }}</h1>"},"text":{"type":"string","maxLength":524288,"description":"The plain-text body for this language. Omit it and a plain-text alternative is derived from the HTML when you submit.\n"}}},"EmailTemplateCreate":{"type":"object","additionalProperties":false,"description":"Parameters for creating an email template and its initial draft.","required":["slug","category","source"],"properties":{"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"description":"The template's workspace-unique handle, and a stable alternative to the template ID when sending by template. It can contain lowercase letters, numbers, hyphens, and underscores. It is fixed at creation, so pick it deliberately. Two prefixes are rejected: `bird_`, reserved for our built-in templates, and `emt_`, the template ID format, which a slug could never be distinguished from.\n","example":"welcome-email"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The template's display name, shown wherever the template is listed. You can change it any time. It defaults to the slug if you do not set one.\n","example":"Welcome email"},"description":{"type":"string","description":"What the template is for, in your own words.","example":"Sent to new customers after signup."},"category":{"$ref":"#/components/schemas/EmailTemplateCategory"},"source":{"allOf":[{"$ref":"#/components/schemas/EmailTemplateSourceWrite"}],"description":"The authoring format the template is written in, fixed at creation.\n`html` is finished markup you provide, optionally personalized with\nLiquid. Liquid supports variables, filters, and control flow such\nas `{% if %}` conditionals and `{% for %}` loops. A few constructs are\nrejected when you submit, and the error names exactly what to change:\n\n- Partial includes (`{% include %}`, `{% render %}`).\n- The `increment`, `decrement`, and `ifchanged` tags.\n- The `money`, `format_date`, `format_time`, `json`, `inspect`, and `type` filters.\n- Comparing against `empty`/`blank` (use `.size == 0` instead).\n- Blocks nested far deeper than real email markup needs.\n\nA broadcast's template additionally cannot use a `{% for %}` loop,\nbecause a broadcast supplies one value per contact property, so there\nis nothing to iterate. Send with the messages API instead if the\ntemplate needs one.\n"},"languages":{"type":"object","maxProperties":25,"propertyNames":{"$ref":"#/components/schemas/LanguageTag"},"additionalProperties":{"$ref":"#/components/schemas/EmailTemplateLanguageContent"},"description":"The initial draft's content, keyed by language tag in BCP-47 form such as\n`en` or `pt-BR`. A template holds up to 25 languages, and a send picks one\nof them.\n\nOmit this to create an empty draft and add content later.\n","example":{"en":{"subject":"Welcome to Acme, {{ bird.contact.first_name }}!","html":"<h1>Hi {{ bird.contact.first_name }}</h1>"}}},"default_language":{"$ref":"#/components/schemas/LanguageTag","example":"en","description":"The language a send uses when it does not name one, and the last resort when a requested language is not available. It has to be one of the languages you supply. If you leave it out, we default to `en`, unless you supply exactly one language, in which case we use that one instead. So if you supply two or more languages and `en` is not among them, you have to set this yourself.\n"},"on_missing_language":{"allOf":[{"$ref":"#/components/schemas/TemplateOnMissingLanguage"}],"default":"fallback","description":"What a send does when it asks for a language this template does not carry. Defaults to `fallback` on email.\n"},"language_source_required":{"type":"boolean","default":false,"description":"Whether a send has to name a language. Set it to true to reject a send that names none instead of serving the default language. Pair it with `on_missing_language: fail` when every send must pick a language deliberately: on its own, `fail` is bypassed by naming no language at all. A template with this set cannot be used for a broadcast, which has no way to name one. Defaults to false.\n"}}},"BrandKitID":{"type":"string","minLength":1,"pattern":"^bk_[0-9a-hjkmnp-tv-z]{26}$","example":"bk_01krdgeqcxet5s7t44vh8rt9mg"},"EmailTemplate":{"type":"object","additionalProperties":false,"required":["id","workspace_id","slug","name","scope","status","category","source","theme","draft_version_id","live_version_id","published_version_id","revision","languages","default_language","available_languages","on_missing_language","language_source_required","description","last_submitted_at","created_at","updated_at"],"properties":{"id":{"readOnly":true,"description":"Template ID.","$ref":"#/components/schemas/EmailTemplateID"},"workspace_id":{"readOnly":true,"description":"The workspace that owns the template. Null for a built-in `system` template, which no workspace owns.","oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}]},"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"readOnly":true,"description":"The name you send the template by. You can use either the slug or the id when you send. It never changes after the template is created. A built-in `system` template's slug always starts with `bird_`.","example":"welcome-email"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The template's display name, shown wherever the template is listed. You can change it any time. It defaults to the slug if you do not set one.","example":"Welcome email"},"description":{"type":["string","null"],"description":"What the template is for, in your own words. Null if you have not set one."},"scope":{"example":"workspace","$ref":"#/components/schemas/TemplateScope"},"status":{"$ref":"#/components/schemas/TemplateStatus"},"category":{"$ref":"#/components/schemas/EmailTemplateCategory"},"source":{"$ref":"#/components/schemas/EmailTemplateSource"},"theme":{"readOnly":true,"description":"The visual theme a built-in template is designed in, or null for a template your workspace authored (which has no theme).","example":null,"oneOf":[{"$ref":"#/components/schemas/EmailTemplateTheme"},{"type":"null"}]},"draft_version_id":{"readOnly":true,"description":"The current editable draft version. Null for a built-in `system` template, which has no draft.","oneOf":[{"$ref":"#/components/schemas/EmailTemplateVersionID"},{"type":"null"}]},"live_version_id":{"readOnly":true,"description":"The version a send resolves to, or null if the template has never been published.\n","oneOf":[{"$ref":"#/components/schemas/EmailTemplateVersionID"},{"type":"null"}]},"published_version_id":{"readOnly":true,"deprecated":true,"description":"Deprecated: use `live_version_id` instead, which carries the same value.\n","oneOf":[{"$ref":"#/components/schemas/EmailTemplateVersionID"},{"type":"null"}]},"revision":{"type":["integer","null"],"readOnly":true,"minimum":0,"description":"The draft's revision counter. Send it back on the next update to detect concurrent edits. Null for a built-in `system` template, which is unversioned."},"languages":{"type":"object","readOnly":true,"propertyNames":{"$ref":"#/components/schemas/LanguageTag"},"additionalProperties":{"$ref":"#/components/schemas/EmailTemplateLanguageState"},"description":"Every language this template has, keyed by language tag in BCP-47 form\nsuch as `en` or `pt-BR`, each with its state. One read tells you which\nlanguages are live and which have unpublished edits, without fetching any\ncontent.\n\nContent is not here: read a version's languages for that, one language at\na time.\n","example":{"en":{"status":"live"},"pt-BR":{"status":"live","draft":true},"de":{"status":"draft"}}},"default_language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language the draft defaults to. This is the language `languages` is\nkeyed against while you edit, and the language used by sends once you\nsubmit this draft.\n\nUntil then sends keep using the live version's default, so this can\ndiffer from what is being sent right now. `available_languages` describes\nthe live version for the same reason; read a version to see the default a\nsend currently uses.\n"},"available_languages":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/LanguageTag"},"description":"The languages this template currently supports for sending, as BCP-47 tags. Empty until the template is published, because sends serve published content. The set may shrink for reasons other than editing, so read it rather than assuming it matches what was published. A built-in `system` template has no publish step and always reports its one language.\n","example":["en"]},"on_missing_language":{"allOf":[{"$ref":"#/components/schemas/TemplateOnMissingLanguage"}],"readOnly":true,"description":"What a send does when it asks for a language this template does not carry. Defaults to `fallback` on email.\n"},"language_source_required":{"type":"boolean","readOnly":true,"description":"Whether a send has to name a language. When true, a send that names none is rejected instead of being served the default language, and the template cannot be used for a broadcast, which has no way to name one.\n"},"last_submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this template was last submitted. Null if it never has been. Submitting is the only thing that moves this timestamp: rolling back changes which version is live without counting as a submit, so this keeps reporting the last real submit. Read it alongside `languages`, which says where each language stands.\n"},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was created. Null for a built-in `system` template."},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the template was last modified. Null for a built-in `system` template."}}},"EmailTemplateDraftRevision":{"type":"integer","minimum":0,"description":"The draft revision you last read (from the template's `revision` field). A stale value returns a conflict so you can reload and retry.\n","example":3},"EmailTemplateUpdate":{"type":"object","additionalProperties":false,"description":"Partial update of a template's metadata and draft settings. Only the fields you send are changed. The rest are left as-is. Include the draft `revision` you last read so concurrent edits are detected. Content is not here: save a language on the draft version to change what the template says.\n","required":["revision"],"properties":{"revision":{"$ref":"#/components/schemas/EmailTemplateDraftRevision"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"New display name, in free text. The slug stays fixed at creation, so renaming the template does not break whatever refers to it by slug or id.\n"},"description":{"type":["string","null"],"description":"What the template is for, in your own words. Send `null` to clear it."},"default_language":{"$ref":"#/components/schemas/LanguageTag","description":"New default language for the draft. Must be one of the languages the draft already has, so add the language first if it is not there yet.\n"},"on_missing_language":{"allOf":[{"$ref":"#/components/schemas/TemplateOnMissingLanguage"}],"description":"What a send does when it asks for a language this template does not carry.\n"},"language_source_required":{"type":"boolean","description":"Whether a send has to name a language. Turning it on rejects a send that names none instead of serving the default language, and makes the template unusable for a broadcast, which has no way to name one.\n"}}},"EmailTemplateDuplicate":{"type":"object","additionalProperties":false,"description":"Optional parameters when duplicating an email template. The body may be omitted entirely to accept the defaults.\n","properties":{"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"description":"The copy's workspace-unique handle, and the stable alternative to the template ID when sending by template. It can contain lowercase letters, numbers, hyphens, and underscores. Omit it to derive one from the source (for example, `welcome-email-copy`), with a numeric suffix if that slug is already taken. Two prefixes are rejected: `bird_`, reserved for our built-in templates, and `emt_`, the template ID format, which a slug could never be distinguished from. If you supply a slug that is already in use in the workspace, the request returns a conflict.\n","example":"welcome-email-copy"}}},"EmailTemplatePreviewContent":{"type":"object","additionalProperties":false,"description":"Content to render instead of the template's stored draft. Give it the subject and bodies you have in hand and they are rendered exactly as the draft would be, so an editor can show what a change looks like before it is saved.\n","properties":{"subject":{"type":"string","maxLength":998,"description":"The subject line to render.","example":"Welcome to Acme, {{ bird.contact.first_name }}!"},"preview_text":{"type":"string","maxLength":255,"description":"The preview text to render. It is folded into the top of the HTML the same way publishing folds it, so the rendered body carries the hidden preheader a recipient's inbox would read.\n","example":"{{ bird.contact.first_name }}, your order is on its way"},"html":{"type":"string","maxLength":524288,"description":"The HTML body to render.","example":"<h1>Hi {{ bird.contact.first_name }}</h1>"},"text":{"type":"string","maxLength":524288,"description":"The plain-text body to render. Omit it and a plain-text alternative is derived from the HTML, the same way it is derived when you publish.\n"}}},"EmailTemplatePreviewRequest":{"type":"object","additionalProperties":false,"description":"Sample values and options for rendering a template preview. Omit the body entirely to preview the current draft: `bird.contact.` tokens then fill from stand-in values, and a parameter you give no value for renders as empty.\n","properties":{"content":{"allOf":[{"$ref":"#/components/schemas/EmailTemplatePreviewContent"}],"description":"Render this content rather than the template's stored draft. It is what an editor uses to show a change as it is made, since nothing has to be saved first.\n\nThe content is treated exactly as a draft would be: personalization is filled in the same way, a plain-text body is derived from the HTML when you omit it, and content that could not be published is refused with the same error. `version` asks for a published version's own content, so the two cannot be combined.\n"},"parameters":{"type":"object","additionalProperties":true,"description":"Sample values for the variables the template uses, for this one preview only. A variable takes its value under its own name. A `bird.` value nests to match the token, so `{\"bird\": {\"contact\": {\"first_name\": \"Ada\"}}}` fills `{{ bird.contact.first_name }}`.\n\nA preview is more forgiving than a send: a parameter you leave out renders as empty here rather than being rejected. `parameters` is capped at 16 KB once serialized.\n","example":{"bird":{"contact":{"first_name":"Ada"}},"animal":"otter"}},"contact":{"allOf":[{"$ref":"#/components/schemas/ContactID"}],"description":"Render the template the way this contact would receive it. Every `{{ bird.contact.… }}` token takes its value from the contact's record, narrowed to the attributes the template reads and filled from each property's `fallback_value` where the contact holds no value: the same values a broadcast to this contact would send.\n\nValues are read as the contact stands right now, so a preview reflects an edit to their record as soon as you make it. A `bird.contact.…` value you also pass in `parameters` wins for that one attribute, so you can preview a contact with one field changed without editing them.\n"},"language":{"$ref":"#/components/schemas/LanguageTag","description":"Which of the template's languages to render. Omit it to render the default language. When the template does not have the language you ask for, its own `on_missing_language` setting decides whether a close match is rendered instead or the request is rejected. It is the same choice the send makes.\n"},"version":{"allOf":[{"$ref":"#/components/schemas/EmailTemplateVersionID"}],"description":"Preview a specific published version by its id, instead of the current draft.\n"}}},"EmailCompatibilityReportSeverity":{"type":"string","minLength":1,"readOnly":true,"enum":["problem","warning","none"],"x-enum-varnames":["EmailCompatibilityReportSeverityProblem","EmailCompatibilityReportSeverityWarning","EmailCompatibilityReportSeverityNone"],"description":"The worst severity across every finding the response was computed from, which\nis the authoritative reading: a response that caps how many findings it lists\nstill accounts here for the ones it left out. Each response's `compatibility`\nsays which content it covered.\n\n- `problem`: at least one finding is a `problem`.\n- `warning`: every finding is a `warning`.\n- `none`: there are no findings.\n","example":"warning"},"EmailCompatibilityRuleID":{"type":"string","minLength":1,"readOnly":true,"description":"Which rule produced a finding.\n\n- `html_script`: a `<script>` tag.\n- `html_event_handlers`: a JavaScript event-handler attribute such as `onclick`.\n- `html_embedded_content`: an `<iframe>`, `<embed>`, or `<object>`.\n- `html_linked_stylesheet`: a `<link rel=\"stylesheet\">`.\n- `css_at_import`: an `@import` rule.\n- `html_form`: a `<form>`, `<input>`, `<select>`, or `<textarea>`.\n- `html_svg`: an inline `<svg>`.\n- `html_media`: a `<video>` or `<audio>` element.\n- `css_display_flex_grid`: `display: flex` or `display: grid`, and their `inline-` forms.\n- `css_position_fixed_sticky`: `position: fixed` or `position: sticky`.\n- `css_variables_no_fallback`: a `var()` with no fallback value.\n- `css_viewport_units`: a `vh` or `vw` length.\n- `html_button`: a `<button>` element.\n- `css_math_functions`: `clamp()`, `min()`, or `max()`.\n- `css_modern_color`: `oklch()`, `oklab()`, `lch()`, or `lab()`.\n- `html_web_page_markup`: markup a web framework left behind, such as a `data-reactroot` attribute or a `__next` element id.\n","x-extensible-enum":["html_script","html_event_handlers","html_embedded_content","html_linked_stylesheet","css_at_import","html_form","html_svg","html_media","css_display_flex_grid","css_position_fixed_sticky","css_variables_no_fallback","css_viewport_units","html_button","css_math_functions","css_modern_color","html_web_page_markup"],"example":"css_display_flex_grid"},"EmailCompatibilitySeverity":{"type":"string","minLength":1,"readOnly":true,"enum":["problem","warning"],"x-enum-varnames":["EmailCompatibilitySeverityProblem","EmailCompatibilitySeverityWarning"],"description":"What a finding costs you.\n\n- `problem`: the pattern does nothing at all. The client removes the markup, never loads the stylesheet carrying it, or will not operate the control. Where a finding names clients, that is what happens in those clients.\n- `warning`: it does something, but not what you wrote.\n\nNeither one refuses a save, a submit, or a send.\n","example":"warning"},"EmailClientFamily":{"type":"string","minLength":1,"readOnly":true,"description":"Which mail client a finding applies to. A finding's `message` names at most\nApple Mail, Gmail, Outlook, and Yahoo; its `unsupported_clients` and\n`partial_clients` name every client affected.\n\n- `gmail`: Gmail\n- `outlook`: Outlook\n- `yahoo`: Yahoo\n- `apple_mail`: Apple Mail\n- `aol`: AOL\n- `thunderbird`: Mozilla Thunderbird\n- `samsung_email`: Samsung Email\n- `sfr`: SFR\n- `orange`: Orange\n- `protonmail`: ProtonMail\n- `hey`: HEY\n- `mail_ru`: Mail.ru\n- `fastmail`: Fastmail\n- `laposte`: LaPoste.net\n- `gmx`: GMX\n- `web_de`: WEB.DE\n- `ionos_1and1`: 1&1\n- `wp_pl`: WP.pl\n","x-extensible-enum":["gmail","outlook","yahoo","apple_mail","aol","thunderbird","samsung_email","sfr","orange","protonmail","hey","mail_ru","fastmail","laposte","gmx","web_de","ionos_1and1","wp_pl"],"example":"outlook"},"EmailClientPlatform":{"type":"string","minLength":1,"readOnly":true,"description":"Which build of a client family a finding applies to. A family can support a\nfeature in one build and not another, so Gmail on iOS is tracked separately\nfrom Gmail on the web.\n\n- `desktop_webmail`: Desktop Webmail\n- `mobile_webmail`: Mobile Webmail\n- `ios`: iOS\n- `android`: Android\n- `windows`: Windows\n- `macos`: macOS\n- `windows_mail`: Windows Mail\n- `outlook_com`: Outlook.com\n","x-extensible-enum":["desktop_webmail","mobile_webmail","ios","android","windows","macos","windows_mail","outlook_com"],"example":"windows"},"EmailClientSupport":{"type":"object","additionalProperties":false,"description":"One mail client family a finding applies to, and which of that family's platforms.\n","required":["family","platforms"],"properties":{"family":{"readOnly":true,"$ref":"#/components/schemas/EmailClientFamily"},"platforms":{"type":"array","minItems":1,"readOnly":true,"items":{"$ref":"#/components/schemas/EmailClientPlatform"},"description":"Which of the family's platforms this applies to, in alphabetical order.\n","example":["windows","windows_mail"]}}},"EmailCompatibilityFinding":{"type":"object","additionalProperties":false,"description":"One pattern in the HTML that mail clients remove, ignore, or render inconsistently.\n","required":["rule_id","severity","language","field","message","fix","partial","line","column","match","unsupported_clients","partial_clients"],"properties":{"rule_id":{"readOnly":true,"description":"The rule that produced this finding.","$ref":"#/components/schemas/EmailCompatibilityRuleID"},"severity":{"readOnly":true,"$ref":"#/components/schemas/EmailCompatibilitySeverity"},"language":{"readOnly":true,"description":"Which language's content this finding is in. Null when the call covered a single language.\n","oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}]},"field":{"type":"string","minLength":1,"readOnly":true,"description":"Which field of that language the finding is in. Always `html`; the subject and the plain-text body are not checked.\n","example":"html"},"message":{"type":"string","minLength":1,"readOnly":true,"description":"What is wrong and which clients it affects, worded to show to whoever is authoring the template. It covers the rule's whole category rather than the exact text that matched, so a rule covering `<video>` and `<audio>` names both whichever one is on the line. Show `fix` and then `partial` after it.\n","example":"`display: flex` and `grid` don't work in Outlook (Windows/Windows Mail)."},"fix":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"What to use instead. Null when there is no drop-in alternative and the fix is a restructure.\n","example":"Use tables for layout."},"partial":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Which clients support the feature only partly. Null when no client's support is partial.\n","example":"Partial support for `display: flex` and `grid` in Gmail (iOS/Android)."},"line":{"type":"integer","minimum":1,"readOnly":true,"description":"The 1-based line the pattern is on, in the HTML the containing response's `compatibility` says it covered.\n","example":14},"column":{"type":"integer","minimum":1,"readOnly":true,"description":"The 1-based column the pattern starts at, on that line.","example":6},"match":{"type":"string","minLength":1,"maxLength":256,"readOnly":true,"description":"The source text that matched, starting at `line` and `column`: the smallest span that identifies what is wrong. Never the enclosing line. For a finding on a whole element, the span runs from the opening tag through the close tag, because the client drops the element's content along with its markup. Cut at 256 characters, so an element holding a long body is quoted from its start rather than in full.\n","example":"display: flex"},"unsupported_clients":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/EmailClientSupport"},"description":"Every client family that does not support the feature at all, in alphabetical order by each entry's `family`. Empty on a finding whose `message`, `fix`, and `partial` name no client. `message` names at most four families; this names all of them.\n"},"partial_clients":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/EmailClientSupport"},"description":"Every client family that renders something other than what you wrote, in alphabetical order by each entry's `family`. Empty when no client's support is partial, which is also when `partial` is null. `partial` names at most four families; this names all of them.\n"}}},"EmailTemplatePreview":{"type":"object","additionalProperties":false,"description":"A rendered preview of an email template: its subject, HTML, and plain-text bodies with the supplied sample values filled in, ready to display.\n","required":["subject","html","text","variables","language","compatibility","compatibility_severity"],"properties":{"subject":{"type":["string","null"],"readOnly":true,"description":"The rendered subject line. Null when the template has no subject."},"html":{"type":["string","null"],"readOnly":true,"description":"The rendered HTML body. Null when the template has no HTML body."},"text":{"type":["string","null"],"readOnly":true,"description":"The rendered plain-text body. Derived from the HTML when the template has no separate plain-text body, and null when it has neither.\n"},"language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language this preview rendered. It differs from the language you asked for when the template does not have that one and a close match was served instead.\n"},"variables":{"type":"array","readOnly":true,"description":"The variables you can fill in with `parameters`. This list covers only the\nlanguage named by `language`. A version read combines the variables from\nevery language the version holds. Preview each language separately to see\nits own variables.\n\nVariables under the reserved `bird.` namespace are not listed here. We\nsupply those values, but you can nest sample values under `bird` in\n`parameters` to preview them.\n","items":{"$ref":"#/components/schemas/TemplateVariable"}},"compatibility_severity":{"readOnly":true,"$ref":"#/components/schemas/EmailCompatibilityReportSeverity"},"compatibility":{"type":"array","readOnly":true,"maxItems":200,"items":{"$ref":"#/components/schemas/EmailCompatibilityFinding"},"description":"What the previewed HTML uses that mail clients remove, ignore, or render inconsistently, in the order the patterns appear. Empty when nothing is worth reporting. Line and column count in the `content.html` you supplied, or in the template's own HTML when you supplied none, so they address the source rather than the rendered output. Previewing a published `version` is the exception: where the stored version keeps no authored copy of a language the publish step rewrote, the positions count in that rewritten body, which no response returns. A preview renders either way. At most 200 findings come back, the first 200 in source order; `compatibility_severity` is derived from every finding the HTML produced, including any beyond those 200.\n"}}},"EmailTemplateVersionStatus":{"type":"string","minLength":1,"readOnly":true,"enum":["draft","published","archived"],"x-enum-varnames":["EmailTemplateVersionStatusDraft","EmailTemplateVersionStatusPublished","EmailTemplateVersionStatusArchived"],"description":"Whether this version is still being edited or has been published. It records\nthe version's publication history: a version that a later one replaced stays\n`published`. The template's `live_version_id` names the version a send\nresolves to now.\n\n`archived` is reserved and no version carries it yet. Version retirement will\nproduce it, so it is declared here ahead of that feature: a client written\nagainst this list today keeps working when the first archived version arrives,\nrather than the value's arrival being a breaking change.\n","example":"published"},"Actor":{"type":"object","additionalProperties":false,"required":["id","type"],"properties":{"id":{"type":"string","minLength":1,"description":"Actor identifier.","example":"usr_01krdgeqcxet5s7t44vh8rt9mg"},"type":{"type":"string","minLength":1,"x-extensible-enum":["user","api_key","oauth_token","system","sso","service_account","automation"],"description":"New actor types may be added. Treat unrecognized values as future types, not errors.\n- `user`: a member's own session.\n- `api_key`: a workspace API key.\n- `oauth_token`: a token issued to a caller on a member's behalf.\n- `system`: an action we perform without a customer actor.\n- `sso`: an organization's SSO connection.\n- `service_account`: a workspace's connected Integration acting with no member behind it.\n- `automation`: an automation execution in your workspace.","example":"user"},"display_name":{"type":["string","null"],"readOnly":true,"description":"The label the actor is shown under: typically a member's name or email address, or the API key's name. Null when it could not be resolved.\n"}}},"EmailTemplateVersionSummary":{"type":"object","additionalProperties":false,"description":"One version of a template, without its content. Version history lists every version a template has ever had, and each one has a full copy of the content in every language it was published with. The listing describes the versions; read a single version to get what it holds.\n","required":["id","template_id","status","revision","variables","default_language","available_languages","created_at"],"properties":{"id":{"readOnly":true,"description":"Template version ID.","$ref":"#/components/schemas/EmailTemplateVersionID"},"template_id":{"readOnly":true,"description":"The template this version belongs to.","$ref":"#/components/schemas/EmailTemplateID"},"version_number":{"type":["integer","null"],"minimum":1,"readOnly":true,"description":"Sequential published-version number (1, 2, 3…). Null while the version is a draft."},"status":{"$ref":"#/components/schemas/EmailTemplateVersionStatus"},"revision":{"type":"integer","minimum":0,"readOnly":true,"description":"The version's revision counter."},"variables":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/TemplateVariable"},"description":"Every variable this version's content uses. You supply a value for each of them when you send.\n\nThe list combines all the languages, because languages do not have to use the same variables: if the English body uses `discount_code` and the French body uses `shipping_date`, both appear here. Send a value for every variable in the list rather than only the ones you expect the language you are sending to use. A language that does not use a variable ignores the value you sent for it, and a variable the sent language does use but you left out is rejected with a `422` naming it.\n\nVariables under the reserved `bird.` namespace are not listed here. We fill those in ourselves from the recipient's contact record.\n"},"default_language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language this version treats as its default: the one a send uses when it names none, and the last resort when a requested language is not available.\n"},"available_languages":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/LanguageTag"},"description":"The languages this version holds, as BCP-47 tags: the keys its `languages` map would return, without the content itself.\n","example":["en","pt-BR"]},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When this version was created."},"published_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this version was published, or null if it has not been published."},"updated_by":{"oneOf":[{"$ref":"#/components/schemas/Actor"},{"type":"null"}],"readOnly":true,"description":"Who last saved this version: a member's own session, an OAuth token delegated from one, or a workspace API key. Publishing freezes a version, so on a published one this is whoever published it. Null means no actor is on record: a built-in template, which is code-defined rather than stored, or a version last saved by an API key before this field existed. Every other version has one, even when its display_name could not be resolved (a member whose account is gone, say).\n"}}},"EmailTemplateVersionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"One page of the template's versions, newest first. Each entry describes a version and which languages it holds. Read a single version if you want its actual content.\n","items":{"$ref":"#/components/schemas/EmailTemplateVersionSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EmailTemplateBroadcastSummary":{"type":"object","additionalProperties":false,"description":"One broadcast that blocks deleting this template.","required":["id","status","created_at"],"properties":{"id":{"readOnly":true,"description":"Broadcast ID.","$ref":"#/components/schemas/EmailBroadcastID"},"status":{"readOnly":true,"description":"Where the broadcast has got to. Only `scheduled` and `accepted` appear here: those are the two that have not pinned their content yet, so they are the ones blocking the delete. This list carries the status alone; the per-recipient totals live on the broadcast itself.\n","example":"scheduled","allOf":[{"$ref":"#/components/schemas/EmailBroadcastStatus"}]},"scheduled_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the broadcast is due to send, or null when it is not scheduled.","example":"2026-07-03T09:00:00Z"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the broadcast was created.","example":"2026-07-01T00:00:00Z"}}},"EmailTemplateBroadcastList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of broadcasts blocking a delete of the template, newest first.","items":{"$ref":"#/components/schemas/EmailTemplateBroadcastSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EmailTemplateVersion":{"type":"object","additionalProperties":false,"required":["id","template_id","status","revision","variables","languages","default_language","created_at"],"properties":{"id":{"readOnly":true,"description":"Template version ID.","$ref":"#/components/schemas/EmailTemplateVersionID"},"template_id":{"readOnly":true,"description":"The template this version belongs to.","$ref":"#/components/schemas/EmailTemplateID"},"version_number":{"type":["integer","null"],"minimum":1,"readOnly":true,"description":"Sequential published-version number (1, 2, 3…). Null while the version is a draft."},"status":{"$ref":"#/components/schemas/EmailTemplateVersionStatus"},"revision":{"type":"integer","minimum":0,"readOnly":true,"description":"The version's revision counter."},"variables":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/TemplateVariable"},"description":"Every variable this version's content uses. You supply a value for each of them when you send.\n\nThe list combines all the languages, because languages do not have to use the same variables: if the English body uses `discount_code` and the French body uses `shipping_date`, both appear here. Send a value for every variable in the list rather than only the ones you expect the language you are sending to use. A language that does not use a variable ignores the value you sent for it, and a variable the sent language does use but you left out is rejected with a `422` naming it.\n\nVariables under the reserved `bird.` namespace are not listed here. We fill those in ourselves from the recipient's contact record.\n"},"languages":{"type":"object","readOnly":true,"propertyNames":{"$ref":"#/components/schemas/LanguageTag"},"additionalProperties":{"$ref":"#/components/schemas/EmailTemplateLanguageContent"},"description":"The content this version holds, keyed by language tag in BCP-47 form such as `en` or `pt-BR`. Publishing freezes every language together, so a version shows exactly what it would send in each of them. On a published version this is the send content.\n"},"default_language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language this version treats as its default: the one a send uses when it names none, and the last resort when a requested language is not available.\n"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When this version was created."},"published_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this version was published, or null if it has not been published."},"updated_by":{"oneOf":[{"$ref":"#/components/schemas/Actor"},{"type":"null"}],"readOnly":true,"description":"Who last saved this version: a member's own session, an OAuth token delegated from one, or a workspace API key. Publishing freezes a version, so on a published one this is whoever published it. Null means no actor is on record: a built-in template, which is code-defined rather than stored, or a version last saved by an API key before this field existed. Every other version has one, even when its display_name could not be resolved (a member whose account is gone, say).\n"}}},"EmailTemplateLanguageSummary":{"type":"object","additionalProperties":false,"description":"One language of a template version, without its content: enough to list what a version holds and how big each language is. Read a single language to get its content and its compatibility report.\n","required":["language","revision"],"properties":{"language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language, in its canonical form."},"revision":{"type":"integer","minimum":0,"readOnly":true,"description":"This language's revision counter, to send back when you save it. It counts only this language's own changes.\n"},"content_hash":{"type":["string","null"],"readOnly":true,"description":"A hash over this language's content, prefixed with the algorithm that produced it (`sha256:`), so the algorithm can change without the field becoming ambiguous. It tells you whether a language differs without transferring the content, and is comparable only within one version of this API. Null for a language saved before fingerprints were recorded.\n","example":"sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this language was last saved. Null if that is not recorded."},"has_html":{"type":"boolean","readOnly":true,"description":"Whether this language has an HTML body."},"has_text":{"type":"boolean","readOnly":true,"description":"Whether this language has a plain-text body."}}},"EmailTemplateLanguageList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Every language the version holds, ordered by language tag, without their content. Read a single language to get its content.\n","items":{"$ref":"#/components/schemas/EmailTemplateLanguageSummary"}}}},"EmailTemplateLanguage":{"type":"object","additionalProperties":false,"description":"One language of a template version: its content plus the identity a concurrent-edit check needs. Reading a language returns everything you need to edit it and save it back.\n","required":["language","revision","compatibility","compatibility_severity"],"properties":{"language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language this content belongs to, in its canonical form. Send a tag in any casing and this reports the form the template stores.\n"},"revision":{"type":"integer","minimum":0,"readOnly":true,"description":"This language's revision counter. Send it back when you save this language so a concurrent edit is caught instead of silently overwritten. It counts only this language's own changes, so editing another language never invalidates it.\n"},"content_hash":{"type":["string","null"],"readOnly":true,"description":"A hash over this language's content, prefixed with the algorithm that produced it (`sha256:`), so the algorithm can change without the field becoming ambiguous. It tells you whether a language differs without transferring the content, and is comparable only within one version of this API. Null for a language saved before fingerprints were recorded.\n","example":"sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this language was last saved. Null if that is not recorded."},"content":{"$ref":"#/components/schemas/EmailTemplateLanguageContent"},"compatibility_severity":{"readOnly":true,"$ref":"#/components/schemas/EmailCompatibilityReportSeverity"},"compatibility":{"type":"array","readOnly":true,"maxItems":200,"items":{"$ref":"#/components/schemas/EmailCompatibilityFinding"},"description":"What the stored HTML uses that mail clients remove, ignore, or render inconsistently, in the order the patterns appear. Empty when nothing is worth reporting. Line and column count in the `content.html` this response carries. At most 200 findings come back, the first 200 in source order; `compatibility_severity` is derived from every finding the HTML produced, including any beyond those 200.\n"}}},"EmailTemplateLanguageUpsert":{"type":"object","additionalProperties":false,"description":"Content to save for one language, replacing whatever that language held. Send every field you want the language to keep: a field you omit is cleared, which is what makes saving the same content twice land the same way every time.\n","required":["subject"],"properties":{"subject":{"type":"string","minLength":1,"maxLength":998,"description":"The email subject line for this language.","example":"Welcome to Acme, {{ bird.contact.first_name }}!"},"preview_text":{"type":"string","maxLength":255,"description":"The line an inbox shows after the subject in the message list. Leave it out and the inbox shows the opening words of the body instead.\n","example":"{{ bird.contact.first_name }}, your order is on its way"},"html":{"type":"string","maxLength":524288,"description":"The HTML body for this language.","example":"<h1>Hi {{ bird.contact.first_name }}</h1>"},"text":{"type":"string","maxLength":524288,"description":"The plain-text body for this language. Omit it and a plain-text alternative is derived from the HTML when you submit.\n"},"revision":{"type":"integer","minimum":0,"description":"The revision you last read for this language, to detect a concurrent edit. The save is rejected with a conflict if the language moved on since. Omit it to save unconditionally. Creating a language does not need one.\n"}}},"EmailTemplateRef":{"type":"string","minLength":1,"readOnly":true,"description":"The template this call addressed, as its id, even when you addressed it by slug. Send it back as `template_ref` on a follow-up call.\n","example":"emt_01krdgeqcxet5s7t44vh8rt9mg"},"EmailTemplateLanguageSaved":{"type":"object","additionalProperties":false,"description":"The identity, revision counters, and fingerprint of a saved language. The response does not include its content; read the language to retrieve it.\n","required":["language","revision","draft_revision","template_ref","version_id","compatibility","compatibility_severity"],"properties":{"language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language that was saved, in its canonical form. Send a tag in any casing and this reports the form the template stores.\n"},"revision":{"type":"integer","minimum":0,"readOnly":true,"description":"This language's new revision. Send it back on your next save of this language so a concurrent edit is caught instead of silently overwritten.\n"},"draft_revision":{"type":"integer","minimum":0,"readOnly":true,"description":"The draft's new revision. Saving a language moves it, so any template update you make next must send this value instead of the revision you read before the save.\n"},"content_hash":{"type":["string","null"],"readOnly":true,"description":"A hash over the language's content as saved, prefixed with the algorithm that produced it (`sha256:`), so the algorithm can change without the field becoming ambiguous. It tells you whether a language differs without transferring the content, and is comparable only within one version of this API.\n","example":"sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this language was saved."},"template_ref":{"readOnly":true,"description":"The template this save addressed.","$ref":"#/components/schemas/EmailTemplateRef"},"version_id":{"readOnly":true,"description":"The draft this save wrote to.","$ref":"#/components/schemas/EmailTemplateVersionID"},"compatibility_severity":{"readOnly":true,"$ref":"#/components/schemas/EmailCompatibilityReportSeverity"},"next":{"type":"array","readOnly":true,"description":"What to do next with this save. Present on reads that compute it: an empty list means\nthere is nothing to do, and the field is absent entirely on responses that do not\nreport next actions.\n\nA `problem` in `compatibility` routes back to this same write, with the identifiers\nto address it already on this response; a `warning` says what degrades and leaves\nthe draft as it is.\n","items":{"$ref":"#/components/schemas/NextAction"}},"compatibility":{"type":"array","readOnly":true,"maxItems":200,"items":{"$ref":"#/components/schemas/EmailCompatibilityFinding"},"description":"What the HTML you just saved uses that mail clients remove, ignore, or render inconsistently, in the order the patterns appear. Empty when nothing is worth reporting. Advisory: the language was saved either way, and a finding never refuses a write. Line and column count in the HTML as saved, which the language read returns as `content.html`. A partial update reports on the language in full rather than on the fields it carried, so it reads the same as the read of the same language. At most 200 findings come back, the first 200 in source order; `compatibility_severity` is derived from every finding the HTML produced, including any beyond those 200.\n"}}},"EmailTemplateLanguageUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"description":"A partial edit to one language: send only the fields you are changing, and the rest keep their current values. The language must already exist. Create it by saving its full content instead.\n","properties":{"subject":{"type":"string","minLength":1,"maxLength":998,"description":"A new email subject line for this language.","example":"Welcome to Acme, {{ bird.contact.first_name }}!"},"preview_text":{"type":["string","null"],"maxLength":255,"description":"A new line for the inbox to show after the subject in the message list. Send null to clear it, and the inbox shows the opening words of the body instead.\n","example":"{{ bird.contact.first_name }}, your order is on its way"},"html":{"type":"string","maxLength":524288,"description":"A new HTML body for this language.","example":"<h1>Hi {{ bird.contact.first_name }}</h1>"},"text":{"type":["string","null"],"maxLength":524288,"description":"A new plain-text body for this language. Send null to clear it, and a plain-text alternative is derived from the HTML when you submit.\n"},"revision":{"type":"integer","minimum":0,"description":"The revision you last read for this language, to detect a concurrent edit. The edit is rejected with a conflict if the language moved on since. Omit it to apply the edit unconditionally.\n"}}},"EmailTemplateRollback":{"type":"object","additionalProperties":false,"description":"Parameters for rolling a template back to an earlier published version. Rolling back also replaces the draft with that version's content, so you have to pass `revision`, the revision number of the draft you last read. If the draft has changed since you read it, that revision number is now stale, and the rollback fails with a conflict instead of overwriting the newer draft. Read the draft again to get its current revision, and try the rollback again.\n","required":["revision"],"properties":{"revision":{"$ref":"#/components/schemas/EmailTemplateDraftRevision"}}},"EmailTemplateSubmit":{"type":"object","additionalProperties":false,"description":"Options for a submit. Every field here is optional, so an empty request body submits the draft exactly as it stands.\n","properties":{"validate_only":{"type":"boolean","default":false,"description":"Check the draft without actually submitting it. Every language gets checked and every problem gets reported back to you, but nothing is frozen and no new version gets created. Give a validation run its own `Idempotency-Key`, separate from the real submit that follows it. You can also send no key. The validation request and real submit have different bodies, so using the same key for both is rejected as key reuse.\n"},"expected_revision":{"$ref":"#/components/schemas/EmailTemplateDraftRevision"},"languages":{"type":"array","minItems":1,"maxItems":25,"items":{"$ref":"#/components/schemas/LanguageTag"},"description":"Languages to process when submitting an already-published version. Email templates accept submissions only for drafts, so setting this field for an email template is rejected.\n"}},"example":{"expected_revision":3}},"EmailTemplateSubmitProblem":{"type":"object","additionalProperties":false,"description":"One problem found while checking whether a version can be submitted.","required":["code","message"],"properties":{"language":{"readOnly":true,"description":"The language this problem is about. Null when the problem is about the whole version rather than one language, for example an empty draft, or a default language the draft does not have.\n","oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}]},"field":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Which field within that language has the problem, such as `subject` or `html`. Null when the problem is not about one particular field.\n","example":"subject"},"code":{"type":"string","minLength":1,"pattern":"^E\\d{5}$","readOnly":true,"description":"The error code a real submit would fail with. Look it up in the error catalog to see what it means and what to do about it.\n","example":"E04051"},"message":{"type":"string","minLength":1,"readOnly":true,"description":"What is wrong, worded so you can show it directly to whoever is authoring the template.\n","example":"A subject is required to submit."}}},"EmailTemplateSubmitResult":{"type":"object","additionalProperties":false,"description":"The outcome of a submit. This has the same shape whether the submit was a validation run or a real one, so you can read `valid` and `version` the same way either time to see what happened.\n","required":["valid","errors","template_ref","version_id","compatibility","compatibility_severity"],"properties":{"valid":{"type":"boolean","readOnly":true,"description":"Whether the version passed every check."},"errors":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/EmailTemplateSubmitProblem"},"description":"Every problem found across the draft's languages. Empty when `valid` is `true`.\n"},"version":{"readOnly":true,"description":"The version this submit created, or null when it was only a validation run and nothing got frozen. As soon as this is not null, sends already use that version. No further action is required to make it live.\n","oneOf":[{"$ref":"#/components/schemas/EmailTemplateVersion"},{"type":"null"}]},"template_ref":{"readOnly":true,"description":"The template this submit addressed.","$ref":"#/components/schemas/EmailTemplateRef"},"version_id":{"readOnly":true,"description":"The draft this submit addressed: the one it froze, or on a validation run the one it checked and left as it was. The frozen version, when there is one, is `version`.\n","$ref":"#/components/schemas/EmailTemplateVersionID"},"compatibility_severity":{"readOnly":true,"$ref":"#/components/schemas/EmailCompatibilityReportSeverity"},"compatibility":{"type":"array","readOnly":true,"maxItems":200,"items":{"$ref":"#/components/schemas/EmailCompatibilityFinding"},"description":"What the draft's HTML uses that mail clients remove, ignore, or render inconsistently, across every language, in alphabetical order of language tag and then the order the patterns appear. Empty when nothing is worth reporting. Advisory, and separate from `errors`: a finding never fails a submit, so the version froze either way. Each finding names the `language` it is in, and its line and column count in that language's HTML, which the language read returns as `content.html`. At most 200 findings come back, the first 200 in that order, so a draft that reaches the cap can omit a later language's findings entirely rather than trimming each language: read a language's own findings from its read or its write. `compatibility_severity` is derived from every finding the draft produced, including any beyond those 200.\n"}}},"InboundAddressID":{"type":"string","minLength":1,"pattern":"^ina_[0-9a-hjkmnp-tv-z]{26}$","example":"ina_01krdgeqcxet5s7t44vh8rt9mg"},"InboundAddress":{"type":"object","additionalProperties":false,"description":"An email address we create for you, which receives mail on your behalf. Forward a real mailbox (for example, a support inbox) to this address. Every message sent to it becomes a received email you can retrieve.\n","required":["id","address","label","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/InboundAddressID","description":"Inbound address ID."},"address":{"type":"string","format":"email","minLength":5,"readOnly":true,"description":"The address to forward your mailbox to. We generate it when the inbound address is created.","example":"a1b2c3@inbound.eu.bird.com"},"label":{"type":["string","null"],"maxLength":255,"description":"Your own label for this address, typically the source mailbox it maps to. `null` when unset.","example":"Support mailbox"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the inbound address was created."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the inbound address was last updated."}}},"InboundAddressList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of inbound addresses, newest first.","items":{"$ref":"#/components/schemas/InboundAddress"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"InboundAddressCreate":{"type":"object","additionalProperties":false,"description":"Parameters for minting a new inbound address.","properties":{"label":{"type":"string","minLength":1,"maxLength":255,"description":"Your own label for this address, typically the source mailbox it maps to.","example":"Support mailbox"}},"example":{"label":"Support mailbox"}},"InboundAddressUpdate":{"type":"object","additionalProperties":false,"description":"Fields to update on an inbound address.","properties":{"label":{"type":["string","null"],"maxLength":255,"description":"Your own label for this address, typically the source mailbox it maps to. Send `null` to clear it. Omit the field to leave it unchanged.","example":"Support mailbox"}},"example":{"label":"Sales mailbox"}},"InboundEmailMessageID":{"type":"string","minLength":1,"pattern":"^rem_[0-9a-hjkmnp-tv-z]{26}$","example":"rem_01krdgeqcxet5s7t44vh8rt9mg"},"InboundAttachmentID":{"type":"string","minLength":1,"pattern":"^rea_[0-9a-hjkmnp-tv-z]{26}$","example":"rea_01krdgeqcxet5s7t44vh8rt9mg"},"InboundAttachment":{"type":"object","additionalProperties":false,"required":["id","filename","content_type","size"],"description":"Metadata for a file attached to a received email. The raw bytes are fetched separately with `GET /v1/email/inbound-messages/{inbound_message_id}/attachments/{attachment_id}`.\n","properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/InboundAttachmentID","description":"Attachment ID, stable within the received message."},"filename":{"type":["string","null"],"description":"Filename from the attachment's Content-Disposition, or `null` when the sender did not name the part.","example":"invoice.pdf"},"content_type":{"type":["string","null"],"description":"MIME type parsed from the attachment part, or `null` when absent.","example":"application/pdf"},"size":{"type":"integer","minimum":0,"description":"Size of the attachment in bytes.","example":215432}}},"InboundEmailMessage":{"type":"object","additionalProperties":false,"description":"An email received on your behalf, parsed from the original message. Fetch the body with `/body`, the original MIME with `/raw`, and attachment bytes with `/attachments/{attachment_id}`.\n","required":["id","from","to","cc","subject","message_id","in_reply_to","thread_id","authentication","spf_pass","dkim_pass","dmarc_pass","spam_score","attachments","received_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/InboundEmailMessageID","description":"Received message ID."},"from":{"$ref":"#/components/schemas/EmailAddress","description":"Sender address parsed from the message."},"to":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddress"},"description":"Recipients on the message's To header."},"cc":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddress"},"description":"Recipients on the message's Cc header."},"subject":{"type":["string","null"],"description":"Subject line as received, or `null` when the message had no subject.","example":"Re: Your receipt"},"message_id":{"type":["string","null"],"description":"RFC 5322 Message-ID header from the sender, or `null` when the sender did not include one.","example":"<CAH+T8d5...@mail.gmail.com>"},"in_reply_to":{"type":["string","null"],"description":"The `In-Reply-To` header, which holds the Message-ID this message is replying to. `null` when the message is not a reply.","example":"<previous-message@example.com>"},"references":{"type":"array","items":{"type":"string"},"description":"The References header, which holds every Message-ID in this conversation, oldest first. Left out when the message arrived without one."},"thread_id":{"type":["string","null"],"description":"Conversation this message belongs to, or `null` when it is not grouped into one."},"authentication":{"type":["string","null"],"enum":["pass","fail","unknown",null],"description":"DMARC result for the domain in the received message's `From` header.\n\n- `pass`: SPF or DKIM passed and aligned with that domain.\n- `fail`: DMARC was evaluated and did not pass.\n- `unknown`: no trustworthy verdict is available.\n\nThis follows `dmarc_pass` and does not verify a particular person. The receiving provider currently supplies no DMARC result, so received messages report `unknown`.\n"},"spf_pass":{"type":["boolean","null"],"description":"Whether the receiving provider reports that SPF authorized the envelope sender for the sending server. A soft failure is `false`. Missing, neutral and inconclusive results are `null`."},"dkim_pass":{"type":["boolean","null"],"description":"Whether the receiving provider verified a DKIM signature. A passing signature makes this `true` even when another signature fails. Missing signatures and inconclusive verification results are `null`."},"dmarc_pass":{"type":["boolean","null"],"description":"Whether SPF or DKIM passed and aligned with the domain in the message's `From` header. The receiving provider currently supplies no DMARC result, so this is `null`."},"spam_score":{"type":["number","null"],"description":"Content spam score when available. The receiving provider currently supplies no score, so this is `null`."},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/InboundAttachment"},"description":"Metadata for each attachment found on the message. Empty when the message had no attachments."},"received_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the message was received."}}},"InboundEmailMessageList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of received emails, newest first.","items":{"$ref":"#/components/schemas/InboundEmailMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"InboundEmailMessageBody":{"type":"object","additionalProperties":false,"description":"The parsed body of a received email.","required":["html","text"],"properties":{"html":{"type":["string","null"],"description":"The HTML body of the message, or `null` when the message had no HTML part."},"text":{"type":["string","null"],"description":"The plain-text body of the message, or `null` when the message had no text part."}}},"InboundAttachmentList":{"type":"object","additionalProperties":false,"description":"The attachments on a received email. The response returns every attachment without pagination.","required":["data"],"properties":{"data":{"type":"array","description":"Metadata for every attachment on the message. Empty when the message had no attachments.","items":{"$ref":"#/components/schemas/InboundAttachment"}}}},"MailboxID":{"type":"string","minLength":1,"pattern":"^mbx_[0-9a-hjkmnp-tv-z]{26}$","example":"mbx_01krdgeqcxet5s7t44vh8rt9mg"},"MailboxOwner":{"type":"object","additionalProperties":false,"description":"The principal that owns the mailbox. Always the workspace.","required":["type","id"],"properties":{"type":{"type":"string","minLength":1,"enum":["workspace"],"readOnly":true,"description":"Owner principal type."},"id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Owner principal ID."}}},"Mailbox":{"type":"object","additionalProperties":false,"description":"A durable mailbox identity for an agent. A mailbox owns an email address, groups mail into threads, applies receive policy, and remembers message metadata, extracted text, and attachments for its retention tier. The body and raw MIME of each message remain available for 30 days.\n","required":["id","address","display_name","default_reply_to","receive_policy","state","channel","owner","inbound_address_id","retention_tier","message_count","thread_count","size_bytes","metadata","local_part_generated","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/MailboxID","description":"Mailbox ID."},"address":{"type":"string","format":"email","minLength":5,"readOnly":true,"description":"The mailbox's email address. Immutable once created.","example":"concierge@inbox.ai"},"display_name":{"type":["string","null"],"maxLength":255,"description":"Display name used as the sender name on mail from this mailbox. `null` when unset.","example":"Acme Concierge"},"default_reply_to":{"type":["string","null"],"format":"email","description":"Default `Reply-To` address stamped on mail sent from this mailbox. `null` when unset."},"receive_policy":{"type":"string","minLength":1,"enum":["open","replies_only","allowlist","drop"],"description":"Which inbound mail the mailbox accepts:\n\n- `open`: Accepts everything not blocked by a rule.\n- `replies_only`: Accepts only replies to messages this mailbox has\n  sent. A reply must match a message the mailbox sent. Landing in an\n  existing thread by itself does not count.\n- `allowlist`: Accepts only senders matching an allow rule. Replies to\n  prior outbound mail are always admitted unless blocked.\n- `drop`: Stores nothing.\n"},"state":{"type":"string","minLength":1,"enum":["active","suspended"],"readOnly":true,"description":"Lifecycle state. `active` means the mailbox can send, receive, and expose conversations. `suspended` pauses sending, conversation reads, and events; inbound mail is retained with the `blocked` label until you resume it."},"channel":{"type":"string","minLength":1,"enum":["email"],"readOnly":true,"description":"The channel this mailbox receives on. Always `email`."},"owner":{"readOnly":true,"$ref":"#/components/schemas/MailboxOwner"},"inbound_address_id":{"readOnly":true,"$ref":"#/components/schemas/InboundAddressID","description":"The underlying inbound address that receives this mailbox's mail."},"retention_tier":{"type":"string","minLength":1,"enum":["30d","90d","1y"],"description":"How long message metadata, extracted text, and attachments are kept. Original bodies and inbound raw MIME are limited to 30 days on every tier."},"message_count":{"type":"integer","format":"int64","readOnly":true,"description":"Number of retained messages across all threads."},"thread_count":{"type":"integer","format":"int64","readOnly":true,"description":"Number of retained threads."},"size_bytes":{"type":"integer","format":"int64","readOnly":true,"description":"Stored bytes across the mailbox's retained messages: subject, preview, extracted text, and attachment bytes. Message bodies and raw MIME expire after 30 days and do not count. Maintained with each message written or deleted, so the value is current; messages stored before the counter existed are not counted."},"unread_thread_count":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Number of threads with unread messages in this mailbox, excluding trash. `null` on create/update responses.\n"},"metadata":{"type":"object","additionalProperties":true,"description":"Your own key/value data attached to the mailbox. Up to 2 KB. Keys starting with `__bird` are reserved."},"local_part_generated":{"readOnly":true,"type":"boolean","description":"Whether we generated the local part of the address. `false` means a custom handle was chosen at creation. On the shared `inbox.ai` domain a custom handle counts against your plan's custom-handle allowance."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the mailbox was created."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the mailbox was last updated."},"deleted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the mailbox was deleted, or `null` if active. Deletion stops receiving; restore is available for 30 days unless permanent erasure has started."}}},"MailboxList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Mailbox"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"MailboxCreate":{"type":"object","additionalProperties":false,"description":"Parameters for creating a mailbox.","properties":{"local_part":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9._-]+$","description":"The local part of the mailbox address (the part before `@`). Letters, digits, dots, underscores, and hyphens. Stored lowercase. On the shared `inbox.ai` domain, separators must sit between letters or digits. Leading, trailing, and repeated separators are not allowed. Reserved names such as `postmaster` and `abuse` are unavailable. Choosing your own local part uses one of your plan's custom-handle allowance slots; generated addresses remain available. Omit this field to generate a random local part.","example":"concierge"},"domain":{"type":"string","minLength":1,"maxLength":255,"default":"inbox.ai","description":"The domain the address lives under. Defaults to `inbox.ai`, our shared mailbox domain. Creating a mailbox claims the shared address for your organization on a first-come, first-served basis. The address remains reserved to your organization after the mailbox is deleted. You can instead use one of your own domains enabled for receiving email.","example":"mail.acme.com"},"display_name":{"type":"string","minLength":1,"maxLength":255,"description":"Display name used as the sender name on mail from this mailbox.","example":"Acme Concierge"},"default_reply_to":{"type":"string","format":"email","minLength":5,"description":"Default `Reply-To` address stamped on mail sent from this mailbox."},"receive_policy":{"type":"string","enum":["open","replies_only","allowlist","drop"],"default":"open","description":"Which inbound mail the mailbox accepts:\n\n- `open`: Accepts everything not blocked by a rule.\n- `replies_only`: Accepts only replies to messages this mailbox has\n  sent. A reply must match a message the mailbox sent. Landing in an\n  existing thread by itself does not count.\n- `allowlist`: Accepts only senders matching an allow rule.\n- `drop`: Stores nothing.\n"},"retention_tier":{"type":"string","enum":["30d","90d","1y"],"default":"30d","description":"How long message metadata, extracted text, and attachments are kept. Original bodies and inbound raw MIME are limited to 30 days on every tier. Longer tiers require a plan that includes them."},"metadata":{"type":"object","additionalProperties":true,"description":"Your own key/value data to attach to the mailbox. Up to 2 KB. Keys starting with `__bird` are reserved."}},"example":{"local_part":"concierge","display_name":"Acme Concierge","receive_policy":"open","retention_tier":"30d"}},"MailboxUpdate":{"type":"object","additionalProperties":false,"description":"Fields to update on a mailbox. Omitted fields are unchanged. Fields set to `null` are cleared. The address and domain are immutable.","properties":{"display_name":{"type":["string","null"],"maxLength":255,"description":"Display name used as the sender name on mail from this mailbox. `null` clears it."},"default_reply_to":{"type":["string","null"],"format":"email","description":"Default `Reply-To` address stamped on mail sent from this mailbox. `null` clears it."},"receive_policy":{"type":"string","enum":["open","replies_only","allowlist","drop"],"description":"Which inbound mail the mailbox accepts:\n\n- `open`: Accepts everything not blocked by a rule.\n- `replies_only`: Accepts only replies to messages this mailbox has\n  sent. A reply must match a message the mailbox sent. Landing in an\n  existing thread by itself does not count.\n- `allowlist`: Accepts only senders matching an allow rule.\n- `drop`: Stores nothing.\n"},"retention_tier":{"type":"string","enum":["30d","90d","1y"],"description":"How long message metadata, extracted text, and attachments are kept. Original bodies and inbound raw MIME are limited to 30 days on every tier. Longer tiers require a plan that includes them. Lowering the tier requires `confirm=true` when messages would be affected; accepted changes hide those messages immediately. Deletion waits at least ten minutes and until the retention update finishes."},"metadata":{"type":"object","additionalProperties":true,"description":"Replaces the mailbox's key/value data. Up to 2 KB. Keys starting with `__bird` are reserved."}},"example":{"display_name":"Acme Concierge","retention_tier":"30d"}},"MailboxStatsSummary":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Single-row aggregate of the mailbox's email activity across the full requested period. Counts are sums of per-bucket counts across the window. Latency percentiles are computed across the whole period rather than summed per bucket. Rates are `null` when their denominator is zero.\n","required":["sends_accepted","delivery","engagement","latency","received"],"properties":{"sends_accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct email messages the mailbox sent that were accepted, counted at the message level and summed per bucket across the period.","example":231},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct emails the mailbox received, summed per bucket across the period.","example":519}}},"MailboxStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Per-mailbox email activity for one time bucket, bucketed by event time. Sent-mail metrics use the same delivery, engagement, and latency breakdowns as the email stats endpoints. `received` counts mail that arrived at the mailbox. Buckets with no activity are included with zero counts and `null` latency percentiles.\n","required":["bucket","sends_accepted","delivery","engagement","latency","received"],"properties":{"bucket":{"type":"string","minLength":1,"readOnly":true,"description":"The day (`YYYY-MM-DD`) or instant (RFC 3339, on the bucket boundary) this point covers, matching the period's grain.","example":"2026-07-21"},"sends_accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct email messages the mailbox sent that were accepted in this bucket, counted at the message level (one per accepted send regardless of how many recipients it addresses). Every other sent-mail metric in `delivery` and `engagement` is recipient-level or event-level.\n","example":12},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"received":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct emails the mailbox received in this bucket.","example":34}}},"MailboxStatsResponse":{"type":"object","additionalProperties":false,"description":"A mailbox's sent and received email statistics: a period-wide summary plus a bucketed time series. `period` echoes the range and grain actually used. `data` is one row per bucket in chronological order.\n","required":["period","summary","data"],"properties":{"period":{"$ref":"#/components/schemas/EmailStatsSeriesPeriod"},"summary":{"$ref":"#/components/schemas/MailboxStatsSummary"},"data":{"type":"array","readOnly":true,"description":"One row per bucket in the period, in chronological order. Buckets with no activity are included with zero counts.","items":{"$ref":"#/components/schemas/MailboxStatsPoint"}}}},"ReceiveRuleID":{"type":"string","minLength":1,"pattern":"^erl_[0-9a-hjkmnp-tv-z]{26}$","example":"erl_01krdgeqcxet5s7t44vh8rt9mg"},"ReceiveRule":{"type":"object","additionalProperties":false,"description":"An allow or block entry on a mailbox, evaluated when inbound mail arrives. Matching is against the message's envelope sender; domain entries also match subdomains. A given entry can be allow or block, never both.\n","required":["id","mailbox_id","action","entry","entry_type","note","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ReceiveRuleID","description":"Identifies this rule for deletion. There is no update operation."},"mailbox_id":{"readOnly":true,"$ref":"#/components/schemas/MailboxID","description":"The mailbox the rule applies to."},"action":{"type":"string","minLength":1,"enum":["allow","block"],"readOnly":true,"description":"What the rule does when it matches. Block rules always win: over allow rules and over the reply admission on allowlist mailboxes."},"entry":{"type":"string","minLength":1,"maxLength":255,"readOnly":true,"description":"The sender address or domain the rule matches. Domains also match their subdomains.","example":"partner.example.com"},"entry_type":{"type":"string","minLength":1,"enum":["address","domain"],"readOnly":true,"description":"Whether the entry is a full address or a domain."},"note":{"type":["string","null"],"maxLength":512,"readOnly":true,"description":"Your own note about why the rule exists. `null` when unset."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the rule was created."}}},"ReceiveRuleList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ReceiveRule"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"ReceiveRuleCreate":{"type":"object","additionalProperties":false,"description":"Parameters for adding a receive rule to a mailbox.","required":["action","entry"],"properties":{"action":{"type":"string","minLength":1,"enum":["allow","block"],"description":"What the rule does when it matches. Block rules always win. To flip an entry's action, delete the existing rule and re-create it."},"entry":{"type":"string","minLength":1,"maxLength":255,"description":"The sender address (`alice@example.com`) or domain (`example.com`) to match. Domains also match their subdomains. Stored lowercase.","example":"partner.example.com"},"note":{"type":"string","minLength":1,"maxLength":512,"description":"Your own note about why the rule exists."}},"example":{"action":"allow","entry":"partner.example.com","note":"Approved partner senders"}},"EmailInboundRouteID":{"type":"string","minLength":1,"pattern":"^ein_[0-9a-hjkmnp-tv-z]{26}$","example":"ein_01krdgeqcxet5s7t44vh8rt9mg"},"InboundRoute":{"type":"object","additionalProperties":false,"description":"A routing rule that directs inbound mail on one of your domains into a mailbox, or drops it. Routes are tried in priority order, lowest number first. Each mailbox's own address always matches at priority 10 and your own routes take a priority from 11 to 1000, so delivery to an exact address always takes precedence.\n","required":["id","domain","match_type","match_value","action","target_mailbox_id","priority","enabled","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/EmailInboundRouteID","description":"Inbound route ID."},"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The domain the route applies to.","example":"mail.acme.com"},"match_type":{"type":"string","minLength":1,"enum":["address","catch_all"],"description":"How the route matches recipients. `address` matches one local part. `catch_all` matches every recipient on the domain that nothing else matched."},"match_value":{"type":["string","null"],"maxLength":64,"description":"The local part an `address` route matches. `null` for `catch_all` routes.","example":"refunds"},"action":{"type":"string","minLength":1,"enum":["deliver_to_mailbox","drop"],"description":"What happens to matching mail. `deliver_to_mailbox` delivers it to `target_mailbox_id`. `drop` discards it silently, with nothing stored and no webhook fired."},"target_mailbox_id":{"oneOf":[{"$ref":"#/components/schemas/MailboxID"},{"type":"null"}],"description":"The mailbox that receives matching mail. `null` for `drop` routes."},"priority":{"type":"integer","minimum":11,"maximum":1000,"description":"The order routes are tried in, lowest number first. Your own routes take a priority from 11 to 1000, and default to 100. A mailbox's own address always matches at priority 10."},"enabled":{"type":"boolean","description":"Whether the route is evaluated. Disabled routes are kept but skipped."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the route was created."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the route was last updated."}}},"InboundRouteList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of inbound routes in evaluation order, with the lowest priority number first.","items":{"$ref":"#/components/schemas/InboundRoute"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"InboundRouteCreate":{"type":"object","additionalProperties":false,"description":"Parameters for creating an inbound route.","required":["domain","match_type","action"],"properties":{"domain":{"type":"string","minLength":1,"maxLength":255,"description":"The domain the route applies to. It has to be one of your inbound-enabled custom domains.","example":"mail.acme.com"},"match_type":{"type":"string","minLength":1,"enum":["address","catch_all"],"description":"How the route matches recipients. `address` matches one local part and requires `match_value`. `catch_all` matches every recipient on the domain that nothing else matched."},"match_value":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9._-]+$","description":"The local part an `address` route matches. Required for `address` routes. Stored lowercase.","example":"refunds"},"action":{"type":"string","minLength":1,"enum":["deliver_to_mailbox","drop"],"description":"What happens to matching mail. `deliver_to_mailbox` delivers it to `target_mailbox_id` (required). `drop` discards it silently, with nothing stored and no webhook fired."},"target_mailbox_id":{"$ref":"#/components/schemas/MailboxID","description":"The mailbox that receives matching mail. Required for `deliver_to_mailbox` routes."},"priority":{"type":"integer","minimum":11,"maximum":1000,"default":100,"description":"The order routes are tried in, lowest number first. Your own routes take a priority from 11 to 1000. The mailbox's own address always matches at priority 10, so a route can never pre-empt exact-address delivery."},"enabled":{"type":"boolean","default":true,"description":"Whether the route is evaluated."}},"example":{"domain":"mail.acme.com","match_type":"address","match_value":"refunds","action":"deliver_to_mailbox","target_mailbox_id":"mbx_01krdgeqcxet5s7t44vh8rt9mg"}},"InboundRouteUpdate":{"type":"object","additionalProperties":false,"description":"Fields to update on an inbound route. Omitted fields are unchanged. The domain is immutable.","properties":{"match_type":{"type":"string","enum":["address","catch_all"],"description":"How the route matches recipients."},"match_value":{"type":["string","null"],"maxLength":64,"description":"The local part an `address` route matches. `null` for `catch_all` routes."},"action":{"type":"string","enum":["deliver_to_mailbox","drop"],"description":"What happens to matching mail. `deliver_to_mailbox` delivers it to `target_mailbox_id` (required). `drop` discards it silently, with nothing stored and no webhook fired."},"target_mailbox_id":{"oneOf":[{"$ref":"#/components/schemas/MailboxID"},{"type":"null"}],"description":"The mailbox that receives matching mail. `null` for `drop` routes."},"priority":{"type":"integer","minimum":11,"maximum":1000,"description":"The order routes are tried in, lowest number first. Your own routes take a priority from 11 to 1000. The mailbox's own address always matches at priority 10."},"enabled":{"type":"boolean","description":"Whether the route is evaluated."}},"example":{"priority":50,"enabled":true}},"ThreadID":{"type":"string","minLength":1,"pattern":"^thr_[0-9a-hjkmnp-tv-z]{26}$","example":"thr_01krdgeqcxet5s7t44vh8rt9mg"},"EmailThreadHighlights":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Matched search fragments for a thread, one array per field the query matched, with the matched terms wrapped in `**`. A field is present only when the query matched it, so the keys that are present tell you which fields produced the hit. Returned only on thread search results.\n","properties":{"subject":{"type":"array","items":{"type":"string","minLength":1},"description":"Matched fragments from the conversation's subject.","example":["Re: your **order** **4821**"]},"text":{"type":"array","items":{"type":"string","minLength":1},"description":"Matched fragments from a message's body text.","example":["confirming your **order** **4821** shipped"]}}},"EmailThread":{"type":"object","additionalProperties":false,"description":"A conversation in a mailbox. It groups every message in both directions, the mail the mailbox received and the replies it sent, and it holds the conversation's read state, labels, and participant list. A message is retained until it is trashed or ages past the mailbox's retention tier. Only retained messages count toward the totals below.\n","required":["id","mailbox_id","channel","contact_id","subject","participants","message_count","unread_count","last_message_at","last_direction","labels","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ThreadID","description":"Thread ID."},"mailbox_id":{"readOnly":true,"$ref":"#/components/schemas/MailboxID","description":"Mailbox this conversation belongs to."},"channel":{"type":"string","readOnly":true,"minLength":1,"description":"Channel this conversation lives on. Always `email`.","example":"email"},"contact_id":{"oneOf":[{"$ref":"#/components/schemas/ContactID"},{"type":"null"}],"description":"Contact linked to this conversation, or null when none is linked."},"subject":{"type":["string","null"],"readOnly":true,"description":"Subject of the conversation, taken from its first message. Null when that message had no subject.","example":"Re: Your order"},"participants":{"type":"array","readOnly":true,"items":{"type":"string","format":"email"},"description":"Addresses that appear on the retained messages in this conversation, including the mailbox's own address."},"message_count":{"type":"integer","readOnly":true,"minimum":0,"description":"Number of retained messages in this conversation, both directions."},"unread_count":{"type":"integer","readOnly":true,"minimum":0,"description":"Number of retained received messages that are still unread. Spam and blocked mail is not counted."},"last_message_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the most recent retained message in this conversation was received or sent."},"last_direction":{"type":"string","enum":["inbound","outbound"],"minLength":1,"readOnly":true,"description":"Direction of the most recent message: `inbound` for a received message, `outbound` for a sent one."},"labels":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"maxItems":20,"description":"Labels on this conversation. Exactly one system placement label is always present, set by the message that started the conversation:\n\n- `inbox`: The conversation is in the inbox.\n- `archive`: The conversation was filed away and is done for now.\n- `spam`: The conversation's opening message is filed in Spam.\n- `blocked`: The conversation's opening message was rejected by the mailbox's receive policy or rules.\n\nMove a conversation by updating its labels. Add `spam` to file it as spam, add `archive` to clean it out of the inbox, and add `inbox`, or remove `spam`, `blocked`, or `archive`, to bring it back. An archived conversation returns to the inbox by itself when a new message arrives. Custom labels share the same list, and a conversation has at most 20 labels in total.\n","example":["inbox","urgent"]},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the thread was created."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the thread last changed."},"highlights":{"$ref":"#/components/schemas/EmailThreadHighlights","readOnly":true,"description":"Matched search fragments, keyed by the field that matched. Returned only by thread search. Omitted when listing threads.\n"}}},"EmailThreadList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailThread"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EmailLabelsUpdate":{"type":"object","additionalProperties":false,"description":"Label changes to apply. Labels in `add` are applied and labels in `remove` are taken off; other labels are left untouched. Adding a label that is already present, or removing one that is not, has no effect. System labels express state changes. On a conversation, adding `spam` files it as spam. Adding `archive` files it away without deleting it. Adding `inbox`, or removing `spam`, `blocked`, or `archive`, returns it to the inbox. Removing `unread` marks all retained received messages as read in one call. On a message, adding or removing `unread` flips read state. Adding or removing `trash` moves it to or out of the trash. The API rejects changes that contradict this model. A request cannot add more than one placement label. It cannot add `blocked`, because blocking a sender is a receive-rule decision. Removing `inbox` requires adding a destination. A conversation cannot add `trash` or `unread`; removing `unread` is the mark-all-read shortcut, and `trash` uses the `DELETE` verb. A message cannot use placement labels; move its conversation instead. A sent message cannot use `unread`. Custom labels are 1-64 characters with no commas, control characters, or leading or trailing whitespace. System label names and a small reserved set (`all`, `archived`, `deleted`, `draft`, `drafts`, `flagged`, `important`, `junk`, `muted`, `none`, `outbox`, `pinned`, `read`, `scheduled`, `snoozed`, `starred`) cannot be used as custom labels, in any casing. A conversation or message has at most 20 labels, system labels included.\n","properties":{"add":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^,\\s](?:[^,]*[^,\\s])?$"},"maxItems":20,"description":"Labels to apply.","example":["urgent"]},"remove":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[^,\\s](?:[^,]*[^,\\s])?$"},"maxItems":20,"description":"Labels to take off.","example":["pending"]}}},"EmailThreadUpdateRequest":{"type":"object","additionalProperties":false,"description":"Changes to apply to a thread. Omitted fields are left unchanged.","properties":{"labels":{"$ref":"#/components/schemas/EmailLabelsUpdate"},"contact_id":{"oneOf":[{"$ref":"#/components/schemas/ContactID"},{"type":"null"}],"description":"Contact to link this conversation to, or null to unlink the current contact."}}},"EmailThreadMessageRecipient":{"type":"object","additionalProperties":false,"description":"One recipient's terminal delivery outcome on a sent conversation message, recorded once the outcome becomes known.\n","required":["address","status"],"properties":{"address":{"type":"string","format":"email","readOnly":true,"minLength":1,"description":"Recipient address."},"status":{"type":"string","enum":["delivered","failed"],"minLength":1,"readOnly":true,"description":"Terminal outcome: `delivered`, or `failed` (bounce or provider rejection)."}}},"EmailThreadMessageAttachment":{"type":"object","additionalProperties":false,"description":"Attachment metadata on a conversation message. Both the metadata and the attachment bytes stay available for the mailbox's retention tier.\n","required":["id","filename","content_type","size"],"properties":{"id":{"type":"string","readOnly":true,"minLength":1,"description":"Attachment ID, used to download the attachment bytes.","example":"rea_01krdgeqcxet5s7t44vh8rt9mg"},"filename":{"type":["string","null"],"readOnly":true,"description":"Original filename, or null when the attachment had none.","example":"invoice.pdf"},"content_type":{"type":["string","null"],"readOnly":true,"description":"MIME content type, or null when it could not be determined.","example":"application/pdf"},"size":{"type":"integer","readOnly":true,"minimum":0,"description":"Attachment size in bytes."}}},"EmailThreadMessageSource":{"type":"object","additionalProperties":false,"description":"Link to the message's entry in the received-message or sent-message log, which has delivery analytics such as per-recipient events. Log entries expire 30 days after the message occurred.\n","required":["resource","available_until"],"properties":{"resource":{"type":"string","readOnly":true,"minLength":1,"description":"API path of the log entry for this message.","example":"/v1/email/inbound-messages/rem_01krdgeqcxet5s7t44vh8rt9mg"},"available_until":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the log entry (and the message's body and raw MIME) expires."}}},"EmailThreadMessage":{"type":"object","additionalProperties":false,"description":"A message in a mailbox conversation, either direction. Message metadata, extracted text, and attachment bytes stay readable for the mailbox's retention tier. The body and raw MIME are available through the body and raw endpoints for 30 days after the message occurred.\n","required":["id","direction","channel","thread_id","from","to","cc","delivered_to","subject","preview","labels","status","authentication","spf_pass","dkim_pass","dmarc_pass","attachment_count","attachment_manifest","reference_ids","contact_id","recipients","purge_at","source","occurred_at"],"properties":{"id":{"type":"string","readOnly":true,"minLength":1,"pattern":"^(rem|em)_[0-9a-hjkmnp-tv-z]{26}$","description":"Message ID. Received messages have a `rem_` ID, sent messages an `em_` ID: the same IDs used by the received-message and sent-message logs.\n","example":"rem_01krdgeqcxet5s7t44vh8rt9mg"},"direction":{"type":"string","enum":["inbound","outbound"],"minLength":1,"readOnly":true,"description":"Which way the message went. `inbound` means you received it, `outbound` means you sent it."},"channel":{"type":"string","readOnly":true,"minLength":1,"description":"Channel this message lives on. Always `email`.","example":"email"},"thread_id":{"readOnly":true,"$ref":"#/components/schemas/ThreadID","description":"Conversation this message belongs to."},"from":{"type":"string","format":"email","readOnly":true,"minLength":1,"description":"Sender address."},"to":{"type":"array","readOnly":true,"items":{"type":"string","format":"email"},"description":"Recipient addresses on the To line."},"cc":{"type":"array","readOnly":true,"items":{"type":"string","format":"email"},"description":"Recipient addresses on the Cc line. Empty when the message had none."},"delivered_to":{"type":["string","null"],"format":"email","readOnly":true,"description":"Address the message was actually delivered to, when it differs from the mailbox address (for example mail routed in from another address). Null for sent messages and for mail addressed directly to the mailbox.\n"},"subject":{"type":["string","null"],"readOnly":true,"description":"Message subject. Null when the message had no subject.","example":"Re: Your order"},"preview":{"type":["string","null"],"readOnly":true,"description":"Short plain-text preview of the message body."},"extracted_text":{"type":["string","null"],"readOnly":true,"description":"Plain-text content of the message with quoted history stripped. Readable for the mailbox's full retention tier, in both directions. Always present when fetching a single message. On list endpoints it is included only when the request sets `include=extracted_text`. Null when no text could be extracted.\n"},"labels":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"maxItems":20,"description":"Labels on this message. A received message always has exactly one placement label:\n\n- `inbox`: Accepted mail.\n- `archive`: The message's conversation was filed away.\n- `spam`: The message is filed in Spam.\n- `blocked`: The message was rejected by the mailbox's receive policy or rules.\n\nA received message also has `unread` until it is read. `trash` marks a message in the trash, in either direction. Custom labels share the same list, and a message has at most 20 labels in total.\n","example":["inbox","unread"]},"status":{"type":["string","null"],"readOnly":true,"description":"Aggregate delivery status of a sent message:\n\n- `accepted`: Accepted for sending.\n- `sent`: Handed off to the provider.\n- `delivered`: All attempted recipients delivered.\n- `failed`: Terminal failure.\n\nNull for received messages.\n"},"recipients":{"type":["array","null"],"readOnly":true,"items":{"$ref":"#/components/schemas/EmailThreadMessageRecipient"},"description":"Terminal per-recipient delivery outcomes of a sent message, filled in as each one becomes known and kept for the mailbox's full retention tier. Null for received messages and before any recipient reaches a terminal state. Per-recipient event detail lives on the sent-message log (`source`) for 30 days.\n"},"authentication":{"type":["string","null"],"enum":["pass","fail","unknown",null],"readOnly":true,"description":"DMARC result for the domain in the received message's `From` header.\n\n- `pass`: SPF or DKIM passed and aligned with that domain.\n- `fail`: DMARC was evaluated and did not pass.\n- `unknown`: no trustworthy verdict is available.\n\nThis follows `dmarc_pass` and does not verify a particular person. The receiving provider currently supplies no DMARC result, so received messages report `unknown`.\n\nNull for sent messages. This field is readable for the mailbox's full\nretention tier, so the verdict is still available after the 30-day\nreceived-message log has expired.\n"},"spf_pass":{"type":["boolean","null"],"readOnly":true,"description":"Whether the receiving provider reports that SPF authorized the envelope sender for the sending server. A soft failure is `false`. Missing, neutral and inconclusive results are `null`. Sent messages have `null` results. Kept for the mailbox retention tier."},"dkim_pass":{"type":["boolean","null"],"readOnly":true,"description":"Whether the receiving provider verified a DKIM signature. A passing signature makes this `true` even when another signature fails. Missing signatures and inconclusive verification results are `null`. Sent messages have `null` results. Kept for the mailbox retention tier."},"dmarc_pass":{"type":["boolean","null"],"readOnly":true,"description":"Whether SPF or DKIM passed and aligned with the domain in the message's `From` header. The receiving provider currently supplies no DMARC result, so this is `null`. Sent messages have `null` results. Kept for the mailbox retention tier."},"purge_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"Scheduled permanent-deletion time. This is the end of the mailbox's retention tier, moved to no more than 30 days in the future while the message is in the trash. Restore a trashed message before then with `PATCH {\"labels\": {\"remove\": [\"trash\"]}}`.\n"},"attachment_count":{"type":"integer","readOnly":true,"minimum":0,"description":"Number of attachments on the message."},"attachment_manifest":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/EmailThreadMessageAttachment"},"description":"Attachment metadata (filename, content type, size). Both the metadata and the attachment bytes stay available for the mailbox's retention tier.\n"},"reference_ids":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"RFC 5322 References header entries used to thread the conversation."},"contact_id":{"oneOf":[{"$ref":"#/components/schemas/ContactID"},{"type":"null"}],"description":"Contact linked to this message, or null when none is linked."},"source":{"readOnly":true,"$ref":"#/components/schemas/EmailThreadMessageSource"},"occurred_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the message was received or accepted for sending."}}},"EmailThreadMessageList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailThreadMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EmailThreadMessageUpdateRequest":{"type":"object","additionalProperties":false,"description":"Changes to apply to a conversation message. Omitted fields are left unchanged.","properties":{"labels":{"$ref":"#/components/schemas/EmailLabelsUpdate"},"contact_id":{"oneOf":[{"$ref":"#/components/schemas/ContactID"},{"type":"null"}],"description":"Contact to link this message to, or null to unlink the current contact."}}},"EmailThreadMessageBody":{"type":"object","additionalProperties":false,"description":"The original rendered body of a conversation message. Available for 30 days after the message occurred. After that, the endpoint returns `410 Gone`, but the message's extracted text stays readable on the message itself.\n","required":["html","text"],"properties":{"html":{"type":["string","null"],"description":"The HTML body of the message, or null when the message had no HTML part."},"text":{"type":["string","null"],"description":"The plain-text body of the message, or null when the message had no text part."}}},"EmailThreadMessageAttachmentList":{"type":"object","additionalProperties":false,"description":"The attachments on a conversation message.","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailThreadMessageAttachment"}}}},"EmailThreadMessageReplyRequest":{"type":"object","additionalProperties":false,"description":"A reply to a conversation message. Recipients are derived from the message being replied to: its Reply-To address when present, otherwise its From address. Set `reply_all` to also include the original To and Cc recipients (minus the mailbox's own address). The subject and threading headers are set automatically. At least one of `html` or `text` must be provided.\n","properties":{"html":{"type":"string","maxLength":524288,"description":"HTML body of the reply. At least one of html or text must be provided."},"text":{"type":"string","maxLength":524288,"description":"Plain-text body of the reply. At least one of html or text must be provided."},"reply_all":{"type":"boolean","default":false,"description":"Also send the reply to the original To and Cc recipients, minus the mailbox's own address."},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"maxItems":20,"description":"Structured `{name, value}` labels for filtering and analytics on the sent-message log. Cap: 20 tags per send.\n"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary JSON object stored on the send and echoed in webhook payloads. Cap: 2 KB serialized.\n"},"category":{"$ref":"#/components/schemas/EmailMessageCategory","default":"transactional"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/EmailAttachment"},"maxItems":20,"description":"File attachments to include with the reply. The send is rejected when the estimated generated message size exceeds 20 MB (bodies plus all attachments after base64 encoding). Keep total raw attachment content at or below 15 MB for reliable headroom. Attachment metadata stays on the message's `attachment_manifest`, and the bytes are downloadable for the mailbox's retention tier.\n"}},"example":{"text":"Thanks, confirming we received your request."}},"EmailThreadMessageForwardRequest":{"type":"object","additionalProperties":false,"description":"Forwards a conversation message to new recipients. The original body and attachments are sent as they were received or sent, and the subject gains a `Fwd:` prefix. Forwarding requires the message's original rendered source, which is available for 30 days after the message occurred.\n","required":["to"],"properties":{"to":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"string","format":"email"},"description":"Recipient addresses to forward the message to."},"cc":{"type":"array","maxItems":50,"items":{"type":"string","format":"email"},"description":"Cc recipient addresses."},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"maxItems":20,"description":"Structured `{name, value}` labels for filtering and analytics on the sent-message log. Cap: 20 tags per send.\n"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary JSON object stored on the send and echoed in webhook payloads. Cap: 2 KB serialized.\n"},"category":{"$ref":"#/components/schemas/EmailMessageCategory","default":"transactional"}},"example":{"to":["delivered@messagebird.dev"]}},"EmailMailboxComposeRequest":{"type":"object","additionalProperties":false,"description":"A new message sent from a mailbox, starting a new conversation. Mirrors the plain send request without `from`, because the mailbox is who the message comes from, and without `scheduled_at`, because a mailbox sends immediately. We set the RFC 5322 Message-ID so replies thread back into this conversation. At least one of `html` or `text` must be provided.\n","required":["to","subject"],"properties":{"to":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddressInput"},"minItems":1,"maxItems":50,"description":"Primary recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane <jane@acme.com>`), or an object with an optional display name."},"cc":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddressInput"},"maxItems":50,"description":"CC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane <jane@acme.com>`), or an object with an optional display name."},"bcc":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddressInput"},"maxItems":50,"description":"BCC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane <jane@acme.com>`), or an object with an optional display name."},"subject":{"type":"string","minLength":1,"maxLength":998,"description":"Message subject line."},"html":{"type":"string","maxLength":524288,"description":"HTML body. At least one of html or text must be provided."},"text":{"type":"string","maxLength":524288,"description":"Plain-text body. At least one of html or text must be provided."},"reply_to":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddressInput"},"minItems":1,"maxItems":25,"description":"Reply-To addresses. When omitted, the mailbox's `default_reply_to` applies (replies then come back to the mailbox itself).\n"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/EmailAttachment"},"maxItems":20,"description":"File attachments. The send is rejected when the estimated generated message size exceeds 20 MB (bodies plus all attachments after base64 encoding). Keep total raw attachment content at or below 15 MB for reliable headroom. Attachment metadata stays on the message's `attachment_manifest`, and the bytes are downloadable for the mailbox's retention tier.\n"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"maxItems":20,"description":"Structured `{name, value}` labels for filtering and analytics on the sent-message log. Cap: 20 tags per send.\n"},"metadata":{"type":"object","additionalProperties":true,"description":"Arbitrary JSON object stored on the send and echoed in webhook payloads. Cap: 2 KB serialized.\n"},"category":{"$ref":"#/components/schemas/EmailMessageCategory","default":"transactional"}},"example":{"to":["delivered@messagebird.dev"],"subject":"Your quote","text":"Hi, here is the quote you asked for."}},"EmailMailboxLabel":{"type":"object","additionalProperties":false,"description":"One label available in a mailbox.","required":["name","type"],"properties":{"name":{"type":"string","readOnly":true,"minLength":1,"maxLength":64,"description":"The label name, as it appears on conversations and messages.","example":"inbox"},"type":{"type":"string","readOnly":true,"minLength":1,"enum":["system","custom"],"description":"`system` labels are the built-in placements a message can be in:\n\n- Inbox.\n- Archive.\n- Spam.\n- Blocked.\n- Sent.\n- Trash.\n- Unread.\n\n`custom` labels are the workspace's own tags."}}},"EmailMailboxLabelList":{"type":"object","additionalProperties":false,"description":"The labels available in a mailbox.","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailMailboxLabel"}}}},"EmailSmtpConfig":{"allOf":[{"type":"object","required":["api_key_id","category","tags","track_opens","track_clicks"],"properties":{"api_key_id":{"readOnly":true,"$ref":"#/components/schemas/APIKeyID","description":"ID of the API key this config applies to, the same key your SMTP client authenticates with. One config exists per key.\n"},"ip_pool_id":{"type":["string","null"],"pattern":"^ipp_([0-9a-hjkmnp-tv-z]{26}|shared)$","description":"ID of the IP pool that SMTP sends with this key use, or `ipp_shared` for the shared pool. `null` when this key uses your organization's default pool.\n"},"category":{"type":"string","minLength":1,"enum":["marketing","transactional"],"description":"Content classification applied to messages submitted over SMTP with\nthis key. Controls suppression policy:\n\n- `marketing`: Blocks on all suppression reasons.\n- `transactional`: Allows delivery through complaint and unsubscribe suppressions.\n"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"maxItems":20,"description":"Structured `{name, value}` labels applied to every message submitted over SMTP with this key, the same tags used by the email sending API. Use tags to filter and break down your email statistics.\n"},"track_opens":{"type":"boolean","description":"Whether open events are tracked for messages submitted over SMTP with this key."},"track_clicks":{"type":"boolean","description":"Whether click events are tracked for messages submitted over SMTP with this key."}}},{"$ref":"#/components/schemas/Timestamps"}]},"EmailSmtpConfigList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of stored SMTP configs, newest first by default.","items":{"$ref":"#/components/schemas/EmailSmtpConfig"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"EmailSmtpConfigUpdate":{"type":"object","additionalProperties":false,"description":"Desired changes to the SMTP config for the key. A field you omit is left unchanged. If no config exists yet for this key, omitted fields take their documented defaults instead.\n","properties":{"ip_pool_id":{"type":["string","null"],"pattern":"^ipp_([0-9a-hjkmnp-tv-z]{26}|shared)$","description":"ID of the IP pool to send from (`ipp_` prefix), or `ipp_shared` to route through the shared pool explicitly. Send `null` to reset to your organization's default pool, or omit to leave unchanged. An unknown pool, or a pool with no dedicated IPs available to send from, is rejected with a `422`.\n"},"category":{"type":"string","enum":["marketing","transactional"],"description":"Content classification, independent of which endpoint messages are\nsubmitted through. Controls suppression policy:\n\n- `marketing`: Blocks on all suppression reasons.\n- `transactional`: Allows delivery through complaint and unsubscribe suppressions.\n\nOmit to leave unchanged.\n"},"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"},"maxItems":20,"description":"Structured `{name, value}` labels applied to every message submitted over SMTP with this key. Send an empty array to clear all tags, or omit to leave unchanged.\n"},"track_opens":{"type":"boolean","description":"Whether to track open events for messages submitted over SMTP with this key. Omit to leave unchanged."},"track_clicks":{"type":"boolean","description":"Whether to track click events for messages submitted over SMTP with this key. Omit to leave unchanged."}}},"EmailSmtpConnection":{"type":"object","additionalProperties":false,"required":["api_key_id","remote_ip","tls_mode","messages_submitted","last_activity_at"],"properties":{"api_key_id":{"$ref":"#/components/schemas/APIKeyID"},"remote_ip":{"type":"string","minLength":1,"description":"The client IP address the connection originates from. This is the real client address and excludes intermediate hops.\n"},"tls_mode":{"type":"string","minLength":1,"enum":["implicit_tls","starttls","unknown"],"description":"How the client established TLS for this connection:\n\n- `implicit_tls`: TLS from connect, port 465.\n- `starttls`: Upgraded in-band.\n- `unknown`: The connection's TLS posture could not be determined.\n"},"messages_submitted":{"type":"integer","format":"int64","minimum":0,"description":"Number of messages submitted on this connection so far."},"last_activity_at":{"type":"string","minLength":1,"format":"date-time","description":"When the connection was last active (set at authentication and updated on each message submission)."}}},"EmailSmtpConnectionList":{"type":"object","additionalProperties":false,"required":["data","in_use"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailSmtpConnection"},"description":"The workspace's currently-connected SMTP submission sessions."},"in_use":{"type":"integer","format":"int64","minimum":0,"description":"The number of concurrent SMTP submission connections currently open across your organization, counted against the connection limit.\n"},"limit":{"type":"integer","format":"int64","minimum":0,"description":"The maximum number of concurrent SMTP submission connections allowed across your organization.\n"}}},"WebhookSortField":{"type":"string","enum":["created_at","url"],"default":"created_at","description":"Field to sort webhook endpoints by: `created_at` (the default; newest first with the default `order`) or `url`.\n"},"WebhookEndpointID":{"type":"string","minLength":1,"pattern":"^whk_[0-9a-hjkmnp-tv-z]{26}$","example":"whk_01krdgeqcxet5s7t44vh8rt9mg"},"WebhookEventType":{"type":"string","minLength":1,"description":"Webhook event type. This is an open enum, so accept unrecognized values in deliveries. Subscribing to a type outside the event catalog returns a `422`.\n","x-extensible-enum":["domain.failed","domain.verified","email.accepted","email.bounced","email.canceled","email.clicked","email.complained","email.deferred","email.delivered","email.list_unsubscribed","email.opened","email.out_of_band_bounce","email.processed","email.received","email.rejected","email.scheduled","email.unsubscribed","email_mailbox.message_delivered","email_mailbox.message_failed","email_mailbox.message_received","email_mailbox.message_sent","email_mailbox.suspended","email_mailbox.thread_created","email_suppression.created","preference.deleted","preference.granted","preference.revoked","sms.accepted","sms.delivered","sms.expired","sms.failed","sms.received","sms.rejected","sms.sent","sms.undelivered","sms_suppression.created","verify.attempt.delivered","verify.attempt.sent","verify.attempt.undelivered","verify.verification.created","verify.verification.failed","verify.verification.verified","voice_call.answered","voice_call.ended","voice_call.initiated","whatsapp.accepted","whatsapp.delivered","whatsapp.failed","whatsapp.reacted","whatsapp.read","whatsapp.received","whatsapp.rejected","whatsapp.sent","whatsapp_suppression.created"]},"WebhookEndpointRealtime":{"type":"object","additionalProperties":false,"required":["realtime_app_id"],"description":"Realtime app scope for the endpoint. Required when subscribing to a `realtime.*` event and valid only when at least one such event is included. Invalid combinations return a `422`. The app scope cannot change after the endpoint is created.","properties":{"realtime_app_id":{"description":"ID of the Realtime app this endpoint is scoped to: the `rap_`-prefixed value from the app's `id` field (not its numeric `app_id`).\n","allOf":[{"$ref":"#/components/schemas/RealtimeAppID"}]}}},"WebhookEndpoint":{"allOf":[{"type":"object","required":["id","url","events","status","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WebhookEndpointID","description":"Unique identifier for the endpoint (`whk_` prefix). Accepted as `webhook_id` by every `/v1/webhooks/{webhook_id}` operation.\n"},"url":{"type":"string","format":"uri","minLength":1,"description":"HTTPS URL where the API delivers events for this endpoint.","example":"https://example.com/webhook"},"description":{"type":"string","minLength":1,"description":"Human-readable label for the endpoint.","example":"Production webhook endpoint"},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventType"},"description":"Event types this endpoint is subscribed to; only matching events are delivered. Change the set with [Update a webhook endpoint](/docs/api/reference/update-webhook).\n","example":["email.delivered","email.bounced"]},"status":{"type":"string","readOnly":true,"minLength":1,"description":"Delivery state of the endpoint.\n\n- `active`: The initial state; events are being delivered normally.\n- `degraded`: Recent deliveries are failing. We keep delivering and retrying,\n  and the endpoint returns to `active` automatically once deliveries succeed\n  again.\n- `paused`: All delivery is stopped, either because an update set `status` to\n  `paused` or automatically after sustained delivery failures. A paused endpoint\n  never resumes on its own: re-enable it with\n  [Update a webhook endpoint](/docs/api/reference/update-webhook), then recover\n  the missed events with\n  [Replay missed events](/docs/api/reference/create-webhook-replay).\n","enum":["active","degraded","paused"]}}},{"$ref":"#/components/schemas/Timestamps"}]},"WebhookEndpointList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"WebhookEndpointCreate":{"type":"object","additionalProperties":false,"required":["url","events"],"properties":{"url":{"type":"string","maxLength":2048,"format":"uri","minLength":1,"description":"HTTPS URL to deliver events to, at most 2048 characters. The host must be publicly reachable: URLs on private, loopback, or link-local addresses are rejected with a `422`.\n","example":"https://example.com/webhook"},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventType"},"minItems":1,"description":"Event types to subscribe to; the endpoint receives only matching events. Types outside the event catalog return a `422`, and an endpoint holds at most 100 entries.","example":["email.delivered","email.bounced"]},"description":{"type":"string","maxLength":256,"description":"Human-readable label for this endpoint, up to 256 characters.","example":"Production webhook endpoint"}},"example":{"url":"https://example.com/webhooks/bird","events":["email.delivered","email.bounced"],"description":"Production delivery + bounce notifications"}},"WebhookEndpointCreated":{"allOf":[{"$ref":"#/components/schemas/WebhookEndpoint"},{"type":"object","required":["secret"],"properties":{"secret":{"type":"string","minLength":1,"x-sensitive":true,"description":"Signing secret for this endpoint (`whsec_` prefix), used to verify every delivery signature. Present in this response only: store it immediately, it cannot be retrieved again. If you lose it, mint a new one with [Rotate webhook signing secret](/docs/api/reference/rotate-webhook-secret).\n","example":"whsec_base64encodedvalue"}}}]},"WebhookEndpointUpdate":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string","maxLength":2048,"format":"uri","description":"Replacement delivery URL. Same rules as at creation: HTTPS, at most 2048 characters, and the host must be publicly reachable (private, loopback, and link-local addresses return a `422`). Omit to keep the current URL.\n","example":"https://example.com/webhook"},"description":{"type":"string","maxLength":256,"description":"Human-readable label for this endpoint, up to 256 characters.","example":"Updated webhook endpoint"},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventType"},"minItems":1,"description":"Replaces all event subscriptions with this list. Omit to keep the current set. Types outside the event catalog return a `422`.\n","example":["email.delivered","email.bounced","email.complained"]},"status":{"type":"string","enum":["active","paused"],"description":"`paused` stops all deliveries; `active` re-enables a paused endpoint. Omit to leave the status unchanged. Events that fire while paused are not delivered; after re-enabling, recover them with [Replay missed events](/docs/api/reference/create-webhook-replay). A `degraded` endpoint cannot be reset through this field: it returns to `active` automatically once deliveries succeed again.\n"}}},"WebhookRotateSecretResponse":{"type":"object","additionalProperties":false,"required":["secret"],"properties":{"secret":{"type":"string","minLength":1,"x-sensitive":true,"description":"The new signing secret (`whsec_` prefix). Shown only in this response: store it immediately, it cannot be retrieved again. Deliveries are signed with both this and the previous secret for 24 hours after rotation, then the previous secret stops signing.\n","example":"whsec_newbase64encodedvalue"}}},"WebhookTestRequest":{"type":"object","additionalProperties":false,"properties":{"event_type":{"type":"string","description":"Event type to simulate. Any type from the event catalog is accepted, whether or not the endpoint subscribes to it; an unknown type returns a `422`. When omitted, the endpoint's first subscribed event type is used.\n","example":"email.delivered"}},"example":{"event_type":"email.delivered"}},"EventAMBPaymentUpdated":{"type":"object","additionalProperties":false,"description":"An Apple Messages for Business payment changed durable state. Consumers deduplicate by payment id and version and ignore older versions.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"amb.payment.updated","description":"Event type."},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When Bird committed this payment version."},"data":{"$ref":"#/components/schemas/AMBPayment"}}},"EventDomainFailedData":{"type":"object","additionalProperties":false,"description":"Payload of the domain.failed event.","required":["domain_id","domain","workspace_id"],"properties":{"domain_id":{"$ref":"#/components/schemas/DomainID","description":"The sending domain resource whose verification failed.","example":"dom_01krdgeqcxet5s7t44vh8rt9mg"},"domain":{"type":"string","minLength":1,"description":"The sending domain hostname.","example":"mail.example.com"},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"The workspace the domain is assigned to.","example":"ws_01krdgeqcxet5s7t44vh8rt9mg"},"failure_reason":{"type":["string","null"],"description":"Why verification failed, when a specific reason is available (for example, the DKIM record was not found at the expected selector).","example":"DKIM record not found at the expected selector."}}},"EventDomainFailed":{"type":"object","additionalProperties":false,"description":"A sending domain failed DNS verification.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["domain.failed"],"description":"Event type.","example":"domain.failed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventDomainFailedData"}}},"EventDomainVerifiedData":{"type":"object","additionalProperties":false,"description":"Payload of the domain.verified event.","required":["domain_id","domain","workspace_id"],"properties":{"domain_id":{"$ref":"#/components/schemas/DomainID","description":"The sending domain resource that verified.","example":"dom_01krdgeqcxet5s7t44vh8rt9mg"},"domain":{"type":"string","minLength":1,"description":"The sending domain hostname.","example":"mail.example.com"},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"The workspace the domain is assigned to.","example":"ws_01krdgeqcxet5s7t44vh8rt9mg"}}},"EventDomainVerified":{"type":"object","additionalProperties":false,"description":"A sending domain completed DNS verification successfully.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["domain.verified"],"description":"Event type.","example":"domain.verified"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventDomainVerifiedData"}}},"EventEmailBase":{"type":"object","description":"Identity fields shared by every email lifecycle event payload.","required":["email_id","recipient_id","workspace_id","recipient","recipient_role","tags","metadata","broadcast_id"],"properties":{"email_id":{"$ref":"#/components/schemas/EmailID","description":"ID of the email send."},"recipient_id":{"$ref":"#/components/schemas/RecipientID","description":"ID of the recipient."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace that owns this event."},"recipient":{"type":"string","minLength":1,"format":"email","description":"Recipient address as it appeared on the envelope.","example":"alice@bird.com"},"recipient_role":{"$ref":"#/components/schemas/RecipientRole","description":"Envelope position of the recipient."},"tags":{"type":["array","null"],"items":{"$ref":"#/components/schemas/Tag"},"description":"Tags provided on the send request, echoed on every event for the send so you can route and correlate without an extra lookup. Null when the send carried no tags.\n"},"metadata":{"type":["object","null"],"additionalProperties":true,"description":"The metadata object provided on the send request, echoed on every event for the send so you can correlate events with your own records. Null when the send carried no metadata.\n","example":{"order_id":"ord_123"}},"broadcast_id":{"oneOf":[{"$ref":"#/components/schemas/EmailBroadcastID"},{"type":"null"}],"description":"The broadcast this send went out as part of, echoed on every per-recipient event for the send so you can attribute engagement to the broadcast without an extra lookup. Null when the send was not part of a broadcast. On `email.unsubscribed` and `email.list_unsubscribed`, null can also mean the recipient used an unsubscribe link that names no broadcast, so on those two events null does not rule a broadcast out.\n","example":"eb_01krdgeqcxet5s7t44vh8rt9mg"}}},"EventEmailAcceptedData":{"type":"object","description":"Payload of the email.accepted event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"}]},"EventEmailAccepted":{"type":"object","additionalProperties":false,"description":"The API accepted the email send and is preparing it for delivery. Fires once per requested recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.accepted"],"description":"Event type.","example":"email.accepted"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the API accepted the send.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailAcceptedData"}}},"EmailBounceType":{"type":"string","minLength":1,"enum":["hard","soft","undetermined","admin","block"],"description":"Bounce classification.\n\n- `hard`: A permanent failure, such as an invalid address or a domain that does not exist.\n- `soft`: A transient failure, such as a full mailbox or a server that is temporarily unavailable.\n- `block`: The receiving mail server refused the sending IP on reputation grounds.\n- `admin`: An administrative refusal, such as relaying denied or a blocklisted domain.\n- `undetermined`: The receiving server's response was ambiguous.\n","example":"hard"},"EventEmailBouncedData":{"type":"object","description":"Payload of the email.bounced event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"},{"type":"object","required":["bounce_type","bounce_class","bounce_code","bounce_description","sending_ip"],"properties":{"bounce_type":{"$ref":"#/components/schemas/EmailBounceType"},"bounce_class":{"type":["integer","null"],"minimum":1,"maximum":255,"description":"Numeric bounce classification for fine-grained deliverability triage, or null when the receiving server's response could not be classified. Lets you distinguish, for example, a DNS failure from a spam block when both would be `bounce_type: soft` or `bounce_type: block`.\n","example":10},"bounce_code":{"type":["string","null"],"description":"SMTP reply code returned by the receiving mail server, or null when none was provided.","example":"550"},"bounce_description":{"type":["string","null"],"description":"Human-readable reason the receiving mail server gave for the bounce, or null when none was provided.","example":"5.1.1 Unknown user"},"sending_ip":{"type":["string","null"],"description":"The IP address used to send this message, or null when it is not known.","example":"192.0.2.10"}}}]},"EventEmailBounced":{"type":"object","additionalProperties":false,"description":"An outbound email permanently failed at the recipient's mail server. Fires once per recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.bounced"],"description":"Event type.","example":"email.bounced"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the bounce was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailBouncedData"}}},"EventEmailMessageBase":{"type":"object","description":"Identity fields shared by the message-level email lifecycle events (scheduled, canceled), which are not tied to a single recipient.","required":["email_id","workspace_id","tags","metadata"],"properties":{"email_id":{"$ref":"#/components/schemas/EmailID","description":"ID of the email send."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace that owns this event."},"tags":{"type":["array","null"],"items":{"$ref":"#/components/schemas/Tag"},"description":"Tags provided on the send request, echoed on the event so you can route and correlate without an extra lookup. Null when the send carried no tags.\n"},"metadata":{"type":["object","null"],"additionalProperties":true,"description":"The metadata object provided on the send request, echoed on the event so you can correlate events with your own records. Null when the send carried no metadata.\n","example":{"order_id":"ord_123"}}}},"EventEmailCanceledData":{"type":"object","description":"Payload of the email.canceled event.","allOf":[{"$ref":"#/components/schemas/EventEmailMessageBase"}]},"EventEmailCanceled":{"type":"object","additionalProperties":false,"description":"A scheduled send was canceled before it fired. Fires once for each message regardless of its recipient count.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.canceled"],"description":"Event type.","example":"email.canceled"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the scheduled send was canceled.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailCanceledData"}}},"EventEmailClickedData":{"type":"object","description":"Payload of the email.clicked event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"},{"type":"object","required":["url","ip_address","user_agent"],"properties":{"url":{"type":"string","minLength":1,"description":"The URL the recipient clicked.","example":"https://bird.com/welcome"},"ip_address":{"type":["string","null"],"description":"IP address of the client that clicked the link, or null when it is not known.","example":"203.0.113.9"},"user_agent":{"type":["string","null"],"description":"User-agent string of the client that clicked the link, or null when it is not known.","example":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"}}}]},"EventEmailClicked":{"type":"object","additionalProperties":false,"description":"The recipient clicked a tracked link in the email. May fire more than once per recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.clicked"],"description":"Event type.","example":"email.clicked"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the click was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailClickedData"}}},"EventEmailComplainedData":{"type":"object","description":"Payload of the email.complained event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"},{"type":"object","required":["feedback_type"],"properties":{"feedback_type":{"type":["string","null"],"description":"The kind of feedback the mailbox provider reported (such as `abuse` or `fraud`), or null when the provider did not specify one.","example":"abuse"}}}]},"EventEmailComplained":{"type":"object","additionalProperties":false,"description":"The recipient marked the email as spam through their mailbox provider's feedback loop. Fires once per recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.complained"],"description":"Event type.","example":"email.complained"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the complaint was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailComplainedData"}}},"EventEmailDeferredData":{"type":"object","description":"Payload of the email.deferred event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"},{"type":"object","required":["bounce_type","bounce_class","defer_reason","sending_ip"],"properties":{"bounce_type":{"$ref":"#/components/schemas/EmailBounceType"},"bounce_class":{"type":["integer","null"],"minimum":1,"maximum":255,"description":"Numeric bounce classification for fine-grained deliverability triage, or null when the receiving server's response could not be classified. Distinguishes, for example, a greylisting deferral from a full mailbox.\n","example":21},"defer_reason":{"type":["string","null"],"description":"Human-readable reason the receiving mail server gave for the deferral, or null when none was provided.","example":"4.2.1 Mailbox temporarily unavailable, will retry"},"sending_ip":{"type":["string","null"],"description":"The IP address used to send this message, or null when it is not known.","example":"192.0.2.10"}}}]},"EventEmailDeferred":{"type":"object","additionalProperties":false,"description":"The recipient's mail server temporarily refused the email. Delivery remains pending and is retried. May fire more than once per recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.deferred"],"description":"Event type.","example":"email.deferred"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the deferral was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailDeferredData"}}},"EventEmailDeliveredData":{"type":"object","description":"Payload of the email.delivered event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"}]},"EventEmailDelivered":{"type":"object","additionalProperties":false,"description":"An outbound email reached the recipient's mail server and was accepted.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.delivered"],"description":"Event type.","example":"email.delivered"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the recipient's mail server accepted the message.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailDeliveredData"}}},"EventEmailListUnsubscribedData":{"type":"object","description":"Payload of the email.list_unsubscribed event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"}]},"EventEmailListUnsubscribed":{"type":"object","additionalProperties":false,"description":"Recipient unsubscribed via the RFC 8058 one-click List-Unsubscribe mechanism. Fires once per recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.list_unsubscribed"],"description":"Event type.","example":"email.list_unsubscribed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the unsubscribe was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailListUnsubscribedData"}}},"EventEmailOpenedData":{"type":"object","description":"Payload of the email.opened event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"},{"type":"object","required":["ip_address","user_agent"],"properties":{"ip_address":{"type":["string","null"],"description":"IP address of the client that opened the email, or null when it is not known.","example":"203.0.113.9"},"user_agent":{"type":["string","null"],"description":"User-agent string of the client that opened the email, or null when it is not known.","example":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"}}}]},"EventEmailOpened":{"type":"object","additionalProperties":false,"description":"The recipient opened the email (the tracking pixel was loaded). May fire more than once per recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.opened"],"description":"Event type.","example":"email.opened"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the open was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailOpenedData"}}},"EventEmailOutOfBandBounceData":{"type":"object","description":"Payload of the email.out_of_band_bounce event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"},{"type":"object","required":["bounce_type","bounce_class","bounce_code","bounce_description","sending_ip"],"properties":{"bounce_type":{"$ref":"#/components/schemas/EmailBounceType"},"bounce_class":{"type":["integer","null"],"minimum":1,"maximum":255,"description":"Numeric bounce classification for fine-grained deliverability triage, or null when the receiving server's response could not be classified.\n","example":10},"bounce_code":{"type":["string","null"],"description":"SMTP reply code returned by the receiving mail server, or null when none was provided.","example":"550"},"bounce_description":{"type":["string","null"],"description":"Human-readable reason the receiving mail server gave for the bounce, or null when none was provided.","example":"5.1.1 Unknown user"},"sending_ip":{"type":["string","null"],"description":"The IP address used to send this message, or null when it is not known.","example":"192.0.2.10"}}}]},"EventEmailOutOfBandBounce":{"type":"object","additionalProperties":false,"description":"A bounce notification arrived after the message had already been accepted for delivery. Fires once per recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.out_of_band_bounce"],"description":"Event type.","example":"email.out_of_band_bounce"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the bounce notification was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailOutOfBandBounceData"}}},"EventEmailProcessedData":{"type":"object","description":"Payload of the email.processed event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"}]},"EventEmailProcessed":{"type":"object","additionalProperties":false,"description":"The API prepared the message for delivery to the recipient's mail server. Fires once per recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.processed"],"description":"Event type.","example":"email.processed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the message was prepared for delivery.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailProcessedData"}}},"EventEmailReceivedData":{"type":"object","additionalProperties":false,"description":"Payload of the email.received event.","required":["inbound_message_id","workspace_id","message_id","from","to","subject"],"properties":{"inbound_message_id":{"$ref":"#/components/schemas/InboundEmailMessageID","description":"ID of the received email. Fetch its parsed metadata with `GET /v1/email/inbound-messages/{id}`, and its content from that message's `/body`, `/raw`, and `/attachments` sub-resources."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace that owns this event."},"message_id":{"type":["string","null"],"description":"RFC 5322 Message-ID header from the sender, or null when the sender did not include one.","example":"<CAH+T8d5...@mail.gmail.com>"},"from":{"type":"string","minLength":1,"format":"email","description":"Address from the message's From header, with the relay's parsed sender and then the SMTP envelope sender as fallbacks when that header cannot be read. This field alone does not authenticate the sender.","example":"alice@example.com"},"to":{"type":"array","items":{"type":"string","format":"email"},"description":"Parsed recipient addresses from the message headers, not the envelope recipient used to route this delivery.","example":["support@acme.com"]},"subject":{"type":["string","null"],"description":"Subject line as received, or null when the message had no subject.","example":"Welcome to Bird"},"in_reply_to":{"type":["string","null"],"description":"`In-Reply-To` header containing the `Message-ID` this message replies to, or null when it is not a reply.","example":"<previous-message@example.com>"},"authentication":{"type":["string","null"],"enum":["pass","fail","unknown",null],"description":"DMARC result for the domain in the received message's `From` header.\n\n- `pass`: SPF or DKIM passed and aligned with that domain.\n- `fail`: DMARC was evaluated and did not pass.\n- `unknown`: no trustworthy verdict is available.\n\nThis follows `dmarc_pass` and does not verify a particular person. The receiving provider currently supplies no DMARC result, so received messages report `unknown`.\n"},"spf_pass":{"type":["boolean","null"],"description":"Whether the receiving provider reports that SPF authorized the envelope sender for the sending server. A soft failure is `false`. Missing, neutral and inconclusive results are `null`."},"dkim_pass":{"type":["boolean","null"],"description":"Whether the receiving provider verified a DKIM signature. A passing signature makes this `true` even when another signature fails. Missing signatures and inconclusive verification results are `null`."},"dmarc_pass":{"type":["boolean","null"],"description":"Whether SPF or DKIM passed and aligned with the domain in the message's `From` header. The receiving provider currently supplies no DMARC result, so this is `null`."},"spam_score":{"type":["number","null"],"description":"Content spam score when available. The receiving provider currently supplies no score, so this is `null`."}}},"EventEmailReceived":{"type":"object","additionalProperties":false,"description":"The API received and parsed an inbound email. The payload carries the message's identifiers, sender and recipients, subject, threading reference, and authentication results, which is enough to route and triage without a fetch. Fetch content separately. Get the parsed body with `GET /v1/email/inbound-messages/{id}/body`. Get the original MIME with `GET /v1/email/inbound-messages/{id}/raw`. Get attachment bytes with `GET /v1/email/inbound-messages/{id}/attachments/{attachment_id}`.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.received"],"description":"Event type.","example":"email.received"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the API received the message.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailReceivedData"}}},"EmailRejectionReason":{"type":"string","minLength":1,"enum":["recipient_suppressed","transmission_failed","generation_failure","policy_rejection","domain_unverified","quota_exceeded","recipient_not_allowed"],"x-enum-varnames":["EmailRejectionReasonRecipientSuppressed","EmailRejectionReasonTransmissionFailed","EmailRejectionReasonGenerationFailure","EmailRejectionReasonPolicyRejection","EmailRejectionReasonDomainUnverified","EmailRejectionReasonQuotaExceeded","EmailRejectionReasonRecipientNotAllowed"],"description":"Why an email was rejected before delivery.\n\n- `recipient_suppressed`: The recipient is on the workspace suppression list, so\n  delivery was never attempted.\n- `transmission_failed`: The message could not be transmitted for delivery.\n- `generation_failure`: The message could not be built for delivery (template or\n  content issue).\n- `policy_rejection`: The message was refused by sending policy.\n- `domain_unverified`: The sending domain was not verified.\n- `quota_exceeded`: The organization's send quota was reached.\n- `recipient_not_allowed`: A recipient was not permitted for this send (for shared\n  onboarding-domain sends, recipients must be verified workspace members).\n","example":"recipient_suppressed"},"EventEmailRejectedData":{"type":"object","description":"Payload of the email.rejected event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"},{"type":"object","required":["rejection_reason"],"properties":{"rejection_reason":{"$ref":"#/components/schemas/EmailRejectionReason"}}}]},"EventEmailRejected":{"type":"object","additionalProperties":false,"description":"The API rejected the email before delivery because of suppression, transmission failure, content, or policy. Fires once per recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.rejected"],"description":"Event type.","example":"email.rejected"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the rejection was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailRejectedData"}}},"EventEmailScheduledData":{"type":"object","description":"Payload of the email.scheduled event.","allOf":[{"$ref":"#/components/schemas/EventEmailMessageBase"},{"type":"object","required":["scheduled_at"],"properties":{"scheduled_at":{"type":"string","minLength":1,"format":"date-time","description":"When the message is scheduled to send.","example":"2026-05-22T09:00:00Z"}}}]},"EventEmailScheduled":{"type":"object","additionalProperties":false,"description":"The API accepted an email scheduled for a future time. Fires once per message when the schedule is created.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.scheduled"],"description":"Event type.","example":"email.scheduled"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the send was scheduled.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailScheduledData"}}},"EventEmailUnsubscribedData":{"type":"object","description":"Payload of the email.unsubscribed event.","allOf":[{"$ref":"#/components/schemas/EventEmailBase"}]},"EventEmailUnsubscribed":{"type":"object","additionalProperties":false,"description":"Recipient unsubscribed by clicking a tracked unsubscribe link in the email. Fires once per recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email.unsubscribed"],"description":"Event type.","example":"email.unsubscribed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the unsubscribe was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailUnsubscribedData"}}},"EventEmailMailboxMessageDeliveredData":{"type":"object","additionalProperties":false,"description":"Payload of the email_mailbox.message_delivered event.","required":["message_id","mailbox_id","thread_id"],"properties":{"message_id":{"$ref":"#/components/schemas/EmailID","description":"ID of the delivered message. Per-recipient `email.*` events use this value as `email_id`. Use it to deduplicate events when you subscribe to both event families."},"mailbox_id":{"$ref":"#/components/schemas/MailboxID","description":"ID of the mailbox the message was sent from."},"thread_id":{"$ref":"#/components/schemas/ThreadID","description":"ID of the thread the message belongs to."}}},"EventEmailMailboxMessageDelivered":{"type":"object","additionalProperties":false,"description":"Every recipient of a mailbox message reached a delivered state. This event fires once per message. The same send also emits one `email.delivered` event for each recipient. Choose one event family for each automation and deduplicate mailbox events by `message_id`.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email_mailbox.message_delivered"],"description":"Event type.","example":"email_mailbox.message_delivered"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the event occurred.","example":"2026-07-08T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailMailboxMessageDeliveredData"}}},"EventEmailMailboxMessageFailedData":{"type":"object","additionalProperties":false,"description":"Payload of the email_mailbox.message_failed event.","required":["message_id","mailbox_id","thread_id","reason"],"properties":{"message_id":{"$ref":"#/components/schemas/EmailID","description":"ID of the failed message. Per-recipient `email.*` events use this value as `email_id`. Use it to deduplicate events when you subscribe to both event families."},"mailbox_id":{"$ref":"#/components/schemas/MailboxID","description":"ID of the mailbox the message was sent from."},"thread_id":{"$ref":"#/components/schemas/ThreadID","description":"ID of the thread the message belongs to."},"reason":{"type":"string","minLength":1,"description":"Why the message reached a terminal delivery failure.","example":"all recipients bounced"}}},"EventEmailMailboxMessageFailed":{"type":"object","additionalProperties":false,"description":"A mailbox message reached a terminal delivery failure. This event fires once per message. The same send also emits per-recipient `email.*` events. Choose one event family for each automation and deduplicate mailbox events by `message_id`.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email_mailbox.message_failed"],"description":"Event type.","example":"email_mailbox.message_failed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the event occurred.","example":"2026-07-08T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailMailboxMessageFailedData"}}},"EventEmailMailboxMessageReceivedData":{"type":"object","additionalProperties":false,"description":"Identifiers, threading details, authentication results, and extracted text for a received mailbox message. The thread-message endpoints provide the original source during its 30-day retention window.","required":["message_id","mailbox_id","thread_id","from","to","subject","attachment_count"],"properties":{"message_id":{"$ref":"#/components/schemas/InboundEmailMessageID","description":"ID of the received message. The corresponding `email.received` event uses this value as `inbound_message_id`. Use it to deduplicate events when you subscribe to both event families."},"mailbox_id":{"$ref":"#/components/schemas/MailboxID","description":"ID of the mailbox that received the message."},"thread_id":{"$ref":"#/components/schemas/ThreadID","description":"ID of the thread the message was filed into."},"route_id":{"type":["string","null"],"description":"ID (ein_…) of the explicit inbound route that matched, or null when the message was delivered by the virtual exact-address route.","example":null},"from":{"type":"string","minLength":1,"format":"email","description":"Envelope-from address.","example":"alice@example.com"},"to":{"type":"array","items":{"type":"string","format":"email"},"description":"Recipient addresses the message was sent to.","example":["support@inbox.ai"]},"subject":{"type":["string","null"],"description":"Subject line as received, or null when the message had no subject.","example":"Re: Your quote"},"extracted_text":{"type":["string","null"],"description":"Plain-text body with quoted history removed, capped at 64 KB. See `truncated_text` to check whether the value was truncated. Null when extraction produces no text.","example":"Sounds good — can you send the invoice?"},"truncated_text":{"type":"boolean","description":"True when `extracted_text` was truncated to the 64 KB cap. Fetch the full text through the thread-member endpoint.","default":false},"attachment_count":{"type":"integer","minimum":0,"description":"Number of attachments on the message. Attachment content remains available for the mailbox's retention tier.","example":1},"authentication":{"type":["string","null"],"enum":["pass","fail","unknown",null],"description":"DMARC result for the domain in the received message's `From` header.\n\n- `pass`: SPF or DKIM passed and aligned with that domain.\n- `fail`: DMARC was evaluated and did not pass.\n- `unknown`: no trustworthy verdict is available.\n\nThis follows `dmarc_pass` and does not verify a particular person. The receiving provider currently supplies no DMARC result, so received messages report `unknown`.\n"},"spf_pass":{"type":["boolean","null"],"description":"Whether the receiving provider reports that SPF authorized the envelope sender for the sending server. A soft failure is `false`. Missing, neutral and inconclusive results are `null`."},"dkim_pass":{"type":["boolean","null"],"description":"Whether the receiving provider verified a DKIM signature. A passing signature makes this `true` even when another signature fails. Missing signatures and inconclusive verification results are `null`."},"dmarc_pass":{"type":["boolean","null"],"description":"Whether SPF or DKIM passed and aligned with the domain in the message's `From` header. The receiving provider currently supplies no DMARC result, so this is `null`."}}},"EventEmailMailboxMessageReceived":{"type":"object","additionalProperties":false,"description":"An email arrived in a mailbox. The same message also emits an `email.received` event. The two events can arrive in either order. Choose one event family for each automation and deduplicate mailbox events by `message_id`.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email_mailbox.message_received"],"description":"Event type.","example":"email_mailbox.message_received"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the event occurred.","example":"2026-07-08T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailMailboxMessageReceivedData"}}},"EventEmailMailboxMessageSentData":{"type":"object","additionalProperties":false,"description":"Payload of the email_mailbox.message_sent event.","required":["message_id","mailbox_id","thread_id"],"properties":{"message_id":{"$ref":"#/components/schemas/EmailID","description":"ID of the sent message. Per-recipient `email.*` events use this value as `email_id`. Use it to deduplicate events when you subscribe to both event families."},"mailbox_id":{"$ref":"#/components/schemas/MailboxID","description":"ID of the mailbox the message was sent from."},"thread_id":{"$ref":"#/components/schemas/ThreadID","description":"ID of the thread the message belongs to."}}},"EventEmailMailboxMessageSent":{"type":"object","additionalProperties":false,"description":"A mailbox message was handed off for delivery. This event fires once per message. The same send also emits per-recipient `email.*` events. Choose one event family for each automation and deduplicate mailbox events by `message_id`.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email_mailbox.message_sent"],"description":"Event type.","example":"email_mailbox.message_sent"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the event occurred.","example":"2026-07-08T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailMailboxMessageSentData"}}},"EventEmailMailboxSuspendedData":{"type":"object","additionalProperties":false,"description":"Payload of the email_mailbox.suspended event.","required":["mailbox_id","reason"],"properties":{"mailbox_id":{"$ref":"#/components/schemas/MailboxID","description":"ID of the suspended mailbox."},"reason":{"type":"string","minLength":1,"description":"Why the mailbox was suspended.","example":"abuse review"}}},"EventEmailMailboxSuspended":{"type":"object","additionalProperties":false,"description":"This mailbox-suspension event is reserved and is not currently emitted.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email_mailbox.suspended"],"description":"Event type.","example":"email_mailbox.suspended"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the event occurred.","example":"2026-07-08T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailMailboxSuspendedData"}}},"EventEmailMailboxThreadCreatedData":{"type":"object","additionalProperties":false,"description":"Payload of the email_mailbox.thread_created event.","required":["thread_id","mailbox_id","subject","initiated_by"],"properties":{"thread_id":{"$ref":"#/components/schemas/ThreadID","description":"ID of the thread."},"mailbox_id":{"$ref":"#/components/schemas/MailboxID","description":"ID of the mailbox the thread was created in."},"subject":{"type":["string","null"],"description":"Subject of the first message in the thread, or null when it had none.","example":"Your quote"},"initiated_by":{"type":"string","minLength":1,"enum":["inbound","outbound"],"description":"Which direction created the thread.","example":"inbound"}}},"EventEmailMailboxThreadCreated":{"type":"object","additionalProperties":false,"description":"A new thread was created in a mailbox, from either direction.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email_mailbox.thread_created"],"description":"Event type.","example":"email_mailbox.thread_created"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the event occurred.","example":"2026-07-08T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailMailboxThreadCreatedData"}}},"EventEmailSuppressionCreatedData":{"type":"object","additionalProperties":false,"description":"Payload of the email_suppression.created event.","required":["suppression_id","email","reason","workspace_id"],"properties":{"suppression_id":{"$ref":"#/components/schemas/SuppressionID","description":"The suppression entry that was created.","example":"sup_01krdgeqcxet5s7t44vh8rt9mg"},"email":{"type":"string","minLength":1,"format":"email","description":"The recipient address that was added to the suppression list.","example":"user@example.com"},"reason":{"type":"string","minLength":1,"x-extensible-enum":["hard_bounce","complaint","unsubscribe","manual"],"description":"Why the address was suppressed. New values may be added over time; treat unknown values as informational.\n","example":"hard_bounce"},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"The workspace the suppression belongs to.","example":"ws_01krdgeqcxet5s7t44vh8rt9mg"}}},"EventEmailSuppressionCreated":{"type":"object","additionalProperties":false,"description":"An email address was added to the workspace's suppression list (manually, via complaint, or via hard bounce).","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["email_suppression.created"],"description":"Event type.","example":"email_suppression.created"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEmailSuppressionCreatedData"}}},"EsimID":{"type":"string","minLength":1,"pattern":"^esm_[0-9a-hjkmnp-tv-z]{26}$","example":"esm_01krdgeqcxet5s7t44vh8rt9mg"},"EventEsimBase":{"type":"object","description":"Identity fields shared by every eSIM lifecycle event payload.","required":["esim_id","workspace_id","iccid","tags","metadata"],"properties":{"esim_id":{"$ref":"#/components/schemas/EsimID","description":"ID of the eSIM."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace."},"iccid":{"type":["string","null"],"description":"ICCID of the eSIM profile. Null while no profile is allocated, for example when provisioning failed before allocation.","example":"8944500212345678912"},"tags":{"type":["array","null"],"items":{"$ref":"#/components/schemas/Tag"},"description":"Tags on the eSIM, echoed on every event for the eSIM so you can route and correlate without an extra lookup. Null when the eSIM carries no tags.\n"},"metadata":{"type":["object","null"],"additionalProperties":true,"description":"Metadata provided on the eSIM, echoed on every event for the eSIM so you can route and correlate without an extra lookup. Null when the eSIM carries no metadata.\n"}}},"EsimDeliveryChannel":{"type":"string","minLength":1,"enum":["email","sms"],"description":"Channel the install credentials are delivered over."},"EsimDeliveryID":{"type":"string","minLength":1,"pattern":"^edv_[0-9a-hjkmnp-tv-z]{26}$","example":"edv_01krdgeqcxet5s7t44vh8rt9mg"},"EventEsimCredentialsDeliveredData":{"type":"object","description":"Payload of the esim.credentials.delivered event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"},{"type":"object","required":["delivery_id","channel","to"],"properties":{"channel":{"allOf":[{"$ref":"#/components/schemas/EsimDeliveryChannel"}],"description":"Channel the credentials went out on."},"delivery_id":{"$ref":"#/components/schemas/EsimDeliveryID","description":"The delivery this event settles, as returned when it was accepted."},"to":{"type":"string","minLength":3,"description":"Recipient address the credentials were sent to."}}}]},"EventEsimCredentialsDelivered":{"type":"object","additionalProperties":false,"description":"Install credentials were delivered to the traveler over the requested channel.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.credentials.delivered","description":"Event type.","example":"esim.credentials.delivered"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimCredentialsDeliveredData"}}},"EventEsimCredentialsDeliveryFailedData":{"type":"object","description":"Payload of the esim.credentials.delivery_failed event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"},{"type":"object","required":["delivery_id","channel","to","failure_code","retryable"],"properties":{"channel":{"allOf":[{"$ref":"#/components/schemas/EsimDeliveryChannel"}],"description":"Channel the delivery was attempted on."},"delivery_id":{"$ref":"#/components/schemas/EsimDeliveryID","description":"The delivery this event settles, as returned when it was accepted."},"to":{"type":"string","minLength":3,"description":"Recipient address the delivery was attempted to."},"failure_code":{"type":"string","minLength":1,"x-extensible-enum":["invalid_recipient","suppressed","blocked","bounced","channel_unavailable","send_failed"],"description":"Why the delivery failed, the same vocabulary the delivery record carries. Open enum: treat unrecognized values as future failure kinds."},"retryable":{"type":"boolean","description":"Whether re-sending with the same recipient can succeed. False for invalid or suppressed recipients."}}}]},"EventEsimCredentialsDeliveryFailed":{"type":"object","additionalProperties":false,"description":"Delivering install credentials to the traveler failed.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.credentials.delivery_failed","description":"Event type.","example":"esim.credentials.delivery_failed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimCredentialsDeliveryFailedData"}}},"EventEsimExpiredData":{"type":"object","description":"Payload of the esim.expired event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"}]},"EventEsimExpired":{"type":"object","additionalProperties":false,"description":"The eSIM expired: no data package has been usable for the expiry window. Not terminal: a completed top-up returns it to active, so the event can recur per eSIM.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.expired","description":"Event type.","example":"esim.expired"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimExpiredData"}}},"EsimFailureStage":{"type":"string","minLength":1,"enum":["provisioning","installation"],"description":"Which stage of the eSIM's lifecycle failed.\n\n- `provisioning`: the eSIM could not be created; its status is `failed` and nothing was charged.\n- `installation`: the eSIM exists and stays `ready`, but installing it on a device failed; `installation.state` carries the detail.\n"},"EventEsimFailedData":{"type":"object","description":"Payload of the esim.failed event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"},{"type":"object","required":["stage","reason","code","retryable"],"properties":{"stage":{"allOf":[{"$ref":"#/components/schemas/EsimFailureStage"}],"description":"Which stage failed."},"code":{"type":"string","minLength":1,"x-extensible-enum":["device_ineligible","download_limit_exhausted","carrier_error"],"description":"Machine-readable failure code. Open enum: treat unrecognized values as future failure kinds."},"retryable":{"type":"boolean","description":"Whether retrying the failed stage can succeed without changing anything."},"reason":{"type":["string","null"],"description":"Human-readable failure reason, for example an ineligible device or an exhausted download limit. Null when not known."}}}]},"EventEsimFailed":{"type":"object","additionalProperties":false,"description":"A stage of the eSIM's lifecycle failed. The payload's stage field says whether provisioning failed (the eSIM is dead, nothing was charged) or an installation attempt failed (the eSIM stays ready to install).","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.failed","description":"Event type.","example":"esim.failed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimFailedData"}}},"EventEsimInstalledData":{"type":"object","description":"Payload of the esim.installed event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"}]},"EventEsimInstalled":{"type":"object","additionalProperties":false,"description":"The eSIM profile was installed on the end user's device.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.installed","description":"Event type.","example":"esim.installed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimInstalledData"}}},"EsimLifecycleOperation":{"type":"string","minLength":1,"enum":["suspend","resume","release","remove_package"],"description":"The accepted lifecycle change that failed to apply."},"EsimPackageID":{"type":"string","minLength":1,"pattern":"^epk_[0-9a-hjkmnp-tv-z]{26}$","example":"epk_01krdgeqcxet5s7t44vh8rt9mg"},"EventEsimOperationFailedData":{"type":"object","description":"Payload of the esim.operation_failed event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"},{"type":"object","required":["operation","package_id","code","reason"],"properties":{"operation":{"allOf":[{"$ref":"#/components/schemas/EsimLifecycleOperation"}],"description":"Which accepted change failed."},"package_id":{"description":"The package a remove_package operation targeted. Null for eSIM-level operations.","oneOf":[{"$ref":"#/components/schemas/EsimPackageID"},{"type":"null"}]},"code":{"type":"string","minLength":1,"x-extensible-enum":["carrier_error","invalid_state","capability_unsupported"],"description":"Machine-readable failure code. Open enum: treat unrecognized values as future failure kinds."},"reason":{"type":["string","null"],"description":"Human-readable failure reason. Null when not known."}}}]},"EventEsimOperationFailed":{"type":"object","additionalProperties":false,"description":"An accepted lifecycle change was definitively rejected by the mobile network. The resource has returned to the status it held before the change was accepted.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.operation_failed","description":"Event type.","example":"esim.operation_failed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimOperationFailedData"}}},"EsimOrderID":{"type":"string","minLength":1,"pattern":"^eor_[0-9a-hjkmnp-tv-z]{26}$","example":"eor_01krdgeqcxet5s7t44vh8rt9mg"},"EsimOfferID":{"type":"string","minLength":1,"pattern":"^eof_[0-9a-hjkmnp-tv-z]{26}$","example":"eof_01krdgeqcxet5s7t44vh8rt9mg"},"EventEsimOrderCompletedData":{"type":"object","additionalProperties":false,"description":"Payload of the esim.order.completed event.","required":["order_id","workspace_id","offer_id","esim_id","package_id"],"properties":{"order_id":{"$ref":"#/components/schemas/EsimOrderID","description":"ID of the completed order."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace."},"offer_id":{"$ref":"#/components/schemas/EsimOfferID","description":"Offer that was purchased."},"esim_id":{"$ref":"#/components/schemas/EsimID","description":"The eSIM the purchase landed on."},"package_id":{"$ref":"#/components/schemas/EsimPackageID","description":"The data package the order produced."}}},"EventEsimOrderCompleted":{"type":"object","additionalProperties":false,"description":"An eSIM order completed. The order names the eSIM and data package it produced.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.order.completed","description":"Event type.","example":"esim.order.completed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimOrderCompletedData"}}},"EventEsimOrderFailedData":{"type":"object","additionalProperties":false,"description":"Payload of the esim.order.failed event.","required":["order_id","workspace_id","offer_id","esim_id","failure_code","failure_reason"],"properties":{"order_id":{"$ref":"#/components/schemas/EsimOrderID","description":"ID of the failed order."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace."},"offer_id":{"$ref":"#/components/schemas/EsimOfferID","description":"Offer the order was for."},"esim_id":{"description":"The eSIM the package was meant for. Null when the order was provisioning a new eSIM.","oneOf":[{"$ref":"#/components/schemas/EsimID"},{"type":"null"}]},"failure_code":{"type":["string","null"],"x-extensible-enum":["insufficient_balance","carrier_error","capacity_exhausted","offer_unavailable","internal_error","canceled","resolved_by_support","esim_released"],"description":"Machine-readable reason the order failed. Open enum: treat unrecognized values as future failure kinds. canceled means the workspace canceled the order while it was waiting for funds; resolved_by_support means Bird support closed a stuck order as failed; esim_released means the top-up target became unserviceable before delivery: its eSIM was releasing, released, or failed. In every case any charge has been credited back."},"failure_reason":{"type":["string","null"],"description":"Why the order failed, in plain terms. Null when not known."}}},"EventEsimOrderFailed":{"type":"object","additionalProperties":false,"description":"An eSIM order failed. Any charge for it has been credited back.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.order.failed","description":"Event type.","example":"esim.order.failed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimOrderFailedData"}}},"EventEsimPackageBalanceLowData":{"type":"object","description":"Payload of the esim.package.balance_low event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"},{"type":"object","required":["package_id","remaining_bytes","threshold_bytes","total_bytes"],"properties":{"package_id":{"$ref":"#/components/schemas/EsimPackageID","description":"ID of the data package."},"remaining_bytes":{"type":"integer","minimum":0,"description":"Remaining data in bytes at the time of the event."},"threshold_bytes":{"type":"integer","minimum":0,"description":"The low-balance threshold that was crossed, in bytes."},"total_bytes":{"type":"integer","minimum":0,"description":"Total data of the package in bytes."}}}]},"EventEsimPackageBalanceLow":{"type":"object","additionalProperties":false,"description":"Fires once, when a data package's remaining balance first drops below its low-balance threshold, 20% of the package's total data. A package's balance only ever decreases - a top-up is a new package with its own threshold - so the event never repeats for a package.\n","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.package.balance_low","description":"Event type.","example":"esim.package.balance_low"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimPackageBalanceLowData"}}},"EventEsimPackageDepletedData":{"type":"object","description":"Payload of the esim.package.depleted event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"},{"type":"object","required":["package_id"],"properties":{"package_id":{"$ref":"#/components/schemas/EsimPackageID","description":"ID of the data package."}}}]},"EventEsimPackageDepleted":{"type":"object","additionalProperties":false,"description":"A data package's balance is fully used. The eSIM cannot consume data from this package anymore, even if validity remains.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.package.depleted","description":"Event type.","example":"esim.package.depleted"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimPackageDepletedData"}}},"EventEsimPackageExpiredData":{"type":"object","description":"Payload of the esim.package.expired event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"},{"type":"object","required":["package_id"],"properties":{"package_id":{"$ref":"#/components/schemas/EsimPackageID","description":"ID of the data package."}}}]},"EventEsimPackageExpired":{"type":"object","additionalProperties":false,"description":"A data package's validity period ended. Any unused balance expired with it.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.package.expired","description":"Event type.","example":"esim.package.expired"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimPackageExpiredData"}}},"EventEsimPackageReadyData":{"type":"object","description":"Payload of the esim.package.ready event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"},{"type":"object","required":["package_id","offer_id"],"properties":{"package_id":{"$ref":"#/components/schemas/EsimPackageID","description":"ID of the data package."},"offer_id":{"allOf":[{"$ref":"#/components/schemas/EsimOfferID"}],"description":"ID of the offer the package was created from."}}}]},"EventEsimPackageReady":{"type":"object","additionalProperties":false,"description":"A data package was confirmed on the eSIM and is ready to use in its coverage zone.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.package.ready","description":"Event type.","example":"esim.package.ready"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimPackageReadyData"}}},"EventEsimPackageRemovedData":{"type":"object","description":"Payload of the esim.package.removed event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"},{"type":"object","required":["package_id","offer_id"],"properties":{"package_id":{"$ref":"#/components/schemas/EsimPackageID","description":"ID of the removed data package."},"offer_id":{"allOf":[{"$ref":"#/components/schemas/EsimOfferID"}],"description":"ID of the offer the package was created from."}}}]},"EventEsimPackageRemoved":{"type":"object","additionalProperties":false,"description":"A data package was removed from the eSIM and its zone slot is free again.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.package.removed","description":"Event type.","example":"esim.package.removed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimPackageRemovedData"}}},"EventEsimReadyData":{"type":"object","description":"Payload of the esim.ready event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"}]},"EventEsimReady":{"type":"object","additionalProperties":false,"description":"The initial data package finished provisioning and the eSIM is ready to install and use.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.ready","description":"Event type.","example":"esim.ready"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimReadyData"}}},"EventEsimReleasedData":{"type":"object","description":"Payload of the esim.released event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"}]},"EventEsimReleased":{"type":"object","additionalProperties":false,"description":"The eSIM was permanently released. It can never be used again.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.released","description":"Event type.","example":"esim.released"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimReleasedData"}}},"EventEsimResumedData":{"type":"object","description":"Payload of the esim.resumed event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"}]},"EventEsimResumed":{"type":"object","additionalProperties":false,"description":"The eSIM was resumed and can use data again.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.resumed","description":"Event type.","example":"esim.resumed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimResumedData"}}},"EventEsimSuspendedData":{"type":"object","description":"Payload of the esim.suspended event.","allOf":[{"$ref":"#/components/schemas/EventEsimBase"}]},"EventEsimSuspended":{"type":"object","additionalProperties":false,"description":"The eSIM was suspended and can no longer use data until resumed.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"const":"esim.suspended","description":"Event type.","example":"esim.suspended"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the event occurred.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventEsimSuspendedData"}}},"PreferenceDeletedEventType":{"type":"string","minLength":1,"enum":["preference.deleted"],"description":"Always `preference.deleted` for this event.","example":"preference.deleted"},"EventPreferenceBase":{"type":"object","description":"Identity fields shared by every preference lifecycle event: the key plus the ledger entry the write appended. `effective_at` is not repeated here: it is the envelope `timestamp`.","required":["preference_id","transition_id","channel","handle","sender_scope","topic_id","coverage","contact_id"],"properties":{"preference_id":{"$ref":"#/components/schemas/PreferenceID","description":"The preference key this write applied to.","example":"prf_01krdgeqcxet5s7t44vh8rt9mg"},"transition_id":{"$ref":"#/components/schemas/PreferenceTransitionID","description":"The ledger entry this write appended.","example":"prt_01krdgeqcxet5s7t44vh8rt9mg"},"channel":{"allOf":[{"$ref":"#/components/schemas/PreferenceChannel"}]},"handle":{"type":"string","minLength":1,"maxLength":320,"description":"Who the statement is about: an email address on the email channel, a phone number in E.164 format on SMS and WhatsApp.","example":"+15550001234"},"sender_scope":{"type":["string","null"],"description":"The sender the statement is limited to, or null when it covers the whole channel. Present-with-null on every payload of this type: it is part of the key alongside `topic_id`, and pinning its presence keeps a subscriber from ever learning `(handle, channel)` as the unique key.","example":"+15557654321"},"topic_id":{"type":["string","null"],"description":"The topic the statement is limited to, or null when it covers every topic. Reserved: always null in v1. Present-with-null for the same reason as `sender_scope`.","example":null},"coverage":{"allOf":[{"$ref":"#/components/schemas/PreferenceCoverage"}]},"contact_id":{"oneOf":[{"$ref":"#/components/schemas/ContactID"},{"type":"null"}],"description":"The contact whose handle matched when the statement was recorded. Null when no contact matched at that moment."}}},"EventPreferenceDeletedData":{"type":"object","description":"Payload of the preference.deleted event.","allOf":[{"$ref":"#/components/schemas/EventPreferenceBase"}]},"EventPreferenceDeleted":{"type":"object","additionalProperties":false,"description":"A stated preference was deleted, superseding it in the ledger without erasing its history.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/PreferenceDeletedEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the delete took effect (`effective_at`), not when it was recorded.","example":"2026-08-12T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventPreferenceDeletedData"}}},"PreferenceGrantedEventType":{"type":"string","minLength":1,"enum":["preference.granted"],"description":"Always `preference.granted` for this event.","example":"preference.granted"},"EventPreferenceGrantedData":{"type":"object","description":"Payload of the preference.granted event.","allOf":[{"$ref":"#/components/schemas/EventPreferenceBase"}]},"EventPreferenceGranted":{"type":"object","additionalProperties":false,"description":"A stated preference was granted (a person consented, or a customer wrote a grant) and became the key's live statement.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/PreferenceGrantedEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the statement took effect (`effective_at`), not when it was recorded.","example":"2026-08-12T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventPreferenceGrantedData"}}},"PreferenceRevokedEventType":{"type":"string","minLength":1,"enum":["preference.revoked"],"description":"Always `preference.revoked` for this event.","example":"preference.revoked"},"EventPreferenceRevokedData":{"type":"object","description":"Payload of the preference.revoked event.","allOf":[{"$ref":"#/components/schemas/EventPreferenceBase"}]},"EventPreferenceRevoked":{"type":"object","additionalProperties":false,"description":"A stated preference was revoked (a person opted out, or a customer wrote a revoke) and became the key's live statement.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/PreferenceRevokedEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the statement took effect (`effective_at`), not when it was recorded.","example":"2026-08-12T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventPreferenceRevokedData"}}},"EventSMSBase":{"type":"object","description":"Identity fields shared by every SMS lifecycle event payload.","required":["sms_id","workspace_id","to","from","tags","metadata","requested_language","resolved_language","template_id","template_version_id","template_content_hash"],"properties":{"sms_id":{"$ref":"#/components/schemas/SMSMessageID","description":"ID of the SMS message."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace that owns this event."},"to":{"type":"string","minLength":1,"description":"Where the message went. On an outbound message this is the recipient's phone number in E.164 format; on an inbound one it is your own number that received it.\n","example":"+15551234567"},"from":{"type":"string","minLength":1,"description":"Where the message came from. On an outbound message this is the sender you sent it from: an E.164 number, an alphanumeric sender ID, or a short code. On an inbound one it is the phone number that sent it to you.\n","example":"+15557654321"},"tags":{"type":["array","null"],"items":{"$ref":"#/components/schemas/Tag"},"description":"Tags provided on the send request, echoed on every event for the message so you can route and correlate without an extra lookup. Null when the message carried no tags.\n"},"metadata":{"type":["object","null"],"additionalProperties":true,"description":"The metadata object provided on the send request, echoed on every event for the message so you can correlate events with your own records. Null when the message carried no metadata.\n","example":{"order_id":"ord_123"}},"requested_language":{"description":"The template language requested by the send, in canonical form. Null when the send named no language or used no template.\n","oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}]},"resolved_language":{"description":"The template language rendered at acceptance, in canonical form. Null when the send used no template.\n","oneOf":[{"$ref":"#/components/schemas/LanguageTag"},{"type":"null"}]},"template_id":{"description":"The template rendered at acceptance, or null for a free-text message.","oneOf":[{"$ref":"#/components/schemas/SMSTemplateID"},{"type":"null"}]},"template_version_id":{"description":"The workspace published version or synthetic built-in version rendered at acceptance, or null for a free-text message. For a built-in template, `template_content_hash` identifies the exact catalogue source.\n","oneOf":[{"$ref":"#/components/schemas/SMSTemplateVersionID"},{"type":"null"}]},"template_content_hash":{"description":"The rendered language's source fingerprint, or null for a free-text message.","oneOf":[{"$ref":"#/components/schemas/SMSTemplateContentHash"},{"type":"null"}]},"cost":{"$ref":"#/components/schemas/MessageCost","description":"Message cost as of this event, split into the platform charge and any\nthird-party fees passed through. Null on an event that priced nothing.\n\nComponents are named so you can merge them per component rather than replacing the\nobject: webhook delivery is not ordered, so an older event arriving late would\notherwise overwrite a newer figure. Take the latest `occurred_at` you have seen for\neach component. `amount` is the sum of the components in this payload and does not\nrepresent a settled total.\n"}}},"EventSMSAcceptedData":{"type":"object","description":"Payload of the sms.accepted event.","allOf":[{"$ref":"#/components/schemas/EventSMSBase"},{"type":"object","required":["segments"],"properties":{"segments":{"$ref":"#/components/schemas/SMSSegments","description":"Segment breakdown used to calculate the message charge."}}}]},"EventSMSAccepted":{"type":"object","additionalProperties":false,"description":"The API accepted the SMS send request and queued it for processing.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["sms.accepted"],"description":"Event type.","example":"sms.accepted"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the API accepted the request.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSAcceptedData"}}},"EventSMSDeliveredData":{"type":"object","description":"Payload of the sms.delivered event.","allOf":[{"$ref":"#/components/schemas/EventSMSBase"},{"type":"object","properties":{"carrier":{"type":"string","description":"Carrier that delivered the message. Absent when the carrier does not report one.","example":"Verizon"},"mcc_mnc":{"type":"string","description":"Mobile country code and mobile network code of the carrier. Absent when the carrier does not report one.","example":"311480"}}}]},"EventSMSDelivered":{"type":"object","additionalProperties":false,"description":"The carrier confirmed delivery of the message to the recipient handset.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["sms.delivered"],"description":"Event type.","example":"sms.delivered"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the carrier confirmed delivery.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSDeliveredData"}}},"EventSMSExpiredData":{"type":"object","description":"Payload of the sms.expired event.","allOf":[{"$ref":"#/components/schemas/EventSMSBase"},{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/SMSError","description":"Why the message was still undelivered when its validity period elapsed. Typically `unreachable`, the handset having stayed off or out of coverage for the whole window."}}}]},"EventSMSExpired":{"type":"object","additionalProperties":false,"description":"The message's validity period elapsed before it could be delivered.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["sms.expired"],"description":"Event type.","example":"sms.expired"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the message expired.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSExpiredData"}}},"EventSMSFailedData":{"type":"object","description":"Payload of the sms.failed event.","allOf":[{"$ref":"#/components/schemas/EventSMSBase"},{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/SMSError","description":"Why the message terminally failed."}}}]},"EventSMSFailed":{"type":"object","additionalProperties":false,"description":"Message delivery failed permanently.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["sms.failed"],"description":"Event type.","example":"sms.failed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the failure was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSFailedData"}}},"SMSReceivedEventType":{"type":"string","minLength":1,"enum":["sms.received"],"description":"Always `sms.received` for this event.","example":"sms.received"},"EventSMSReceivedData":{"type":"object","description":"Payload of the sms.received event.","allOf":[{"$ref":"#/components/schemas/EventSMSBase"},{"type":"object","required":["segments"],"anyOf":[{"required":["text"]},{"required":["media"]}],"properties":{"text":{"type":"string","minLength":1,"description":"The message body, so you can act on it without a follow-up read. Absent when the message carried only attachments and no text of its own.\n","example":"STOP"},"segments":{"$ref":"#/components/schemas/SMSSegments","description":"Segment breakdown of the received body."},"carrier":{"type":"string","description":"Carrier the message came in over. Absent where the carrier does not report one.","example":"Verizon"},"mcc_mnc":{"type":"string","description":"Mobile country code and mobile network code of the carrier. Absent when not known.","example":"311480"},"subject":{"type":"string","description":"Subject line. Absent when the message carried none."}}}]},"EventSMSReceived":{"type":"object","additionalProperties":false,"description":"A message was received on one of your numbers.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/SMSReceivedEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the sender sent the message.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSReceivedData"}}},"EventSMSRejectedData":{"type":"object","description":"Payload of the sms.rejected event.","allOf":[{"$ref":"#/components/schemas/EventSMSBase"},{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/SMSError","description":"Why the message was rejected before reaching the carrier."}}}]},"EventSMSRejected":{"type":"object","additionalProperties":false,"description":"The API rejected the message before sending it to the carrier because of an invalid destination, suppression, or content or policy restriction.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["sms.rejected"],"description":"Event type.","example":"sms.rejected"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the rejection was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSRejectedData"}}},"EventSMSSentData":{"type":"object","description":"Payload of the sms.sent event.","allOf":[{"$ref":"#/components/schemas/EventSMSBase"},{"type":"object","properties":{"carrier":{"type":"string","description":"Carrier that handled the message. Absent when the carrier does not report one.","example":"Verizon"},"mcc_mnc":{"type":"string","description":"Mobile country code and mobile network code of the carrier. Absent when the carrier does not report one.","example":"311480"}}}]},"EventSMSSent":{"type":"object","additionalProperties":false,"description":"The API handed the message to the carrier for delivery.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["sms.sent"],"description":"Event type.","example":"sms.sent"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the message was handed to the carrier.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSSentData"}}},"EventSMSTfnVerificationBase":{"type":"object","description":"Identity fields shared by every toll-free verification event payload.","required":["verification_id","workspace_id","status","sender_id"],"properties":{"verification_id":{"$ref":"#/components/schemas/TFNVerificationID","description":"ID of the toll-free verification."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace that owns the verification."},"status":{"type":"string","minLength":1,"description":"Lifecycle state of the verification at the time of the event: `draft`, `submitted`, `under_review`, `info_requested`, `approved`, or `rejected`. `info_requested` and `rejected` allow resubmission; `approved` is final.\n","example":"approved"},"sender_id":{"$ref":"#/components/schemas/SMSSenderID","description":"ID of the toll-free number the verification licenses."}}},"EventSMSTfnVerificationApprovedData":{"type":"object","description":"Payload of the sms.tfn_verification.approved event.","allOf":[{"$ref":"#/components/schemas/EventSMSTfnVerificationBase"}]},"EventSMSTfnVerificationApproved":{"type":"object","additionalProperties":false,"description":"The carrier approved a toll-free number verification; the number can now send in its licensed countries.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["sms.tfn_verification.approved"],"description":"Event type.","example":"sms.tfn_verification.approved"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the approval was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSTfnVerificationApprovedData"}}},"EventSMSTfnVerificationInfoRequestedData":{"type":"object","description":"Payload of the sms.tfn_verification.info_requested event.","allOf":[{"$ref":"#/components/schemas/EventSMSTfnVerificationBase"}]},"EventSMSTfnVerificationInfoRequested":{"type":"object","additionalProperties":false,"description":"The carrier requested more information before deciding a toll-free number verification.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["sms.tfn_verification.info_requested"],"description":"Event type.","example":"sms.tfn_verification.info_requested"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the information request was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSTfnVerificationInfoRequestedData"}}},"EventSMSTfnVerificationRejectedData":{"type":"object","description":"Payload of the sms.tfn_verification.rejected event.","allOf":[{"$ref":"#/components/schemas/EventSMSTfnVerificationBase"},{"type":"object","required":["denial_reasons","resubmit_allowed"],"properties":{"denial_reasons":{"type":"array","items":{"type":"string","minLength":1},"description":"Human-readable reasons the carrier gave for the rejection.","example":["opt-in workflow unclear"]},"resubmit_allowed":{"type":"boolean","description":"Whether the verification may be corrected and resubmitted within the resubmission window.","example":true}}}]},"EventSMSTfnVerificationRejected":{"type":"object","additionalProperties":false,"description":"The carrier rejected a toll-free number verification; the number cannot send until an accepted verification is approved.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["sms.tfn_verification.rejected"],"description":"Event type.","example":"sms.tfn_verification.rejected"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the rejection was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSTfnVerificationRejectedData"}}},"EventSMSTfnVerificationSubmittedData":{"type":"object","description":"Payload of the sms.tfn_verification.submitted event.","allOf":[{"$ref":"#/components/schemas/EventSMSTfnVerificationBase"}]},"EventSMSTfnVerificationSubmitted":{"type":"object","additionalProperties":false,"description":"A toll-free number verification was submitted to the carrier for review.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["sms.tfn_verification.submitted"],"description":"Event type.","example":"sms.tfn_verification.submitted"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the verification was submitted.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSTfnVerificationSubmittedData"}}},"EventSMSUndeliveredData":{"type":"object","description":"Payload of the sms.undelivered event.","allOf":[{"$ref":"#/components/schemas/EventSMSBase"},{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/SMSError","description":"Why the message was not delivered."}}}]},"EventSMSUndelivered":{"type":"object","additionalProperties":false,"description":"The carrier reported a non-permanent failure to deliver the message.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["sms.undelivered"],"description":"Event type.","example":"sms.undelivered"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the non-delivery was recorded.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSUndeliveredData"}}},"SMSSuppressionCreatedEventType":{"type":"string","minLength":1,"enum":["sms_suppression.created"],"description":"Always `sms_suppression.created` for this event.","example":"sms_suppression.created"},"EventSMSSuppressionCreatedData":{"type":"object","additionalProperties":false,"description":"Payload of the sms_suppression.created event.","required":["suppression_id","destination","originator","reason","workspace_id"],"properties":{"suppression_id":{"$ref":"#/components/schemas/SMSSuppressionID","description":"The suppression episode that was opened.","example":"ssu_01krdgeqcxet5s7t44vh8rt9mg"},"destination":{"type":"string","minLength":2,"maxLength":20,"description":"The subscriber, in E.164 format.","example":"+15550001234"},"originator":{"type":"string","minLength":1,"maxLength":20,"description":"The sender this stops. An SMS suppression is the exact (sender, recipient) pair, so your other senders still reach this subscriber.","example":"+15557654321"},"reason":{"allOf":[{"$ref":"#/components/schemas/SMSSuppressionReason"}]},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"The workspace the suppression belongs to.","example":"ws_01krdgeqcxet5s7t44vh8rt9mg"}}},"EventSMSSuppressionCreated":{"type":"object","additionalProperties":false,"description":"A destination was added to the workspace's SMS suppression ledger: a subscriber's STOP, a carrier opt-out, or a manual add.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/SMSSuppressionCreatedEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the episode's opening statement took effect (`effective_at`).","example":"2026-08-12T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventSMSSuppressionCreatedData"}}},"EventVerifyBase":{"type":"object","description":"Identity fields shared by every Verify lifecycle event payload.","required":["verification_id","workspace_id","to","metadata"],"properties":{"verification_id":{"$ref":"#/components/schemas/VerificationID","description":"ID of the verification session."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace that owns this event."},"to":{"$ref":"#/components/schemas/VerificationTo","description":"The recipient identity of the verification session (email address, phone number, or both), echoed on every event so you can correlate without an extra lookup. An individual attempt reports the single address it was dispatched to in its own `address` field."},"metadata":{"type":["object","null"],"additionalProperties":true,"description":"The metadata object provided when the verification was created, echoed on every event for the session so you can correlate events with your own records. Null when the verification carried no metadata.\n","example":{"user_id":"usr_123"}}}},"EventVerifyAttemptDeliveredData":{"type":"object","description":"Payload of the verify.attempt.delivered event.","allOf":[{"$ref":"#/components/schemas/EventVerifyBase"},{"type":"object","required":["channel","address","carrier","mcc_mnc","delivered_at"],"properties":{"channel":{"$ref":"#/components/schemas/VerificationChannel","description":"The channel this attempt was sent on.","example":"sms"},"address":{"type":"string","minLength":1,"description":"The single address this attempt was dispatched to, an E.164 phone number or an email address.","example":"+14155550100"},"carrier":{"type":["string","null"],"description":"Carrier that delivered the message, when the carrier network reports it. Always null for email, WhatsApp, and Telegram.","example":"Verizon"},"mcc_mnc":{"type":["string","null"],"description":"Mobile country code and mobile network code of the delivering carrier, when reported. Always null for email, WhatsApp, and Telegram.","example":"311480"},"delivered_at":{"type":"string","minLength":1,"format":"date-time","description":"Time delivery was confirmed.","example":"2026-07-24T12:00:05Z"}}}]},"EventVerifyAttemptDelivered":{"type":"object","additionalProperties":false,"description":"The channel confirmed delivery of a one-time passcode to the recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["verify.attempt.delivered"],"description":"Event type.","example":"verify.attempt.delivered"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time delivery was confirmed.","example":"2026-07-24T12:00:05Z"},"data":{"$ref":"#/components/schemas/EventVerifyAttemptDeliveredData"}}},"EventVerifyAttemptSentData":{"type":"object","description":"Payload of the verify.attempt.sent event.","allOf":[{"$ref":"#/components/schemas/EventVerifyBase"},{"type":"object","required":["channel","address","from","sent_at"],"properties":{"channel":{"$ref":"#/components/schemas/VerificationChannel","description":"The channel this attempt was sent on.","example":"sms"},"address":{"type":"string","minLength":1,"description":"The single address this attempt was dispatched to, an E.164 phone number or an email address.","example":"+14155550100"},"from":{"type":["string","null"],"description":"The sender the passcode was sent from: a phone number, alphanumeric sender ID, short code, or email address. Null when the channel exposes no sender.","example":"29999"},"sent_at":{"type":"string","minLength":1,"format":"date-time","description":"Time the passcode was dispatched.","example":"2026-07-24T12:00:01Z"}}}]},"EventVerifyAttemptSent":{"type":"object","additionalProperties":false,"description":"A one-time passcode was dispatched to the recipient on a channel.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["verify.attempt.sent"],"description":"Event type.","example":"verify.attempt.sent"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the passcode was dispatched.","example":"2026-07-24T12:00:01Z"},"data":{"$ref":"#/components/schemas/EventVerifyAttemptSentData"}}},"EventVerifyAttemptUndeliveredData":{"type":"object","description":"Payload of the verify.attempt.undelivered event.","allOf":[{"$ref":"#/components/schemas/EventVerifyBase"},{"type":"object","required":["channel","address","reason","error","failed_at"],"properties":{"channel":{"$ref":"#/components/schemas/VerificationChannel","description":"The channel this attempt was sent on.","example":"sms"},"address":{"type":"string","minLength":1,"description":"The single address this attempt was dispatched to, an E.164 phone number or an email address.","example":"+14155550100"},"reason":{"$ref":"#/components/schemas/VerificationAttemptFailureReason","description":"Why the attempt failed to reach the recipient.","example":"carrier_rejected"},"error":{"type":["string","null"],"description":"Diagnostic text describing the failure, for display only. Null when none was reported.","example":"Carrier rejected the message before delivery"},"failed_at":{"type":"string","minLength":1,"format":"date-time","description":"Time the failure was recorded.","example":"2026-07-24T12:00:05Z"}}}]},"EventVerifyAttemptUndelivered":{"type":"object","additionalProperties":false,"description":"A one-time passcode failed to deliver to the recipient.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["verify.attempt.undelivered"],"description":"Event type.","example":"verify.attempt.undelivered"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the failure was recorded.","example":"2026-07-24T12:00:05Z"},"data":{"$ref":"#/components/schemas/EventVerifyAttemptUndeliveredData"}}},"EventVerifyVerificationCreatedData":{"type":"object","description":"Payload of the verify.verification.created event.","allOf":[{"$ref":"#/components/schemas/EventVerifyBase"},{"type":"object","required":["channel","status","created_at"],"properties":{"channel":{"$ref":"#/components/schemas/VerificationChannel","description":"The first channel of the verification's resolved channel plan.","example":"sms"},"status":{"type":"string","minLength":1,"x-extensible-enum":["pending"],"description":"The verification's state at creation, always `pending`. Open enum for forward compatibility.","example":"pending"},"created_at":{"type":"string","minLength":1,"format":"date-time","description":"Time the verification session was created.","example":"2026-07-24T12:00:00Z"}}}]},"EventVerifyVerificationCreated":{"type":"object","additionalProperties":false,"description":"A verification session was created and its first one-time passcode is being sent.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["verify.verification.created"],"description":"Event type.","example":"verify.verification.created"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the verification session was created.","example":"2026-07-24T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventVerifyVerificationCreatedData"}}},"VerifyVerificationFailedEventType":{"type":"string","minLength":1,"enum":["verify.verification.failed"],"description":"Always `verify.verification.failed` for this event.","example":"verify.verification.failed"},"EventVerifyVerificationFailedData":{"type":"object","description":"Payload of the verify.verification.failed event.","allOf":[{"$ref":"#/components/schemas/EventVerifyBase"},{"type":"object","required":["status","reason","channel","last_attempt_reason","failed_at"],"properties":{"status":{"type":"string","minLength":1,"x-extensible-enum":["failed"],"description":"The verification's state, always `failed`. Open enum for forward compatibility.","example":"failed"},"reason":{"$ref":"#/components/schemas/VerificationTerminalReason","description":"Why the verification ended. Always `undeliverable` on this event: no planned channel delivered a passcode.","example":"undeliverable"},"channel":{"oneOf":[{"$ref":"#/components/schemas/VerificationChannel"},{"type":"null"}],"description":"The last channel the verification tried, the one whose failure left it with nowhere else to go. Null when no channel was attributed.","example":"sms"},"last_attempt_reason":{"$ref":"#/components/schemas/VerificationAttemptFailureReason","description":"Why that last send did not deliver. This is the actionable half of the event: `not_billable` means the workspace balance could not cover the send, while the delivery reasons point at the recipient or the channel.","example":"not_billable"},"failed_at":{"type":"string","minLength":1,"format":"date-time","description":"Time the verification was resolved.","example":"2026-07-24T12:00:05Z"}}}]},"EventVerifyVerificationFailed":{"type":"object","additionalProperties":false,"description":"The verification ended without the recipient receiving a passcode: every planned channel reported that its send would not arrive.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/VerifyVerificationFailedEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the verification was resolved.","example":"2026-07-24T12:00:05Z"},"data":{"$ref":"#/components/schemas/EventVerifyVerificationFailedData"}}},"EventVerifyVerificationVerifiedData":{"type":"object","description":"Payload of the verify.verification.verified event.","allOf":[{"$ref":"#/components/schemas/EventVerifyBase"},{"type":"object","required":["status","channel","verified_at"],"properties":{"status":{"type":"string","minLength":1,"x-extensible-enum":["verified"],"description":"The verification's state, always `verified`. Open enum for forward compatibility.","example":"verified"},"channel":{"oneOf":[{"$ref":"#/components/schemas/VerificationChannel"},{"type":"null"}],"description":"The channel whose passcode the recipient confirmed, the channel that converted. Null when the verification was resolved without attributing a channel.","example":"sms"},"verified_at":{"type":"string","minLength":1,"format":"date-time","description":"Time the verification was verified.","example":"2026-07-24T12:01:00Z"}}}]},"EventVerifyVerificationVerified":{"type":"object","additionalProperties":false,"description":"The verification was successfully resolved: the recipient confirmed the correct code.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["verify.verification.verified"],"description":"Event type.","example":"verify.verification.verified"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the verification was verified.","example":"2026-07-24T12:01:00Z"},"data":{"$ref":"#/components/schemas/EventVerifyVerificationVerifiedData"}}},"VoiceCallID":{"type":"string","minLength":1,"pattern":"^vcl_[0-9a-hjkmnp-tv-z]{26}$","example":"vcl_01krdgeqcxet5s7t44vh8rt9mg"},"VoiceSessionID":{"type":"string","minLength":1,"pattern":"^vcs_[0-9a-hjkmnp-tv-z]{26}$","example":"vcs_01krdgeqcxet5s7t44vh8rt9mg"},"VoiceCallDirection":{"type":"string","minLength":1,"enum":["inbound","outbound"],"description":"Whether the call originated from your PBX (outbound) or arrived from a remote party (inbound).","example":"outbound"},"EventVoiceBase":{"type":"object","description":"Identity fields shared by every voice call lifecycle event payload.","required":["call_id","workspace_id","direction","from","to"],"properties":{"call_id":{"$ref":"#/components/schemas/VoiceCallID","description":"ID of the call record."},"session_id":{"oneOf":[{"$ref":"#/components/schemas/VoiceSessionID"},{"type":"null"}],"description":"Session identifier shared across all legs of a multi-party or transferred call. Use this to correlate related call records. Null when session correlation is not available for the call."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace that owns this event."},"direction":{"$ref":"#/components/schemas/VoiceCallDirection"},"from":{"type":"string","minLength":1,"description":"Calling party number in E.164 format.","example":"+14155551234"},"to":{"type":"string","minLength":1,"description":"Called party number in E.164 format.","example":"+16505559876"}}},"EventVoiceCallAnsweredData":{"type":"object","description":"Payload of the voice_call.answered event.","allOf":[{"$ref":"#/components/schemas/EventVoiceBase"}]},"EventVoiceCallAnswered":{"type":"object","additionalProperties":false,"description":"The called party answered and media began flowing.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["voice_call.answered"],"description":"Event type.","example":"voice_call.answered"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the call was answered.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventVoiceCallAnsweredData"}}},"VoiceCallStatus":{"type":"string","minLength":1,"enum":["answered","no_answer","busy","canceled","failed","rejected","unknown","ringing","in_progress"],"description":"Call status.\n\nA call that has ended carries one of:\n\n- `answered` means it connected and the far end picked up.\n- `no_answer` means nobody picked up before the call timed out.\n- `rejected` means it was refused rather than attempted. Either we turned it\n  away before dialing a carrier, in which case `rejection_reason` names the\n  check it failed where there was one, or the far end declined it.\n- `failed` means it was attempted and did not work, and `sip_response_code`\n  is what came back.\n- `unknown` means the outcome could not be determined. Contact support with\n  the call `id` if you see one.\n\nAn active call carries `ringing` before it is picked up and `in_progress`\nafterward. The call list's `status` filter takes any mix of the two sets.\n\n`busy` and `canceled` are reserved for incoming calls delivered to your own\nnumbers: `busy` for a called party that rejected the call as busy, `canceled`\nfor a caller who hung up before it was picked up. Neither is emitted yet and\nboth outcomes are reported as `failed` today.\n","example":"answered"},"EventVoiceCallEndedData":{"type":"object","description":"Payload of the voice_call.ended event.","allOf":[{"$ref":"#/components/schemas/EventVoiceBase"},{"type":"object","required":["status","sip_response_code","duration_ms","billable_ms"],"properties":{"status":{"$ref":"#/components/schemas/VoiceCallStatus"},"sip_response_code":{"type":["integer","null"],"minimum":100,"description":"Final SIP response code received from the carrier. Null when no SIP response was received, for example on timeout or DNS failure.","example":200},"duration_ms":{"type":"integer","minimum":0,"description":"Total call duration in milliseconds, measured from the first SIP `INVITE` to the `BYE` or final response.","example":65000},"billable_ms":{"type":"integer","minimum":0,"description":"Billable duration in milliseconds, measured from answer to call end. Zero for unanswered calls.","example":60000}}}]},"EventVoiceCallEnded":{"type":"object","additionalProperties":false,"description":"The call ended after either party hung up or call setup failed.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["voice_call.ended"],"description":"Event type.","example":"voice_call.ended"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time either party hung up or call setup failed.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventVoiceCallEndedData"}}},"EventVoiceCallInitiatedData":{"type":"object","description":"Payload of the voice_call.initiated event.","allOf":[{"$ref":"#/components/schemas/EventVoiceBase"}]},"EventVoiceCallInitiated":{"type":"object","additionalProperties":false,"description":"Call routing began.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["voice_call.initiated"],"description":"Event type.","example":"voice_call.initiated"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the call was initiated.","example":"2026-05-21T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventVoiceCallInitiatedData"}}},"EventWhatsAppBase":{"type":"object","description":"Identity fields shared by every WhatsApp lifecycle event payload.","required":["whatsapp_id","workspace_id","direction","from","to","tags","metadata"],"properties":{"whatsapp_id":{"$ref":"#/components/schemas/WhatsAppMessageID","description":"ID of the WhatsApp message."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace that owns this event."},"direction":{"type":"string","minLength":1,"enum":["outbound","inbound"],"description":"Whether the message was sent by the business (`outbound`) or received from the contact (`inbound`)."},"from":{"$ref":"#/components/schemas/WhatsAppAddress","description":"Sender of the message. On outbound messages, the business number it was sent from; on inbound, the WhatsApp contact."},"to":{"$ref":"#/components/schemas/WhatsAppAddress","description":"Recipient of the message. On outbound messages, the WhatsApp contact; on inbound, the business number."},"tags":{"type":["array","null"],"items":{"$ref":"#/components/schemas/Tag"},"description":"Tags provided on the send request, echoed on every event for the message. Null when the message carried no tags.\n"},"metadata":{"type":["object","null"],"additionalProperties":true,"description":"The metadata object provided on the send request, echoed on every event for the message. Null when the message carried no metadata.\n","example":{"order_id":"ord_123"}},"in_reply_to_message_id":{"allOf":[{"$ref":"#/components/schemas/WhatsAppMessageID"}],"description":"The message this one answers. On an outbound message it is the `in_reply_to_message_id` the send request quoted. On an inbound message it is what WhatsApp reports as the reply's target: a tap on a button or a list row, and equally a text or media message the contact sent as a quoted reply. Absent when the message answers nothing, and absent on an inbound message whose target we cannot match to a message we hold, which is the case for one sent before this workspace started recording them or one already past the 15-day window we keep provider ids for.\n"}}},"EventWhatsAppAcceptedData":{"type":"object","description":"Payload of the whatsapp.accepted event.","allOf":[{"$ref":"#/components/schemas/EventWhatsAppBase"}]},"EventWhatsAppAccepted":{"type":"object","additionalProperties":false,"description":"The API accepted and charged the send request.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["whatsapp.accepted"],"description":"Event type.","example":"whatsapp.accepted"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the API accepted and charged the send request.","example":"2026-07-16T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppAcceptedData"}}},"WhatsAppAgentDeliveredEventType":{"type":"string","minLength":1,"enum":["whatsapp.agent.delivered"],"description":"Always `whatsapp.agent.delivered` for this event.","example":"whatsapp.agent.delivered"},"EventWhatsAppDeliveredData":{"type":"object","description":"Payload of the whatsapp.delivered event.","allOf":[{"$ref":"#/components/schemas/EventWhatsAppBase"},{"type":"object","properties":{"recipient":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"description":"The participant delivery was confirmed to, on a group message. A group send raises this event once per participant, so this is what tells the deliveries apart. Absent on a one-to-one message, whose `to` already names its recipient.\n"}}}]},"EventWhatsAppAgentDelivered":{"type":"object","additionalProperties":false,"description":"A message the business agent sent was delivered to the contact's device.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/WhatsAppAgentDeliveredEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time WhatsApp reported the delivery.","example":"2026-09-01T09:14:52Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppDeliveredData"}}},"WhatsAppAgentHandedOffEventType":{"type":"string","minLength":1,"enum":["whatsapp.agent.handed_off"],"description":"Always `whatsapp.agent.handed_off` for this event.","example":"whatsapp.agent.handed_off"},"EventWhatsAppAgentHandedOffData":{"type":"object","additionalProperties":false,"description":"Payload of the whatsapp.agent.handed_off event.","required":["workspace_id","from","to","new_controller","initiator","reason"],"properties":{"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace that owns this event."},"from":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"description":"Your WhatsApp number, the business side of the conversation."},"to":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"description":"The contact whose conversation changed hands. Carries the business-scoped user ID as well as the phone number whenever the conversation was open at the time."},"new_controller":{"$ref":"#/components/schemas/WhatsAppAgentController","description":"Who holds the conversation from this moment on."},"initiator":{"$ref":"#/components/schemas/WhatsAppAgentHandoffInitiator","description":"What caused the handoff."},"reason":{"type":["string","null"],"description":"The note whoever gave up control left for the next handler, or null when they left none. It is never shown to the contact.","example":"Contact asked for a refund on order 88213."}}},"EventWhatsAppAgentHandedOff":{"type":"object","additionalProperties":false,"description":"A contact's conversation changed hands between the agent and your own application.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/WhatsAppAgentHandedOffEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the conversation changed hands.","example":"2026-09-01T09:14:52Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppAgentHandedOffData"}}},"WhatsAppAgentReadEventType":{"type":"string","minLength":1,"enum":["whatsapp.agent.read"],"description":"Always `whatsapp.agent.read` for this event.","example":"whatsapp.agent.read"},"EventWhatsAppReadData":{"type":"object","description":"Payload of the whatsapp.read event.","allOf":[{"$ref":"#/components/schemas/EventWhatsAppBase"},{"type":"object","properties":{"recipient":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"description":"The participant who opened the message, on a group message. A group send raises this event once per participant, so this is what tells the deliveries apart. Absent on a one-to-one message, whose `to` already names its recipient.\n"}}}]},"EventWhatsAppAgentRead":{"type":"object","additionalProperties":false,"description":"The contact read a message the business agent sent.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/WhatsAppAgentReadEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time WhatsApp reported the read.","example":"2026-09-01T09:14:52Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppReadData"}}},"WhatsAppAgentReceivedEventType":{"type":"string","minLength":1,"enum":["whatsapp.agent.received"],"description":"Always `whatsapp.agent.received` for this event.","example":"whatsapp.agent.received"},"EventWhatsAppReceivedData":{"type":"object","description":"Payload of the whatsapp.received event. Carries the message's content so a subscriber can act on it without reading the message back.\n","allOf":[{"$ref":"#/components/schemas/EventWhatsAppBase"},{"type":"object","properties":{"text":{"allOf":[{"$ref":"#/components/schemas/WhatsAppText"}],"description":"Text the contact sent."},"image":{"allOf":[{"$ref":"#/components/schemas/WhatsAppImage"}],"description":"Image the contact sent."},"video":{"allOf":[{"$ref":"#/components/schemas/WhatsAppVideo"}],"description":"Video the contact sent."},"audio":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAudio"}],"description":"Audio the contact sent."},"sticker":{"allOf":[{"$ref":"#/components/schemas/WhatsAppSticker"}],"description":"Sticker the contact sent."},"document":{"allOf":[{"$ref":"#/components/schemas/WhatsAppDocument"}],"description":"Document the contact sent."},"location":{"allOf":[{"$ref":"#/components/schemas/WhatsAppLocation"}],"description":"Location the contact sent."},"contact_cards":{"type":"array","description":"Contact cards the contact shared, either by tapping a button that asked for their number or by sending a card from their address book.\n","items":{"$ref":"#/components/schemas/WhatsAppContactCard"}},"interactive_reply":{"allOf":[{"$ref":"#/components/schemas/WhatsAppInteractiveReply"}],"description":"What the contact tapped, when the message answers an interactive message or a template's quick-reply button.\n"},"unsupported":{"allOf":[{"$ref":"#/components/schemas/WhatsAppUnsupported"}],"description":"Set when the contact sent content the API does not model, naming the WhatsApp content type.\n"}}}]},"EventWhatsAppAgentReceived":{"type":"object","additionalProperties":false,"description":"A contact wrote to a number while the business agent held their conversation. The agent answers this message; your application does not. Messages that arrive while your application holds the conversation raise `whatsapp.received` instead.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/WhatsAppAgentReceivedEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the contact sent the message, as reported by WhatsApp.","example":"2026-09-01T09:14:52Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppReceivedData"}}},"WhatsAppAgentSentEventType":{"type":"string","minLength":1,"enum":["whatsapp.agent.sent"],"description":"Always `whatsapp.agent.sent` for this event.","example":"whatsapp.agent.sent"},"EventWhatsAppSentData":{"type":"object","description":"Payload of the whatsapp.sent event.","allOf":[{"$ref":"#/components/schemas/EventWhatsAppBase"}]},"EventWhatsAppAgentSent":{"type":"object","additionalProperties":false,"description":"The business agent sent a message to a contact on your number. It is a message your workspace did not compose, so it is reported apart from `whatsapp.sent`.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/WhatsAppAgentSentEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the agent sent the message, as reported by WhatsApp.","example":"2026-09-01T09:14:52Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppSentData"}}},"EventWhatsAppDelivered":{"type":"object","additionalProperties":false,"description":"The message was delivered to the recipient's device.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["whatsapp.delivered"],"description":"Event type.","example":"whatsapp.delivered"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the message was delivered to the recipient's device.","example":"2026-07-16T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppDeliveredData"}}},"EventWhatsAppFailedData":{"type":"object","description":"Payload of the whatsapp.failed event.","allOf":[{"$ref":"#/components/schemas/EventWhatsAppBase"},{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/WhatsAppError","description":"Why the message terminally failed."}}}]},"EventWhatsAppFailed":{"type":"object","additionalProperties":false,"description":"Message delivery failed permanently.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["whatsapp.failed"],"description":"Event type.","example":"whatsapp.failed"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the failure was recorded.","example":"2026-07-16T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppFailedData"}}},"WhatsAppReactedEventType":{"type":"string","minLength":1,"enum":["whatsapp.reacted"],"description":"Always `whatsapp.reacted` for this event.","example":"whatsapp.reacted"},"EventWhatsAppReactedData":{"type":"object","additionalProperties":false,"description":"Payload of the whatsapp.reacted event. Names the message the contact reacted to, not the reaction, because a reaction is an annotation on a message rather than a message of its own.\n","required":["whatsapp_id","emoji","from","to","workspace_id"],"properties":{"whatsapp_id":{"$ref":"#/components/schemas/WhatsAppMessageID","description":"The message the contact reacted to. WhatsApp accepts a reaction on a message up to 30 days old, and we keep provider ids for 15, so a reaction placed on a message older than that cannot be matched to it and raises no event at all.\n"},"emoji":{"type":["string","null"],"description":"The emoji the contact placed, as WhatsApp sent it and not normalized. Null when they took their reaction back rather than placing one. Always present, so null is the removal itself rather than a value we are missing.\n","example":"👍"},"from":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"description":"The contact who reacted, as WhatsApp identified them."},"to":{"allOf":[{"$ref":"#/components/schemas/WhatsAppAddress"}],"description":"Your WhatsApp number, the business side of the conversation."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"ID of the workspace that owns this event."}}},"EventWhatsAppReacted":{"type":"object","additionalProperties":false,"description":"A contact placed, changed or took back a reaction on a message.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/WhatsAppReactedEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the contact reacted, as reported by WhatsApp. Meta reports this to the second, so a contact who changes or withdraws a reaction quickly can produce two events sharing one timestamp. Sorting reactions on one message by this field cannot order those, and neither can delivery order, which retries make unreliable. Act on the reaction each event carries, as the change it describes; do not reconstruct the sequence from the events or treat the last one to arrive as the message's standing reaction. Read the message back for the reactions that stand: `getWhatsAppMessage` (`GET /v1/whatsapp/messages/{message_id}`) returns one entry per sender in `reactions`, and `listWhatsAppMessageReactionEvents` has every change.\n","example":"2026-08-28T19:01:10Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppReactedData"}}},"EventWhatsAppRead":{"type":"object","additionalProperties":false,"description":"The recipient read the message.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["whatsapp.read"],"description":"Event type.","example":"whatsapp.read"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the recipient read the message.","example":"2026-07-16T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppReadData"}}},"WhatsAppReceivedEventType":{"type":"string","minLength":1,"enum":["whatsapp.received"],"description":"Event type.","example":"whatsapp.received"},"EventWhatsAppReceived":{"type":"object","additionalProperties":false,"description":"A contact sent the business a WhatsApp message.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/WhatsAppReceivedEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the contact sent the message, as reported by WhatsApp.","example":"2026-07-16T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppReceivedData"}}},"EventWhatsAppRejectedData":{"type":"object","description":"Payload of the whatsapp.rejected event.","allOf":[{"$ref":"#/components/schemas/EventWhatsAppBase"},{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/WhatsAppError","description":"Why the message was rejected before sending."}}}]},"EventWhatsAppRejected":{"type":"object","additionalProperties":false,"description":"The API rejected the message before sending it to WhatsApp because the recipient is on the workspace suppression list, the wallet has insufficient balance, or the destination is unpriced. The message is not sent or charged.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["whatsapp.rejected"],"description":"Event type.","example":"whatsapp.rejected"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the rejection was recorded.","example":"2026-07-23T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppRejectedData"}}},"EventWhatsAppSent":{"type":"object","additionalProperties":false,"description":"The API handed the message to Meta for delivery.","required":["type","timestamp","data"],"properties":{"type":{"type":"string","minLength":1,"enum":["whatsapp.sent"],"description":"Event type.","example":"whatsapp.sent"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"Time the API handed the message to Meta for delivery.","example":"2026-07-16T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppSentData"}}},"WhatsAppSuppressionCreatedEventType":{"type":"string","minLength":1,"enum":["whatsapp_suppression.created"],"description":"Always `whatsapp_suppression.created` for this event.","example":"whatsapp_suppression.created"},"EventWhatsAppSuppressionCreatedData":{"type":"object","additionalProperties":false,"description":"Payload of the whatsapp_suppression.created event.","required":["suppression_id","address","waba","reason","workspace_id"],"properties":{"suppression_id":{"$ref":"#/components/schemas/WhatsAppSuppressionID","description":"The suppression episode that was opened.","example":"was_01krdgeqcxet5s7t44vh8rt9mg"},"address":{"type":"string","minLength":1,"description":"The suppressed WhatsApp address. For a phone number this is canonical E.164 with a leading plus sign, such as `+5511977670804`.","example":"+5511977670804"},"waba":{"type":["string","null"],"description":"The WhatsApp Business Account the suppression is limited to, identified by its WhatsApp-issued account ID, or null when it covers the whole workspace.","example":null},"reason":{"type":"string","minLength":1,"x-extensible-enum":["manual"],"description":"Why the address is suppressed. `manual` means it was added directly rather than created automatically from a delivery outcome. This list grows over time, so treat an unknown value as informational rather than rejecting the record."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"The workspace the suppression belongs to.","example":"ws_01krdgeqcxet5s7t44vh8rt9mg"}}},"EventWhatsAppSuppressionCreated":{"type":"object","additionalProperties":false,"description":"An address was added to the workspace's WhatsApp suppression ledger.","required":["type","timestamp","data"],"properties":{"type":{"$ref":"#/components/schemas/WhatsAppSuppressionCreatedEventType"},"timestamp":{"type":"string","minLength":1,"format":"date-time","description":"When the episode's opening statement took effect (`effective_at`).","example":"2026-08-12T12:00:00Z"},"data":{"$ref":"#/components/schemas/EventWhatsAppSuppressionCreatedData"}}},"WebhookTestResponse":{"type":"object","additionalProperties":false,"required":["status","response_status_code","response_duration_ms"],"properties":{"status":{"type":"string","minLength":1,"enum":["delivered","failed"],"description":"Whether your endpoint accepted the test event. `delivered` means it returned a `2xx` status; `failed` means it returned a non-`2xx` status or could not be reached (see `error` for the latter).\n"},"response_status_code":{"type":["integer","null"],"description":"HTTP status returned by your endpoint. Null when no response was received (timeout, connection error, DNS failure).","example":200},"response_body":{"type":"string","description":"Response body returned by your endpoint, truncated to the first 1024 bytes. Omitted when your endpoint returned no body or could not be reached.\n","example":"OK"},"response_duration_ms":{"type":"integer","minimum":0,"description":"Round-trip delivery latency in milliseconds.","example":142},"event_payload":{"description":"The full event body delivered to your endpoint. Test sends use a minimal synthetic body rather than a full event payload, so this field is omitted.\n","$ref":"#/components/schemas/WebhookEvent"},"error":{"type":"string","minLength":1,"description":"A short explanation of why the event could not be delivered. Present only when your endpoint could not be reached.","example":"connection refused"}}},"WebhookReplayRequest":{"type":"object","additionalProperties":false,"properties":{"since":{"type":"string","format":"date-time","minLength":1,"description":"Replay events that occurred at or after this timestamp. Defaults to 24 hours before the request when omitted.\n","example":"2026-05-07T00:00:00Z"},"until":{"type":"string","format":"date-time","minLength":1,"description":"Replay events that occurred before or at this timestamp. Omit to bound the window only by `since`.\n","example":"2026-05-07T23:59:59Z"}}},"WebhookEventID":{"type":"string","minLength":1,"pattern":"^whe_[0-9a-hjkmnp-tv-z]{26}$","example":"whe_01krdgeqcxet5s7t44vh8rt9mg"},"WebhookAttempt":{"type":"object","additionalProperties":false,"required":["id","event_type","status","url","response_status_code","response_duration_ms","attempted_at"],"properties":{"id":{"type":"string","readOnly":true,"minLength":1,"description":"Identifier of this individual delivery attempt. Each retry is a separate attempt with its own id; use `event_id` to group the attempts for one event.\n","example":"msgatt_3FdaB1NkOmM6m8AxhgEYTJgqHU3"},"event_id":{"description":"Bird's source event ID, stable across retries of the same event. Null only for older attempts recorded before event IDs were available.","oneOf":[{"$ref":"#/components/schemas/WebhookEventID"},{"type":"null"}]},"event_type":{"$ref":"#/components/schemas/WebhookEventType"},"status":{"type":"string","minLength":1,"enum":["delivered","pending","failed"],"description":"Outcome of this attempt.\n\n- `delivered`: your endpoint accepted it with a `2xx` response.\n- `pending`: the attempt is still in flight.\n- `failed`: it returned a non-`2xx` response or no response at all. A `failed`\n  attempt is not final for the event: automatic retries appear as further\n  attempts with the same `event_id`.\n"},"url":{"type":"string","format":"uri","minLength":1,"description":"URL the request was sent to: the endpoint's `url` at the time of the attempt, which can differ from the current configuration after an update.\n","example":"https://example.com/webhooks"},"response_status_code":{"type":["integer","null"],"description":"HTTP status returned by the receiver. Null when no response was received (timeout, connection error, DNS failure).","example":200},"response_body":{"type":"string","description":"Response body your endpoint returned, which may be truncated. Omitted when no body was returned.\n","example":"{\"ok\":true}"},"response_duration_ms":{"type":"integer","minimum":0,"description":"Round-trip duration in milliseconds.","example":87},"attempted_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this attempt was made. Attempts are listed newest first by this timestamp, and the list's `before`/`after` parameters bound it.\n","example":"2026-05-22T11:50:38.080Z"}}},"WebhookAttemptList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Delivery attempts, newest first.","items":{"$ref":"#/components/schemas/WebhookAttempt"}}}},"RequestID":{"type":"string","minLength":1,"pattern":"^req_[0-9a-hjkmnp-tv-z]{26}$","example":"req_01krdgeqcxet5s7t44vh8rt9mg"},"LogEntryBase":{"type":"object","description":"Fields common to audit and request log entries.","required":["request_id","occurred_at","actor","method","route","path","status"],"properties":{"request_id":{"readOnly":true,"description":"Request ID for correlation. Matches the `X-Request-Id` header returned on the original response.","$ref":"#/components/schemas/RequestID"},"idempotency_key":{"type":["string","null"],"readOnly":true,"description":"The idempotency key sent with the request, if any."},"occurred_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the request was received."},"actor":{"readOnly":true,"description":"The user, API key, or integration that made the request.","$ref":"#/components/schemas/Actor"},"method":{"type":"string","minLength":1,"readOnly":true,"description":"HTTP method of the request.","example":"POST"},"route":{"type":"string","minLength":1,"readOnly":true,"description":"The API endpoint template that handled the request.","example":"/v1/audit-log/{audit_event_id}"},"path":{"type":"string","minLength":1,"readOnly":true,"description":"The resolved request path."},"status":{"type":"integer","minimum":0,"maximum":599,"readOnly":true,"description":"HTTP status code the response returned, or 0 if no response was sent (for example, the connection was aborted).","example":200},"error_code":{"type":["string","null"],"readOnly":true,"description":"Machine-readable error code if the request failed; absent on success.","example":"E01001"},"ip":{"type":["string","null"],"readOnly":true,"description":"Client IP address that made the request."},"user_agent":{"type":["string","null"],"readOnly":true,"description":"Client `User-Agent` header from the request."}}},"AuditEventID":{"type":"string","minLength":1,"pattern":"^aud_[0-9a-hjkmnp-tv-z]{26}$","example":"aud_01krdgeqcxet5s7t44vh8rt9mg"},"AuditLogTarget":{"type":"object","additionalProperties":false,"required":["id","type"],"properties":{"id":{"type":["string","null"],"description":"ID of the affected resource. Null when the action has no single target, such as a session action, onboarding, or a create request rejected before the API assigned an ID.\n","example":"ws_01krdgeqcxet5s7t44vh8rt9mg"},"type":{"type":"string","minLength":1,"description":"Resource type, such as `workspace`, `api_key`, or `domain`.","example":"workspace"},"name":{"type":["string","null"],"readOnly":true,"description":"Human-readable display name of the target resource, captured at the time of the action. Absent when the action has no identifiable target or the name could not be captured.\n"}}},"AuditLogWorkspace":{"type":"object","additionalProperties":false,"required":["id"],"description":"The workspace the action was performed in, if applicable.","properties":{"id":{"type":"string","minLength":1,"readOnly":true,"example":"ws_01krdgeqcxet5s7t44vh8rt9mg"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the workspace at the time of the action.","example":"Production"}}},"AuditLogOrg":{"type":"object","additionalProperties":false,"required":["id"],"description":"The organization the action was performed in.","properties":{"id":{"type":"string","minLength":1,"readOnly":true,"example":"org_01krdgeqcxet5s7t44vh8rt9mg"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the organization.","example":"Acme Corp"}}},"AuditLogFields":{"type":"object","description":"Audit-specific fields of a log entry.","required":["id","action","target"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AuditEventID"},"action":{"type":"string","minLength":1,"readOnly":true,"description":"The action that was performed, as a dotted resource.verb name.","example":"workspace.created"},"title":{"type":["string","null"],"readOnly":true,"description":"Human-readable description of the action, such as \"Created workspace\". Present when the action is recognized; absent for unclassified actions.\n"},"outcome":{"type":["string","null"],"readOnly":true,"description":"High-level result of the action: `succeeded` when the HTTP status code is below 400, `failed` otherwise.\n","example":"succeeded"},"target":{"readOnly":true,"description":"The resource the action acted on.","$ref":"#/components/schemas/AuditLogTarget"},"workspace":{"readOnly":true,"description":"The workspace the action was performed in. Present only for workspace-scoped actions; absent for organization-level events.\n","oneOf":[{"$ref":"#/components/schemas/AuditLogWorkspace"},{"type":"null"}]},"org":{"readOnly":true,"description":"The organization the action belongs to.","oneOf":[{"$ref":"#/components/schemas/AuditLogOrg"},{"type":"null"}]}}},"AuditLogEntry":{"type":"object","description":"A single audit log entry as returned in list responses (without bodies).","allOf":[{"$ref":"#/components/schemas/LogEntryBase"},{"$ref":"#/components/schemas/AuditLogFields"}]},"AuditLogEntryList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AuditLogEntry"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AuditLogExport":{"allOf":[{"type":"object","description":"A page of audit events projected into the requested SIEM export format.","required":["data"],"properties":{"data":{"type":"array","description":"The audit events for this page, each rendered as a single event in the requested export format. In OCSF format every event is a self-describing object whose schema is determined by its OCSF class (API Activity for most control-plane changes, an Identity & Access Management class for authentication and identity changes).\n","items":{"type":"object","additionalProperties":true}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"LogEntryBodies":{"type":"object","description":"Captured request and response bodies. Returned only on the single-entry endpoints. List responses omit them.\n","properties":{"request_body":{"readOnly":true,"description":"The request body, with oversized values truncated and sensitive fields redacted.","type":["object","null"],"additionalProperties":true},"response_body":{"readOnly":true,"description":"The response body, with oversized values truncated and sensitive fields redacted.","type":["object","null"],"additionalProperties":true}}},"AuditLogEntryDetail":{"type":"object","description":"A single audit log entry with the captured request and response bodies.","allOf":[{"$ref":"#/components/schemas/LogEntryBase"},{"$ref":"#/components/schemas/AuditLogFields"},{"$ref":"#/components/schemas/LogEntryBodies"}]},"RequestLogFields":{"type":"object","description":"Request-log-specific fields of a log entry.","required":["id"],"properties":{"id":{"readOnly":true,"description":"Request ID. Equals `request_id`; the value returned in the `X-Request-Id` response header.","$ref":"#/components/schemas/RequestID"},"query":{"type":["string","null"],"readOnly":true,"description":"The request query string."},"duration_ms":{"type":"integer","readOnly":true,"description":"Time taken to handle the request, in milliseconds."}}},"RequestLogEntry":{"type":"object","description":"A single request log entry as returned in list responses (without bodies).","allOf":[{"$ref":"#/components/schemas/LogEntryBase"},{"$ref":"#/components/schemas/RequestLogFields"}]},"RequestLogEntryList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RequestLogEntry"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"RequestLogHistogramPoint":{"type":"object","additionalProperties":false,"required":["occurred_at","total","errors"],"properties":{"occurred_at":{"type":"string","format":"date-time","minLength":1,"description":"Start of the five-minute bucket (UTC)."},"total":{"type":"integer","description":"Total requests in this bucket."},"errors":{"type":"integer","description":"Requests with HTTP status ≥ `400` or no response (status 0) in this bucket."}}},"RequestLogHistogramResponse":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RequestLogHistogramPoint"}}}},"RequestLogBodyMeta":{"type":"object","description":"Size and content-type metadata for the captured request and response bodies. Returned only when fetching a single request log entry.\n","properties":{"request_bytes":{"type":"integer","readOnly":true,"description":"Size of the original request body, in bytes."},"response_bytes":{"type":"integer","readOnly":true,"description":"Size of the original response body, in bytes."},"request_truncated":{"type":"boolean","readOnly":true,"description":"Whether the stored request body was truncated to fit size limits."},"response_truncated":{"type":"boolean","readOnly":true,"description":"Whether the stored response body was truncated to fit size limits."},"request_content_type":{"type":["string","null"],"readOnly":true,"description":"Value of the request body's `Content-Type` header."},"response_content_type":{"type":["string","null"],"readOnly":true,"description":"Value of the response body's `Content-Type` header."}}},"RequestLogEntryDetail":{"type":"object","description":"A single request log entry with the captured request and response bodies.","allOf":[{"$ref":"#/components/schemas/LogEntryBase"},{"$ref":"#/components/schemas/RequestLogFields"},{"$ref":"#/components/schemas/LogEntryBodies"},{"$ref":"#/components/schemas/RequestLogBodyMeta"}]},"AccessRestrictionID":{"type":"string","minLength":1,"pattern":"^ar_[0-9a-hjkmnp-tv-z]{26}$","example":"ar_01krdgeqcxet5s7t44vh8rt9mg"},"AccessRestrictionAppliesTo":{"type":"object","additionalProperties":false,"description":"Targeting selector for the rule. A rule fires only when the incoming request's scope, level, and principal type match. Empty arrays mean \"match all\" for that dimension.","properties":{"scopes":{"type":"array","description":"Scope names the rule applies to. Empty means all scopes.","items":{"type":"string","minLength":1}},"levels":{"type":"array","description":"Access levels the rule applies to, such as `read` or `write`. Empty means all levels.","items":{"type":"string","minLength":1}},"principal_types":{"type":"array","description":"Principal types the rule applies to. Empty means all principal types.","items":{"type":"string","enum":["user","api_key"]}}}},"AccessRestrictionIPAllowlistParameters":{"type":"object","additionalProperties":false,"required":["type","cidrs"],"properties":{"type":{"type":"string","minLength":1,"enum":["ip_allowlist"],"description":"Discriminator field identifying the parameters type."},"cidrs":{"type":"array","items":{"type":"string","minLength":1,"description":"A CIDR range in standard notation, such as `203.0.113.0/24` or `2001:db8::/32`. Supports IPv4 and IPv6."},"minItems":1,"maxItems":1000,"description":"Source IP ranges allowed to make requests. Requests from other source IPs are blocked. Include the originating client IP that reaches the API.\n"},"on_missing_source_ip":{"type":"string","enum":["match","skip"],"default":"skip","description":"Behavior when the source IP cannot be determined. `skip` (default) does not apply the rule. `match` treats the unknown IP as outside the allowlist and denies the request.\n"}}},"AccessRestrictionEffect":{"type":"string","minLength":1,"enum":["deny","allow"],"description":"What happens when the rule matches. `deny` blocks the request. `allow` permits it (useful for allowlist exceptions).\n"},"AccessRestrictionMode":{"type":"string","minLength":1,"enum":["enforcing","shadow","disabled"],"description":"Evaluation behavior.\n\n- `enforcing`: actively allows or denies requests.\n- `shadow`: logs and observes but never blocks.\n- `disabled`: deactivates the rule without deleting it.\n"},"AccessRestriction":{"type":"object","additionalProperties":false,"required":["id","organization_id","name","rule_type","applies_to","parameters","effect","mode","priority","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AccessRestrictionID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}],"description":"Workspace this rule is scoped to. Null means the rule applies org-wide."},"name":{"type":"string","minLength":1,"maxLength":200,"description":"Human-readable name for this restriction."},"description":{"type":"string","maxLength":2000,"description":"Optional description of the restriction's purpose."},"rule_type":{"type":"string","minLength":1,"enum":["ip_allowlist"],"description":"The kind of access rule. Currently only IP allowlists are supported."},"applies_to":{"$ref":"#/components/schemas/AccessRestrictionAppliesTo"},"parameters":{"description":"Rule-specific parameters. Shape is determined by rule_type.","oneOf":[{"$ref":"#/components/schemas/AccessRestrictionIPAllowlistParameters"}],"discriminator":{"propertyName":"type","mapping":{"ip_allowlist":"#/components/schemas/AccessRestrictionIPAllowlistParameters"}}},"effect":{"$ref":"#/components/schemas/AccessRestrictionEffect"},"mode":{"$ref":"#/components/schemas/AccessRestrictionMode"},"priority":{"type":"integer","description":"Sort order, applied when rules are listed and loaded. Higher values come first. Default 0."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the restriction automatically deactivates. Null means no expiry."},"effective_at":{"type":["string","null"],"format":"date-time","description":"When the restriction becomes active. Null means immediately active."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AccessRestrictionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AccessRestriction"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AccessRestrictionCreate":{"type":"object","additionalProperties":false,"required":["name","rule_type","applies_to","parameters"],"properties":{"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"Workspace to scope this restriction to. Omit for an org-wide restriction."},"name":{"type":"string","minLength":1,"maxLength":200,"description":"Human-readable name for this restriction."},"description":{"type":"string","maxLength":2000,"description":"Optional description of the restriction's purpose."},"rule_type":{"type":"string","minLength":1,"enum":["ip_allowlist"],"description":"The kind of access rule. Currently only IP allowlists are supported."},"applies_to":{"$ref":"#/components/schemas/AccessRestrictionAppliesTo"},"parameters":{"description":"Rule-specific parameters. Shape is determined by rule_type.","oneOf":[{"$ref":"#/components/schemas/AccessRestrictionIPAllowlistParameters"}],"discriminator":{"propertyName":"type","mapping":{"ip_allowlist":"#/components/schemas/AccessRestrictionIPAllowlistParameters"}}},"effect":{"allOf":[{"$ref":"#/components/schemas/AccessRestrictionEffect"}],"default":"deny","description":"What happens when the rule matches. Defaults to deny."},"mode":{"allOf":[{"$ref":"#/components/schemas/AccessRestrictionMode"}],"default":"enforcing","description":"Evaluation behavior. Defaults to `enforcing`."},"priority":{"type":"integer","default":0,"description":"Sort order, applied when rules are listed and loaded. Higher values come first."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When this restriction automatically deactivates."},"effective_at":{"type":["string","null"],"format":"date-time","description":"When this restriction becomes active. Defaults to immediately."}}},"AccessRestrictionUpdate":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Human-readable name for this restriction."},"description":{"type":"string","maxLength":2000,"description":"Optional description of the restriction's purpose."},"applies_to":{"$ref":"#/components/schemas/AccessRestrictionAppliesTo"},"parameters":{"description":"Rule-specific parameters. Shape is determined by rule_type.","oneOf":[{"$ref":"#/components/schemas/AccessRestrictionIPAllowlistParameters"}],"discriminator":{"propertyName":"type","mapping":{"ip_allowlist":"#/components/schemas/AccessRestrictionIPAllowlistParameters"}}},"effect":{"$ref":"#/components/schemas/AccessRestrictionEffect"},"mode":{"$ref":"#/components/schemas/AccessRestrictionMode"},"priority":{"type":"integer","description":"Sort order, applied when rules are listed and loaded. Higher values come first."},"expires_at":{"type":["string","null"],"format":"date-time","description":"Set the expiry timestamp. Null clears the expiry so the restriction never expires."},"effective_at":{"type":["string","null"],"format":"date-time","description":"Set when the restriction becomes active. Null clears the effective_at so the restriction is active immediately."}}},"SSOSettings":{"type":"object","additionalProperties":false,"description":"The organization's single sign-on settings: the endpoints an administrator registers with their identity provider, and whether single sign-on is required. The endpoints are readable before any connection exists, which is the point: an OIDC application cannot be created without a redirect URI, and a Bird connection cannot be created without that application's client credentials.\n","required":["oidc_callback_url","sso_required"],"properties":{"oidc_callback_url":{"type":"string","minLength":1,"readOnly":true,"description":"The redirect URI (OAuth `redirect_uri`) to register with an OpenID Connect identity provider. One value for every connection you create, so it only has to be registered once.\n","example":"https://bird.com/dashboard/auth/sso/oidc/callback"},"sso_required":{"type":"boolean","readOnly":true,"description":"Whether members other than the owner must have signed in through one of the organization's active connections to access it. Any active connection satisfies it.\n"}}},"SSOEnforcementUpdate":{"type":"object","additionalProperties":false,"description":"Turn the organization's single sign-on requirement on or off.","required":["sso_required"],"properties":{"sso_required":{"type":"boolean","description":"Whether members other than the owner must sign in through one of the organization's identity providers to access it.\n"}}},"SSODomainStatus":{"type":"string","minLength":1,"enum":["pending","verified","suspended"],"description":"Verification state of a domain claim.\n\n- `pending`: the DNS TXT record has not yet been found.\n- `verified`: the DNS TXT record has been found.\n- `suspended`: a previously verified record later disappeared, which also stops\n  the domain from letting anyone sign in.\n"},"SSODomainDNSRecord":{"type":"object","additionalProperties":false,"required":["type","name","value"],"readOnly":true,"description":"The DNS TXT record to publish to prove ownership of the domain.","properties":{"type":{"type":"string","minLength":1,"readOnly":true,"description":"DNS record type to create. Always TXT.","example":"TXT"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"The record name (host) to create.","example":"_bird-sso-verification.acme.com"},"value":{"type":"string","minLength":1,"readOnly":true,"description":"The exact record value to publish.","example":"bird-sso-verification=Zm9vYmFyYmF6cXV4MTIzNDU2Nzg5MA"}}},"SSOVerifiedDomain":{"type":"object","additionalProperties":false,"description":"A domain claimed for single sign-on, and the DNS record that proves the claim. Only a verified claim is exclusive: while your organization holds one, no other organization can verify the same domain.\n","required":["id","domain","status","dns_record","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid","minLength":1,"readOnly":true,"description":"Identifier of the verified-domain record."},"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The domain being verified for single sign-on.","example":"acme.com"},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SSODomainStatus"}]},"dns_record":{"$ref":"#/components/schemas/SSODomainDNSRecord"},"verified_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the claim was last verified. Later rechecks do not move it, and a suspended claim keeps the timestamp of the verification it lost, so a value here does not mean the domain is verified now: read `status` for that. Cleared only when the verification token is rotated, which returns the claim to pending. Null while a claim has never been verified.\n"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"SSOVerifiedDomainList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The organization's SSO verified-domain records.","items":{"$ref":"#/components/schemas/SSOVerifiedDomain"}}}},"SSOVerifiedDomainCreate":{"type":"object","additionalProperties":false,"required":["domain"],"properties":{"domain":{"type":"string","minLength":1,"maxLength":253,"description":"Domain to verify for single sign-on, such as `acme.com`.","example":"acme.com"}}},"SSOConnectionStatus":{"type":"string","minLength":1,"enum":["draft","active","suspended"],"description":"Lifecycle state of a connection.\n\n- `draft`: configured but not yet serving logins.\n- `active`: serves logins, and requires your organization to hold at least one\n  verified domain.\n- `suspended`: retained but refuses logins.\n"},"SSOConnectionOIDCConfig":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Non-secret OIDC configuration for a connection. The client secret is never returned.","required":["discovery_url","issuer","client_id","client_secret_set","subject_claim","email_claim","token_endpoint_auth_method","initiate_login_url"],"properties":{"initiate_login_url":{"type":"string","minLength":1,"readOnly":true,"description":"Where a browser starts a sign-in for this connection. OpenID Connect has no identity-provider-initiated sign-in of its own, so a click on an app tile has to arrive here and begin an ordinary sign-in. Register it as the login-initiation URL on this connection's application.\n"},"discovery_url":{"type":"string","minLength":1,"readOnly":true,"description":"The identity provider's OpenID discovery document URL."},"issuer":{"type":"string","minLength":1,"readOnly":true,"description":"The issuer resolved from the discovery document."},"client_id":{"type":"string","minLength":1,"readOnly":true,"description":"The OAuth client ID registered with the identity provider."},"client_secret_set":{"type":"boolean","readOnly":true,"description":"Whether a client secret is stored. The secret itself is never returned."},"subject_claim":{"type":"string","minLength":1,"readOnly":true,"description":"The ID-token claim used as the stable user identifier."},"email_claim":{"type":"string","minLength":1,"readOnly":true,"description":"The ID-token claim used as the user's email address."},"extra_scopes":{"type":"array","readOnly":true,"description":"Scopes requested beyond the default \"openid email\".","items":{"type":"string","minLength":1}},"token_endpoint_auth_method":{"type":"string","minLength":1,"readOnly":true,"description":"The negotiated client-authentication method at the token endpoint."}}},"SSONameIDFormat":{"type":"string","minLength":1,"enum":["email","persistent"],"description":"SAML NameID format used as the member's permanent identity key. Assertions\nwith a different format are rejected. Omitting it resolves the format from your\nidentity provider's metadata, in one order: `persistent` if the metadata advertises\nit, otherwise `email` if it advertises that, otherwise `persistent`. So a provider\nadvertising both resolves to `persistent`, and configuring a connection by hand\nadvertises nothing and also resolves to `persistent`. Send the value explicitly if\nyour provider asserts an email NameID.\n\n- `persistent`: Uses an opaque, immutable identifier. Prefer this when your\n  identity provider supports it. Email-shaped persistent IDs are rejected.\n- `email`: Uses the email address. Reassigning an address can transfer access\n  to the account and roles associated with that address.\n"},"SSOConnectionSAMLConfig":{"type":"object","additionalProperties":false,"readOnly":true,"description":"SAML configuration for a connection, resolved from metadata and any directly provided fields.","required":["idp_entity_id","idp_sso_url","preferred_nameid_format","certificate_count"],"properties":{"idp_metadata_url":{"type":["string","null"],"readOnly":true,"description":"The identity provider's SAML metadata document URL, if configured."},"idp_metadata_last_fetched_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the metadata document was last read from that URL."},"idp_entity_id":{"type":"string","minLength":1,"readOnly":true,"description":"The identity provider's entity ID."},"idp_sso_url":{"type":"string","minLength":1,"readOnly":true,"description":"The identity provider's single sign-on URL."},"preferred_nameid_format":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SSONameIDFormat"}]},"certificate_count":{"type":"integer","readOnly":true,"description":"The number of identity-provider signing certificates configured."},"certificate_expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The earliest expiry across the configured signing certificates."}}},"SSOConnectionSAMLSP":{"type":"object","additionalProperties":false,"description":"The values to register with your identity provider for this connection. Every one is derived from the connection's own ID and none depends on the identity provider, so they are present from the moment the connection exists.\n\nThat is the point of them: a SAML connection can be created before its identity provider is configured, and registering these there is how you configure it.\n","required":["sp_entity_id","acs_url","sp_metadata_url","initiate_login_url"],"properties":{"sp_entity_id":{"type":"string","minLength":1,"readOnly":true,"description":"Service-provider entity ID for this connection. Configure it as the audience at your identity provider."},"acs_url":{"type":"string","minLength":1,"readOnly":true,"description":"Assertion Consumer Service URL where your identity provider posts its SAML response."},"sp_metadata_url":{"type":"string","minLength":1,"readOnly":true,"description":"Service-provider metadata document for this connection. Identity providers that support metadata import can read the entity ID and Assertion Consumer Service URL from it."},"initiate_login_url":{"type":"string","minLength":1,"readOnly":true,"description":"Where a browser starts a sign-in for this connection. A click on an app tile posts to the Assertion Consumer Service URL instead of arriving here. Use this URL wherever your identity provider asks for a sign-on URL. It also provides a link that takes members directly to this connection's sign-in.\n"}}},"SSOIdPConnection":{"type":"object","additionalProperties":false,"description":"An identity-provider connection: what members see on the sign-in button, what a first sign-in grants them, and the protocol details to register at the provider. At most one protocol block is present, and a SAML connection created with `configure_later` has none until its details arrive. `saml_sp` is there from the start either way, since every value in it derives from the connection itself.\n","required":["id","protocol","status","display_name","allow_idp_initiated_saml","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid","minLength":1,"readOnly":true,"description":"Identifier of the identity-provider connection."},"protocol":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SSOProtocol"}]},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SSOConnectionStatus"}]},"display_name":{"type":"string","minLength":1,"readOnly":true,"description":"The organization name shown next to the sign-in button."},"default_jit_role":{"type":["string","null"],"readOnly":true,"description":"Role granted to a member provisioned on their first sign-in. Scoped by `default_jit_workspace_id`: a workspace role when one is set, an organization role when it is not, never both.\n"},"default_jit_workspace_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}],"description":"Workspace new users are added to on first sign-in, if any."},"allow_idp_initiated_saml":{"type":"boolean","readOnly":true,"description":"Whether members can start signing in at your identity provider, from an app tile rather than from your sign-in page. SAML only, and false on an OIDC connection, which has no identity-provider-initiated flow of its own.\n"},"oidc_config":{"$ref":"#/components/schemas/SSOConnectionOIDCConfig"},"saml_config":{"$ref":"#/components/schemas/SSOConnectionSAMLConfig"},"saml_sp":{"$ref":"#/components/schemas/SSOConnectionSAMLSP"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}},"not":{"required":["oidc_config","saml_config"],"properties":{"oidc_config":{},"saml_config":{}}}},"SSOIdPConnectionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The organization's identity-provider connections.","items":{"$ref":"#/components/schemas/SSOIdPConnection"}}}},"SSOIdPConnectionOIDCCreate":{"type":"object","additionalProperties":false,"description":"OIDC configuration for a new connection.","required":["issuer_url","client_id","client_secret"],"properties":{"issuer_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The identity provider's issuer. Bird appends `/.well-known/openid-configuration` to it and reads the OpenID discovery document from there. A bare host is accepted and assumed to be https, so an Okta tenant can be entered as `acme.okta.com`. A discovery document URL is accepted too and is reduced to the issuer it belongs to.","example":"acme.okta.com"},"client_id":{"type":"string","minLength":1,"maxLength":512,"description":"The OAuth client ID registered with the identity provider."},"client_secret":{"type":"string","minLength":1,"maxLength":512,"writeOnly":true,"x-sensitive":true,"description":"The OAuth client secret. Write-only; never returned."},"subject_claim":{"type":"string","minLength":1,"maxLength":128,"description":"ID-token claim to use as the stable user identifier. Defaults to \"sub\"."},"email_claim":{"type":"string","minLength":1,"maxLength":128,"description":"ID-token claim to use as the user's email. Defaults to \"email\"."},"extra_scopes":{"type":"array","maxItems":32,"description":"Scopes to request beyond the default \"openid email\".","items":{"type":"string","minLength":1,"maxLength":128}}}},"SSOIdPConnectionSAMLCreate":{"type":"object","additionalProperties":false,"description":"SAML configuration for a new connection. Supply the identity provider's metadata URL to resolve the entity ID, sign-in URL, and signing certificates from it; any field provided directly overrides the metadata.\n\nSet `configure_later` instead to create the connection without them. The values you have to register with your identity provider are derived from the connection's own ID, so they do not exist until the connection does. Send the details afterwards with the SAML configuration endpoint.\n","oneOf":[{"required":["idp_metadata_url"],"not":{"required":["configure_later"],"properties":{"configure_later":{"const":true}}}},{"required":["idp_entity_id","idp_sso_url","idp_x509_certs"],"not":{"required":["configure_later"],"properties":{"configure_later":{"const":true}}}},{"required":["configure_later"],"properties":{"configure_later":{"const":true}},"not":{"anyOf":[{"required":["preferred_nameid_format"],"properties":{"preferred_nameid_format":{}}},{"required":["idp_metadata_url"],"properties":{"idp_metadata_url":{}}},{"required":["idp_entity_id"],"properties":{"idp_entity_id":{}}},{"required":["idp_sso_url"],"properties":{"idp_sso_url":{}}},{"required":["idp_x509_certs"],"properties":{"idp_x509_certs":{}}}]}}],"properties":{"configure_later":{"type":"boolean","description":"Create the connection without identity-provider details. Its entity ID and Assertion Consumer Service URL become available immediately, to register with your identity provider; the connection stays a draft, serves no sign-ins, and cannot be activated until the details arrive.\n","example":true},"idp_metadata_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The identity provider's SAML metadata document URL. The service fetches and parses it.","example":"https://idp.example.com/saml/metadata"},"idp_entity_id":{"type":"string","minLength":1,"maxLength":1024,"description":"The identity provider's entity ID. Required when no metadata URL is provided.","example":"https://idp.example.com"},"idp_sso_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The identity provider's single sign-on URL. Required when no metadata URL is provided."},"idp_x509_certs":{"type":"array","minItems":1,"maxItems":5,"description":"One to five PEM-encoded signing certificates. Required when no metadata URL is provided.","items":{"type":"string","minLength":1,"maxLength":8192}},"preferred_nameid_format":{"allOf":[{"$ref":"#/components/schemas/SSONameIDFormat"}],"description":"Overrides the format the identity provider's metadata advertises. Not accepted with `configure_later`: the format is stored on the configuration row, which that branch does not create, so send it with the details instead.\n"},"signed_requests":{"type":"boolean","description":"Whether authentication requests are signed. `true` is rejected. Omit this field and configure your identity provider not to require a signed `AuthnRequest`.\n"},"allow_idp_initiated_saml":{"type":"boolean","description":"Whether members can start signing in at your identity provider, from an app tile rather than from the sign-in page. On unless you set it to false.\n"}}},"SSOIdPConnectionCreate":{"type":"object","additionalProperties":false,"description":"Create an identity-provider connection. Provide exactly one of oidc or saml to select the single sign-on protocol.\n","required":["display_name","default_jit_role"],"properties":{"display_name":{"type":"string","minLength":1,"maxLength":200,"description":"The organization name shown next to the sign-in button."},"default_jit_role":{"type":"string","minLength":1,"description":"Role granted to users provisioned on their first sign-in. Required: a connection that grants nothing authenticates people into an organization they then have no access to, which needs a second, manual invitation to undo. Scoped by default_jit_workspace_id: a workspace role when one is set, an organization role when it is not. Granted only on first provisioning, and never over an existing grant, so it cannot change the access of someone who is already a member.\n"},"default_jit_workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"Workspace to add new users to on first sign-in."},"oidc":{"$ref":"#/components/schemas/SSOIdPConnectionOIDCCreate"},"saml":{"$ref":"#/components/schemas/SSOIdPConnectionSAMLCreate"}},"oneOf":[{"required":["oidc"]},{"required":["saml"]}]},"SSOIdPConnectionUpdate":{"type":"object","additionalProperties":false,"description":"Partially update an SSO connection's branding and JIT settings. Omit a field to leave it unchanged.","properties":{"display_name":{"type":"string","minLength":1,"maxLength":200,"description":"The organization name shown next to the sign-in button."},"default_jit_role":{"type":"string","minLength":1,"description":"Role granted to a member provisioned on their first sign-in. Scoped by `default_jit_workspace_id`: a workspace role when one is set, an organization role when it is not, never both. Granted only on first provisioning, so it cannot change the access of someone who is already a member.\n"},"default_jit_workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"Workspace to add new users to on first sign-in."},"allow_idp_initiated_saml":{"type":"boolean","description":"Whether members can start signing in at your identity provider, from an app tile rather than from your sign-in page. SAML only: sending it for an OIDC connection is refused rather than stored, because nothing would read it.\n"},"preferred_nameid_format":{"allOf":[{"$ref":"#/components/schemas/SSONameIDFormat"}],"description":"The NameID format this connection expects (SAML connections only). Set this\nto correct a connection that nobody can sign in through. The API refuses an\nassertion whose format does not match. A connection carrying the wrong value\nhas signed nobody in, so correcting it costs nothing.\n\nOnce members have signed in through the connection the format is fixed:\nresending the current value is accepted, and changing it is refused. Their\nidentities are keyed on the identifier the old format sent, and nothing the\nnew format sends would match them. Create a connection for the new format\ninstead.\n"}}},"SSOConnectionSAMLConfigure":{"type":"object","additionalProperties":false,"description":"The identity provider's details for a SAML connection created without them. Supply exactly one of a metadata URL, the metadata document itself, or the entity ID with the sign-in URL and signing certificates.\n\nAll three are validated identically, so a connection configured one way behaves the same as one configured another.\n","oneOf":[{"required":["idp_metadata_url"],"not":{"anyOf":[{"required":["idp_metadata_xml"],"properties":{"idp_metadata_xml":{}}},{"required":["idp_entity_id"],"properties":{"idp_entity_id":{}}}]}},{"required":["idp_metadata_xml"],"not":{"anyOf":[{"required":["idp_metadata_url"],"properties":{"idp_metadata_url":{}}},{"required":["idp_entity_id"],"properties":{"idp_entity_id":{}}}]}},{"required":["idp_entity_id","idp_sso_url","idp_x509_certs"],"not":{"anyOf":[{"required":["idp_metadata_url"],"properties":{"idp_metadata_url":{}}},{"required":["idp_metadata_xml"],"properties":{"idp_metadata_xml":{}}}]}}],"properties":{"idp_metadata_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The identity provider's SAML metadata document URL. The service fetches and parses it.","example":"https://idp.example.com/saml/metadata"},"idp_metadata_xml":{"type":"string","minLength":1,"maxLength":1048576,"x-sensitive":true,"description":"The identity provider's SAML metadata document, for a provider that hands out a file rather than hosting it. Parsed for the entity ID, sign-in URL and signing certificates; the document itself is not stored.\n"},"idp_entity_id":{"type":"string","minLength":1,"maxLength":1024,"description":"The identity provider's entity ID. Required when no metadata is provided.","example":"https://idp.example.com"},"idp_sso_url":{"type":"string","minLength":1,"maxLength":2048,"description":"The identity provider's single sign-on URL. Required when no metadata is provided."},"idp_x509_certs":{"type":"array","minItems":1,"maxItems":5,"description":"One to five PEM-encoded signing certificates. Required when no metadata is provided.","items":{"type":"string","minLength":1,"maxLength":8192}},"preferred_nameid_format":{"allOf":[{"$ref":"#/components/schemas/SSONameIDFormat"}]}}},"SSOConnectionTestStart":{"type":"object","additionalProperties":false,"description":"Where to send a browser to run a connection test.","required":["start_url"],"properties":{"start_url":{"type":"string","minLength":1,"readOnly":true,"description":"Open this URL in a browser window to run the test. It sends the browser through a real sign-in at your identity provider and back, and reports what the identity provider returned. Single-use, and it expires shortly after it is issued.\n"}}},"SSOConnectionStatusTarget":{"type":"string","minLength":1,"enum":["active","suspended"],"description":"The status to move a connection to. A deliberate subset of the connection's own status values: `draft` is only ever the state a connection is created in, and deleting is its own operation, so neither can be set here.\n"},"SSOConnectionStatusUpdate":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"allOf":[{"$ref":"#/components/schemas/SSOConnectionStatusTarget"}]}}},"SSOConnectionCredentialRotation":{"type":"object","additionalProperties":false,"description":"New credentials for an existing connection. Provide the field that matches the connection's protocol: client_secret for OIDC, idp_x509_certs for SAML.\n","properties":{"client_secret":{"type":"string","minLength":1,"maxLength":512,"writeOnly":true,"x-sensitive":true,"description":"The new OAuth client secret issued by your identity provider. Write-only; never returned."},"idp_x509_certs":{"type":"array","minItems":1,"maxItems":5,"description":"The identity provider's new PEM-encoded signing certificates, replacing the current set.","items":{"type":"string","minLength":1,"maxLength":8192}}},"oneOf":[{"required":["client_secret"]},{"required":["idp_x509_certs"]}]},"TrustDisplayStatus":{"type":"string","minLength":1,"description":"Where a trust requirement stands.\n\n- `verified`: the requirement is met.\n- `in_progress`: a verification is being reviewed.\n- `action_required`: you need to complete a step.\n- `under_review`: the requirement is being assessed and needs no action from you.\n","enum":["verified","in_progress","action_required","under_review"]},"TrustGateUnlock":{"type":"object","additionalProperties":false,"description":"A capability that this trust requirement unlocks once it is satisfied.","required":["action","label"],"properties":{"action":{"type":"string","minLength":1,"readOnly":true,"description":"Stable key for the unlocked capability. Use it to link directly to the capability and display `label` to customers.\n","example":"buy_number"},"label":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable name of the unlocked capability.","example":"Buy a phone number"}}},"TrustGateRemediation":{"type":"object","additionalProperties":false,"description":"What happens next for a trust requirement that is not yet verified. This may be a step you take or a review you wait on. Present for every non-verified state.\n","required":["kind"],"properties":{"kind":{"type":"string","minLength":1,"readOnly":true,"description":"What resolves this requirement: completing a hosted verification, running a built-in verification flow, or waiting for a review (no action from you).\n","x-extensible-enum":["hosted_verification","verify_flow","under_review"]}}},"TrustGate":{"type":"object","additionalProperties":false,"description":"A trust requirement and its current state for your organization.","required":["slug","name","display_status","unlocks","remediation"],"properties":{"slug":{"type":"string","minLength":1,"readOnly":true,"description":"Stable identifier for the trust requirement.","example":"identity_verified"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable name of the trust requirement.","example":"Identity verified"},"display_status":{"allOf":[{"$ref":"#/components/schemas/TrustDisplayStatus"}],"readOnly":true},"unlocks":{"type":"array","readOnly":true,"description":"The capabilities this requirement unlocks once it is satisfied.","items":{"$ref":"#/components/schemas/TrustGateUnlock"}},"remediation":{"readOnly":true,"description":"What happens next while the requirement is not yet verified: a step you take or a review you wait on. Present for every non-verified state; null only once the requirement is verified.\n","oneOf":[{"$ref":"#/components/schemas/TrustGateRemediation"},{"type":"null"}]}}},"TrustGateList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Your organization's trust requirements, each with its current state.","items":{"$ref":"#/components/schemas/TrustGate"}}}},"EmailVerificationStart":{"type":"object","additionalProperties":false,"description":"Destination for a requirement verified by email.","required":["email"],"properties":{"email":{"type":"string","format":"email","minLength":1,"description":"The email address to send the one-time code to.","example":"alex@example.com"}}},"PhoneVerificationStart":{"type":"object","additionalProperties":false,"description":"Destination for a requirement verified by phone.","required":["phone_number"],"properties":{"phone_number":{"type":"string","minLength":1,"description":"The phone number, in E.164 format, to send the one-time code to.","example":"+31612345678"}}},"TrustVerificationStart":{"description":"Optional input for starting a verification. Only used for a requirement verified with a one-time code, where it names the destination the code is sent to; ignored for a provider-hosted verification. The requirement determines which variant applies.\n","oneOf":[{"$ref":"#/components/schemas/EmailVerificationStart"},{"$ref":"#/components/schemas/PhoneVerificationStart"}]},"TrustVerificationLaunch":{"type":"object","additionalProperties":false,"description":"A started verification flow. A requirement verified through a provider-hosted flow returns a hosted URL to redirect the customer to; a requirement verified with a one-time code returns its destination and, when known, delivery channel.\n","required":["expires_at"],"properties":{"hosted_url":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The hosted URL where the customer completes the verification. Null for a requirement verified with a one-time code.\n","example":"https://withpersona.com/verify?inquiry-id=inq_ABC123"},"channel":{"$ref":"#/components/schemas/VerificationChannel","readOnly":true,"description":"The channel a one-time code was sent over. Omitted for a provider-hosted verification or while the delivery channel is not yet known.\n"},"sent_to":{"type":"string","readOnly":true,"description":"For a requirement verified with a one-time code, a masked version of where it was sent (for example j•••@example.com). Omitted for a provider-hosted verification.\n"},"expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the hosted URL or one-time code expires. Null when it does not expire.","example":"2026-07-07T12:00:00Z"}}},"TrustVerificationCheck":{"type":"object","additionalProperties":false,"description":"The one-time code sent to complete a verification.","required":["code"],"properties":{"code":{"type":"string","minLength":1,"x-sensitive":true,"description":"The one-time code you received.","example":"493028"}}},"TrustAction":{"type":"string","minLength":1,"description":"An action whose trust requirements are enforced.\n\n- `buy_number`: allocate a dedicated phone number.\n- `register_sender_id`: submit a sender for a country's registration program.\n- `add_sender_id`: claim an alphanumeric sender.\n- `register_verified_number`: register a phone number as a verified caller ID.\n\nRead-only and open: an action is added here whenever a new capability starts\nrequiring verified standing, so treat an unlisted value as valid rather than\nrejecting it.\n","x-extensible-enum":["buy_number","register_sender_id","add_sender_id","register_verified_number"],"example":"buy_number"},"TrustActionGate":{"type":"object","additionalProperties":false,"description":"One trust requirement an action depends on, and where it stands for your organization.","required":["slug","name","satisfied","display_status","remediation"],"properties":{"slug":{"type":"string","minLength":1,"readOnly":true,"description":"Stable identifier for the trust requirement.","example":"identity_verified"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable name of the trust requirement.","example":"Identity verified"},"satisfied":{"type":"boolean","readOnly":true,"description":"Whether this requirement is met. While it is `false` the action is refused, whatever the requirement's display status says.\n"},"display_status":{"allOf":[{"$ref":"#/components/schemas/TrustDisplayStatus"}],"readOnly":true},"remediation":{"readOnly":true,"description":"What happens next while the requirement is not yet verified: a step you take or a review you wait on. Present for every non-verified state; null only once the requirement is verified.\n","oneOf":[{"$ref":"#/components/schemas/TrustGateRemediation"},{"type":"null"}]}}},"TrustActionRequirements":{"type":"object","additionalProperties":false,"description":"What an action requires of your organization, and how far you have got.","required":["action","satisfied","gates"],"properties":{"action":{"allOf":[{"$ref":"#/components/schemas/TrustAction"}],"readOnly":true,"description":"The action these requirements belong to."},"satisfied":{"type":"boolean","readOnly":true,"description":"Whether the action is permitted. `true` once every requirement below is met, and for an action that requires nothing.\n"},"gates":{"type":"array","readOnly":true,"description":"The requirements the action depends on, in the order to present them. Working through them in order is advice rather than a constraint: each can be completed independently, and all of them must be met before the action is permitted.\n","items":{"$ref":"#/components/schemas/TrustActionGate"}}}},"PlatformRoleEnum.gen":{"type":"string","minLength":1,"enum":["admin","compliance_engineer","connectivity_support","engineer","finance","product","readonly","sales","security","support","telecom_operations","voice_engineer"]},"AdminMe":{"type":"object","required":["id","email","roles"],"additionalProperties":false,"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/UserID","description":"Staff user ID."},"email":{"type":"string","format":"email","readOnly":true,"minLength":1,"description":"The staff member's email address."},"roles":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/PlatformRoleEnum.gen"}}}},"AdminWorkProfileId":{"type":"string","minLength":1,"description":"A personal Admin work profile; selecting one never changes staff permissions.","enum":["overview","sales","support","marketing","engineering","finance","voice_engineer","telecom_operations","connectivity_support","compliance_engineer","security","product","readonly"]},"AdminActiveWorkProfile":{"description":"The selected work profile, or null to use the staff role default.","oneOf":[{"$ref":"#/components/schemas/AdminWorkProfileId"},{"type":"null"}]},"AdminPinnedItems":{"type":["array","null"],"maxItems":32,"uniqueItems":true,"description":"Ordered navigation shortcut identifiers. Null uses the role defaults; an empty array keeps the selection empty.","items":{"type":"string","minLength":1,"maxLength":160}},"AdminHomeSectionId":{"type":"string","minLength":1,"description":"A supported section of the personal Admin Home.","enum":["crm_tasks","support_queue","identity","compliance","procurements","disputes","submissions","deals","approvals","campaigns","kpis","email","sms","whatsapp","voice","mrr","payg","milestones","receivables","organizations","domains","verified","invoices","users","interest","support","messages","topups"]},"AdminHomeSections":{"type":["array","null"],"maxItems":8,"uniqueItems":true,"description":"Ordered Home section identifiers. Null uses the role defaults; an empty array keeps the selection empty.","items":{"$ref":"#/components/schemas/AdminHomeSectionId"}},"AdminPreferenceProfile":{"type":"object","additionalProperties":false,"required":["id","pinned_items","home_sections"],"properties":{"id":{"$ref":"#/components/schemas/AdminWorkProfileId"},"pinned_items":{"$ref":"#/components/schemas/AdminPinnedItems"},"home_sections":{"$ref":"#/components/schemas/AdminHomeSections"}}},"AdminPreferences":{"type":"object","additionalProperties":false,"required":["active_profile","profiles"],"properties":{"active_profile":{"$ref":"#/components/schemas/AdminActiveWorkProfile"},"profiles":{"type":"array","maxItems":13,"description":"Saved work profiles for the authenticated staff account in this environment. Profiles without saved choices use their role defaults.","items":{"$ref":"#/components/schemas/AdminPreferenceProfile"}}}},"AdminPreferencesUpdate":{"type":"object","additionalProperties":false,"anyOf":[{"required":["active_profile"]},{"required":["pinned_items"]},{"required":["home_sections"]}],"dependentRequired":{"pinned_items":["profile"],"home_sections":["profile"]},"properties":{"active_profile":{"$ref":"#/components/schemas/AdminActiveWorkProfile"},"profile":{"$ref":"#/components/schemas/AdminWorkProfileId","description":"Required when updating pins or Home sections."},"pinned_items":{"$ref":"#/components/schemas/AdminPinnedItems"},"home_sections":{"$ref":"#/components/schemas/AdminHomeSections"}}},"AdminCrmObject":{"type":"string","enum":["companies","people","deals"],"description":"Attio record collection managed by the CRM workbench.","minLength":1},"AdminCrmIdentifier":{"type":"string","format":"uuid","minLength":36,"maxLength":36,"description":"External Attio identifier; this is not a Bird TypeID."},"AdminCrmAttribute":{"type":"object","additionalProperties":false,"description":"Current Attio attribute definition. Only fields that can be edited in the workbench are marked writable.","required":["slug","title","type","object_id","writable","required","multiselect","allowed_object_ids","currency_code"],"properties":{"slug":{"type":"string","description":"Attribute API slug.","minLength":1},"title":{"type":"string","description":"Attribute label.","minLength":1},"type":{"type":"string","description":"Attio attribute type.","minLength":1},"object_id":{"$ref":"#/components/schemas/AdminCrmIdentifier"},"writable":{"type":"boolean"},"required":{"type":"boolean"},"multiselect":{"type":"boolean"},"allowed_object_ids":{"type":"array","items":{"type":"string","description":"Allowed target object slug or UUID.","minLength":1}},"currency_code":{"type":["string","null"],"minLength":1,"description":"Currency defined by this Attio field; null for other types."}}},"AdminCrmAttributeList":{"type":"object","additionalProperties":false,"description":"Current non-archived attributes in Attio display order.","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmAttribute"}}}},"AdminCrmAttributeOption":{"type":"object","additionalProperties":false,"description":"An existing Attio select option or status.","required":["id","title"],"properties":{"id":{"$ref":"#/components/schemas/AdminCrmIdentifier"},"title":{"type":"string","description":"Choice label.","minLength":1}}},"AdminCrmAttributeOptionList":{"type":"object","additionalProperties":false,"description":"Available choices for an Attio field.","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmAttributeOption"}}}},"AdminCrmFieldValue":{"type":"object","additionalProperties":false,"description":"A field value from Attio, retaining its label and any explicit reference or currency.","required":["value","label","target_object","currency_code","first_name","last_name"],"properties":{"value":{"type":["string","null"],"description":"Scalar value or external identifier.","minLength":1},"label":{"type":["string","null"],"description":"Display text recorded by Attio.","minLength":1},"target_object":{"type":["string","null"],"description":"Object or actor type for a reference; null for other values.","minLength":1},"currency_code":{"type":["string","null"],"description":"ISO currency code returned by Attio; null for non-currency fields.","minLength":1},"first_name":{"type":["string","null"],"minLength":1,"maxLength":1000,"description":"Explicit personal-name component; null for other field types."},"last_name":{"type":["string","null"],"minLength":1,"maxLength":1000,"description":"Explicit personal-name component; null for other field types."}}},"AdminCrmRecordField":{"type":"object","additionalProperties":false,"description":"Values for one Attio attribute.","required":["slug","values"],"properties":{"slug":{"type":"string","description":"Attribute API slug.","minLength":1},"values":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmFieldValue"}}}},"AdminCrmRecord":{"type":"object","additionalProperties":false,"description":"Company, person or deal projected from Attio, without inferred Bird identity or commercial values.","required":["id","object","title","created_at","fields"],"properties":{"id":{"$ref":"#/components/schemas/AdminCrmIdentifier"},"object":{"$ref":"#/components/schemas/AdminCrmObject"},"title":{"type":["string","null"],"description":"Record name, or null when unnamed.","minLength":1},"created_at":{"type":"string","description":"When Attio created the record.","format":"date-time","minLength":1},"fields":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmRecordField"}}}},"AdminCrmRecordList":{"type":"object","additionalProperties":false,"description":"A bounded page from Attio. Offset pages can shift when records change; reload from the first page to refresh.","required":["data","next_offset"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmRecord"}},"next_offset":{"type":["integer","null"],"minimum":0,"maximum":100000,"description":"Offset to request next, or null when Attio returned fewer rows than requested or the next offset would exceed 100000. Reaching the cap does not mean all matching records were returned. Below the cap, a full final page may be followed by an empty page."}}},"AdminCrmFieldValueInput":{"type":"object","additionalProperties":false,"description":"One typed scalar or reference. The attribute definition determines its format.","required":["value"],"properties":{"value":{"type":"string","description":"Scalar value, ISO date/timestamp, or external identifier.","minLength":1,"maxLength":20000},"target_object":{"type":"string","description":"Target object identifier for a record reference. Must match an entry in the attribute’s allowed_object_ids; the entry may be an object UUID or collection slug.","minLength":1,"maxLength":100},"first_name":{"type":"string","minLength":0,"maxLength":1000,"description":"Explicit personal-name component; empty for other field types."},"last_name":{"type":"string","minLength":0,"maxLength":1000,"description":"Explicit personal-name component; empty for other field types."}}},"AdminCrmRecordFieldInput":{"type":"object","additionalProperties":false,"description":"Replace the values of one editable field. Empty values clear an optional field.","required":["slug","values"],"properties":{"slug":{"type":"string","description":"Current attribute API slug.","minLength":1,"maxLength":200},"values":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmFieldValueInput"},"maxItems":50}}},"AdminCrmRecordWrite":{"type":"object","additionalProperties":false,"description":"Write only the specified fields. Omitted fields stay unchanged. Required fields must be supplied when creating a record.","required":["fields"],"properties":{"fields":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmRecordFieldInput"},"minItems":1,"maxItems":50}}},"AdminCrmNote":{"type":"object","additionalProperties":false,"description":"Plain text note recorded on an Attio record.","required":["id","title","content","created_at"],"properties":{"id":{"$ref":"#/components/schemas/AdminCrmIdentifier"},"title":{"type":["string","null"],"minLength":1,"description":"Note title, or null when Attio returns an empty title."},"content":{"type":["string","null"],"minLength":1,"description":"Plain text note content, or null when empty."},"created_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminCrmNoteList":{"type":"object","additionalProperties":false,"description":"A bounded page from Attio. Offset pages can shift when records change; reload from the first page to refresh.","required":["data","next_offset"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmNote"}},"next_offset":{"type":["integer","null"],"minimum":0,"maximum":50000,"description":"Offset to request next, or null when Attio returned fewer rows than requested or the next offset would exceed 50000. Reaching the cap does not mean all matching records were returned. Below the cap, a full final page may be followed by an empty page."}}},"AdminCrmNoteCreate":{"type":"object","additionalProperties":false,"description":"Add a plain text note to an Attio record.","required":["title","content"],"properties":{"title":{"type":"string","minLength":1,"maxLength":200},"content":{"type":"string","minLength":1,"maxLength":20000}}},"AdminCrmConnectionState":{"type":"string","minLength":1,"description":"available means Attio responded; unconfigured means no server credential; unavailable means Attio could not be reached or authorized.","enum":["available","unconfigured","unavailable"]},"AdminCrmStatus":{"type":"object","additionalProperties":false,"description":"Connection and standard company schema available to the staff CRM workbench.","required":["is_demo","state","message","company_object_id","company_fields","unmapped_fields"],"properties":{"is_demo":{"type":"boolean","description":"True only for the disposable local fixture, so the workbench can label sample data. Always false in production."},"state":{"$ref":"#/components/schemas/AdminCrmConnectionState"},"message":{"type":"string","minLength":1},"company_object_id":{"type":["string","null"],"minLength":1,"description":"External Attio companies object identifier, when schema discovery succeeds."},"company_fields":{"type":"array","items":{"type":"string","minLength":1},"description":"Supported standard fields confirmed by Attio schema discovery."},"unmapped_fields":{"type":"array","items":{"type":"string","minLength":1},"description":"Commercial fields with no approved company mapping; they are not inferred from other attributes."}}},"AdminCrmCompany":{"type":"object","additionalProperties":false,"description":"Projected standard Attio company fields.","required":["id","name","domains","description","created_at"],"properties":{"id":{"$ref":"#/components/schemas/AdminCrmIdentifier"},"name":{"type":["string","null"],"minLength":0,"description":"Company name recorded in Attio, or null when absent."},"domains":{"type":"array","items":{"type":"string","minLength":0},"description":"Domains recorded on the company; these do not establish a Bird organization link."},"description":{"type":["string","null"],"minLength":0,"description":"Company description recorded in Attio, or null when absent."},"created_at":{"type":"string","minLength":1,"format":"date-time","description":"Time Attio created the company record."}}},"AdminCrmCompanyList":{"type":"object","additionalProperties":false,"description":"A bounded page from Attio. Offset pages can shift when records change; reload from the first page to refresh.","required":["data","next_offset"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmCompany"}},"next_offset":{"type":["integer","null"],"minimum":0,"maximum":100000,"description":"Offset to request next, or null when Attio returned fewer rows than requested or the next offset would exceed 100000. Reaching the cap does not mean all matching records were returned. Below the cap, a full final page may be followed by an empty page."}}},"AdminCrmOrganizationLink":{"type":"object","additionalProperties":false,"description":"A Bird organization identifier explicitly recorded in a related Attio Workspace record.","required":["organization_id","attio_workspace_record_id"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"attio_workspace_record_id":{"$ref":"#/components/schemas/AdminCrmIdentifier"}}},"AdminCrmOrganizationLinkList":{"type":"object","additionalProperties":false,"description":"A bounded page from Attio. Offset pages can shift when records change; reload from the first page to refresh.","required":["data","next_offset"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmOrganizationLink"}},"next_offset":{"type":["integer","null"],"minimum":0,"maximum":100000,"description":"Offset to request next, or null when Attio returned fewer rows than requested or the next offset would exceed 100000. Reaching the cap does not mean all matching records were returned. Below the cap, a full final page may be followed by an empty page."}}},"AdminCrmRecordReference":{"type":"object","additionalProperties":false,"description":"An explicit record relation from Attio.","required":["object_id","record_id"],"properties":{"object_id":{"type":"string","minLength":1,"description":"External Attio object slug or UUID, as returned by the task relation."},"record_id":{"$ref":"#/components/schemas/AdminCrmIdentifier"}}},"AdminCrmTask":{"type":"object","additionalProperties":false,"description":"Attio task and its explicit assignees and record relations.","required":["id","content","due_at","is_completed","created_at","assignee_ids","linked_records"],"properties":{"id":{"$ref":"#/components/schemas/AdminCrmIdentifier"},"content":{"type":["string","null"],"minLength":1,"description":"Plain text task content, or null when empty."},"due_at":{"type":["string","null"],"minLength":0,"description":"Attio deadline as an ISO 8601 date or timestamp, or null when unset."},"is_completed":{"type":"boolean"},"created_at":{"type":"string","minLength":1,"format":"date-time"},"assignee_ids":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmIdentifier"},"description":"External Attio workspace member identifiers; never Bird staff identifiers."},"linked_records":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmRecordReference"}}}},"AdminCrmTaskList":{"type":"object","additionalProperties":false,"description":"A bounded page from Attio. Offset pages can shift when records change; reload from the first page to refresh.","required":["data","next_offset"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmTask"}},"next_offset":{"type":["integer","null"],"minimum":0,"maximum":100000,"description":"Offset to request next, or null when Attio returned fewer rows than requested or the next offset would exceed 100000. Reaching the cap does not mean all matching records were returned. Below the cap, a full final page may be followed by an empty page."}}},"AdminCrmTaskCreate":{"type":"object","additionalProperties":false,"description":"Create an Attio follow-up with explicit assignees and record links.","required":["content","due_at","assignee_ids","linked_records"],"properties":{"content":{"type":"string","minLength":1,"maxLength":2000,"description":"Plain text describing the next step."},"due_at":{"type":["string","null"],"maxLength":40,"description":"ISO 8601 date or timestamp, or null for no deadline."},"assignee_ids":{"type":"array","maxItems":50,"uniqueItems":true,"items":{"$ref":"#/components/schemas/AdminCrmIdentifier"}},"linked_records":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/AdminCrmRecordReference"}}}},"AdminCrmTaskUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"description":"Update completion, deadline, assignees or record links. Omitted fields stay unchanged. Task text cannot be edited through Attio.","properties":{"is_completed":{"type":"boolean"},"due_at":{"type":["string","null"],"maxLength":40,"description":"ISO 8601 date or timestamp. Null removes the deadline."},"assignee_ids":{"type":"array","maxItems":50,"uniqueItems":true,"description":"Replace assignees with these Attio members. An empty array unassigns everyone.","items":{"$ref":"#/components/schemas/AdminCrmIdentifier"}},"linked_records":{"type":"array","maxItems":50,"description":"Replace the task's record links. An empty array removes all links.","items":{"$ref":"#/components/schemas/AdminCrmRecordReference"}}}},"AdminCrmMember":{"type":"object","additionalProperties":false,"description":"Attio member identity for task assignment filters.","required":["id","name","email"],"properties":{"id":{"$ref":"#/components/schemas/AdminCrmIdentifier"},"name":{"type":["string","null"],"minLength":1,"description":"Member name, or null when Attio returns an empty name."},"email":{"type":["string","null"],"minLength":1,"description":"Email recorded in Attio for explicit staff identity matching, or null when empty."}}},"AdminCrmMemberList":{"type":"object","additionalProperties":false,"description":"Attio workspace members.","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminCrmMember"}}}},"AdminSupportProviderTaskStatus":{"type":"string","minLength":1,"enum":["TODO","IN_PROGRESS","DONE","CANCELLED"]},"AdminSupportWorkbenchAssignee":{"type":"object","additionalProperties":false,"required":["id","name","kind"],"properties":{"id":{"type":"string","minLength":1,"description":"Exact Plain assignee identifier."},"name":{"type":["string","null"],"minLength":1,"description":"Display name in Plain."},"kind":{"type":"string","minLength":1,"description":"Plain assignee type. User identifiers can be assigned from this workbench."}}},"AdminSupportProviderTask":{"type":"object","additionalProperties":false,"required":["id","title","status","priority","is_deleted","updated_at"],"properties":{"id":{"type":"string","minLength":1,"maxLength":128},"ref":{"type":"string","minLength":0,"maxLength":128},"title":{"type":"string","minLength":1,"maxLength":500},"description":{"type":"string","minLength":0,"maxLength":10000},"status":{"$ref":"#/components/schemas/AdminSupportProviderTaskStatus"},"priority":{"type":"integer"},"is_deleted":{"type":"boolean"},"assignee":{"$ref":"#/components/schemas/AdminSupportWorkbenchAssignee"},"updated_at":{"type":"string","format":"date-time","minLength":1}},"description":"Current provider task facts. Empty optional descriptions and references are omitted. Task description input may be empty; omitting description on update preserves the provider value and an explicit empty string clears it."},"AdminSupportProviderTasks":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportProviderTask"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSupportProviderTaskCreate":{"type":"object","additionalProperties":false,"required":["source_updated_at","title"],"properties":{"source_updated_at":{"type":"string","format":"date-time","minLength":1},"title":{"type":"string","minLength":1,"maxLength":500},"description":{"type":"string","minLength":0,"maxLength":10000},"status":{"$ref":"#/components/schemas/AdminSupportProviderTaskStatus"},"priority":{"type":"integer","minimum":0,"maximum":3},"assignee_id":{"type":"string","minLength":1,"maxLength":128}}},"AdminSupportCommandKey":{"type":"string","minLength":1,"maxLength":255},"AdminSupportStaffDeliveryState":{"type":"string","minLength":1,"enum":["prepared","reserved","dispatch_started","not_dispatched","uncertain","acknowledged","entry_linked"]},"AdminSupportStaffDelivery":{"type":"object","additionalProperties":false,"required":["command_key","operation","state","thread_id","content_expires_at","created_at","updated_at"],"properties":{"command_key":{"$ref":"#/components/schemas/AdminSupportCommandKey"},"operation":{"type":"string","minLength":1,"maxLength":128},"state":{"$ref":"#/components/schemas/AdminSupportStaffDeliveryState"},"thread_id":{"type":"string","minLength":1,"maxLength":128},"provider_object_id":{"type":"string","minLength":1,"maxLength":128},"provider_entry_id":{"type":"string","minLength":1,"maxLength":128},"acknowledged_at":{"type":"string","format":"date-time","minLength":1},"content_expires_at":{"type":"string","format":"date-time","minLength":1},"created_at":{"type":"string","format":"date-time","minLength":1},"updated_at":{"type":"string","format":"date-time","minLength":1}},"description":"Durable staff intent. Acknowledged means the provider accepted this mutation; it is not delivery, read, or an exact timeline-entry proof. Unknown results never authorize a new send. Content expires no later than 24 hours after reservation."},"AdminSupportProviderTaskUpdate":{"type":"object","additionalProperties":false,"required":["source_updated_at","task_updated_at"],"properties":{"source_updated_at":{"type":"string","format":"date-time","minLength":1},"title":{"type":"string","minLength":1,"maxLength":500},"description":{"type":"string","minLength":0,"maxLength":10000},"status":{"$ref":"#/components/schemas/AdminSupportProviderTaskStatus"},"priority":{"type":"integer","minimum":0,"maximum":3},"assignee_id":{"type":"string","minLength":1,"maxLength":128},"task_updated_at":{"type":"string","format":"date-time","minLength":1}}},"AdminSupportProviderThreadLink":{"type":"object","additionalProperties":false,"required":["id","thread_id"],"properties":{"id":{"type":"string","minLength":1,"maxLength":128},"thread_id":{"type":"string","minLength":1,"maxLength":128},"task_id":{"type":"string","minLength":0,"maxLength":128},"related_thread_id":{"type":"string","minLength":0,"maxLength":128},"link_type":{"type":"string","minLength":0,"maxLength":128}}},"AdminSupportProviderThreadLinks":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportProviderThreadLink"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSupportProviderTaskLinkCreate":{"type":"object","additionalProperties":false,"required":["source_updated_at","task_id"],"properties":{"source_updated_at":{"type":"string","format":"date-time","minLength":1},"task_id":{"type":"string","minLength":1,"maxLength":128}}},"AdminSupportProviderDiscussion":{"type":"object","additionalProperties":false,"required":["id","thread_id","email_recipients","updated_at"],"properties":{"id":{"type":"string","minLength":1,"maxLength":128},"thread_id":{"type":"string","minLength":1,"maxLength":128},"status":{"type":"string","minLength":0,"maxLength":128},"visibility":{"type":"string","minLength":0,"maxLength":128},"channel":{"type":"string","minLength":0,"maxLength":128},"slack_channel_id":{"type":"string","minLength":0,"maxLength":128},"slack_channel_name":{"type":"string","minLength":0,"maxLength":500},"email_recipients":{"type":"array","items":{"type":"string","minLength":1,"maxLength":254}},"updated_at":{"type":"string","format":"date-time","minLength":1}}},"AdminSupportProviderDiscussions":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportProviderDiscussion"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSupportProviderDiscussionChannel":{"type":"string","minLength":1,"enum":["SLACK","EMAIL"]},"AdminSupportProviderDiscussionCreate":{"type":"object","additionalProperties":false,"required":["source_updated_at","channel","markdown"],"properties":{"source_updated_at":{"type":"string","format":"date-time","minLength":1},"channel":{"$ref":"#/components/schemas/AdminSupportProviderDiscussionChannel"},"markdown":{"type":"string","x-sensitive":true,"minLength":1,"maxLength":10000},"connected_slack_channel_id":{"type":"string","minLength":1,"maxLength":128},"to_addresses":{"type":"array","maxItems":20,"items":{"type":"string","format":"email"}},"cc_addresses":{"type":"array","maxItems":20,"items":{"type":"string","format":"email"}}},"description":"Explicitly sends the exact opening text to the selected provider destination. Requires verified integration capability and an authorized destination. Never implied by creating local handoff work.","oneOf":[{"properties":{"channel":{"const":"SLACK"}},"required":["connected_slack_channel_id"]},{"properties":{"channel":{"const":"EMAIL"},"to_addresses":{"minItems":1}},"required":["to_addresses"]}]},"AdminSupportProviderDiscussionMessage":{"type":"object","additionalProperties":false,"required":["id","discussion_id","created_at"],"properties":{"id":{"type":"string","minLength":1,"maxLength":128},"discussion_id":{"type":"string","minLength":1,"maxLength":128},"text":{"type":"string","minLength":0,"maxLength":50000},"type":{"type":"string","minLength":0,"maxLength":128},"created_at":{"type":"string","format":"date-time","minLength":1}}},"AdminSupportProviderDiscussionMessages":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportProviderDiscussionMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSupportProviderDiscussionReply":{"type":"object","additionalProperties":false,"required":["source_updated_at","channel","markdown"],"properties":{"source_updated_at":{"type":"string","format":"date-time","minLength":1},"channel":{"$ref":"#/components/schemas/AdminSupportProviderDiscussionChannel"},"markdown":{"type":"string","x-sensitive":true,"minLength":1,"maxLength":10000},"connected_slack_channel_id":{"type":"string","minLength":1,"maxLength":128},"to_addresses":{"type":"array","maxItems":20,"items":{"type":"string","format":"email"}},"cc_addresses":{"type":"array","maxItems":20,"items":{"type":"string","format":"email"}}},"oneOf":[{"properties":{"channel":{"const":"SLACK"}},"required":["connected_slack_channel_id"]},{"properties":{"channel":{"const":"EMAIL"},"to_addresses":{"minItems":1}},"required":["to_addresses"]}]},"AdminSupportWorkbenchSource":{"type":"string","minLength":1,"enum":["live","demo"],"description":"live reads and writes Plain; demo uses an isolated development fixture."},"AdminSupportProviderActor":{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"id":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"kind":{"type":"string","minLength":1,"maxLength":128}},"description":"Provider actor responsible for a status change. Staff identities are returned for human and machine users; other actor kinds may have no identity."},"AdminSupportSLAObjective":{"type":"object","additionalProperties":false,"properties":{"state":{"type":"string","minLength":0,"maxLength":128},"breaching_at":{"type":"string","format":"date-time","minLength":1},"breached_at":{"type":"string","format":"date-time","minLength":1},"achieved_at":{"type":"string","format":"date-time","minLength":1},"completed_at":{"type":"string","format":"date-time","minLength":1},"cancelled_at":{"type":"string","format":"date-time","minLength":1}}},"AdminSupportProviderStatusFacts":{"type":"object","additionalProperties":false,"required":["observed_at"],"properties":{"status_changed_at":{"type":"string","format":"date-time","minLength":1},"status_changed_by":{"$ref":"#/components/schemas/AdminSupportProviderActor"},"status_detail":{"type":"string","minLength":0,"maxLength":128},"first_response":{"$ref":"#/components/schemas/AdminSupportSLAObjective"},"next_response":{"$ref":"#/components/schemas/AdminSupportSLAObjective"},"observed_at":{"type":"string","format":"date-time","minLength":1}},"description":"Point-in-time provider facts. Missing SLA objectives are unavailable or not configured; Bird waiting party and due commitments are independent."},"AdminSupportWorkbenchStatus":{"type":"string","minLength":1,"enum":["TODO","SNOOZED","DONE"],"description":"TODO needs staff attention; SNOOZED waits until its timer expires or activity arrives; DONE means finished for now and reopens on new activity."},"AdminSupportWorkbenchLabelType":{"type":"object","additionalProperties":false,"required":["id","name","type","parent_name"],"properties":{"id":{"type":"string","minLength":1,"description":"Plain label type identifier."},"name":{"type":"string","minLength":1,"description":"Label name."},"type":{"type":"string","minLength":1,"description":"Plain label category; TEAM identifies a team label."},"parent_name":{"type":["string","null"],"minLength":1,"description":"Parent label name, or null for a top-level label."}}},"AdminSupportWorkbenchLabel":{"type":"object","additionalProperties":false,"required":["id","label_type"],"properties":{"id":{"type":"string","minLength":1,"description":"Applied label instance; differs from its label type."},"label_type":{"$ref":"#/components/schemas/AdminSupportWorkbenchLabelType"}}},"AdminSupportWorkbenchThread":{"type":"object","additionalProperties":false,"required":["id","ref","title","status","preview","priority","channel","customer_id","customer_name","customer_email","tenant_name","reply_supported","created_at","updated_at","labels"],"properties":{"provider_status":{"$ref":"#/components/schemas/AdminSupportProviderStatusFacts"},"id":{"type":"string","minLength":1,"description":"Exact Plain thread identifier."},"ref":{"type":"string","minLength":1,"description":"Human-readable Plain reference."},"title":{"type":["string","null"],"minLength":1,"description":"Thread subject."},"status":{"$ref":"#/components/schemas/AdminSupportWorkbenchStatus"},"preview":{"type":["string","null"],"minLength":1,"description":"Preview of the latest activity."},"priority":{"type":"integer","description":"Plain priority, from 0 urgent to 3 low.","minimum":0,"maximum":3},"channel":{"type":"string","minLength":1,"description":"Native Plain channel."},"customer_id":{"type":"string","minLength":1,"description":"Exact Plain customer identifier."},"customer_name":{"type":["string","null"],"minLength":1,"description":"Customer display name in Plain."},"customer_email":{"type":["string","null"],"minLength":1,"description":"Customer email as stored in Plain."},"tenant_name":{"type":["string","null"],"minLength":1,"description":"Tenant display name in Plain; it does not establish a Bird organization match."},"organization_id":{"$ref":"#/components/schemas/OrganizationID","description":"Verified Bird organization matched by its exact identifier. Absent when no exact match is available."},"assignee":{"$ref":"#/components/schemas/AdminSupportWorkbenchAssignee"},"created_at":{"type":"string","minLength":1,"description":"When Plain created the thread.","format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"description":"When Plain last updated the thread.","format":"date-time","readOnly":true},"reply_supported":{"type":"boolean","description":"Whether the thread channel supports sending replies from this workbench."},"labels":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportWorkbenchLabel"}}}},"AdminSupportWorkbenchThreadList":{"allOf":[{"type":"object","required":["source","data"],"properties":{"source":{"$ref":"#/components/schemas/AdminSupportWorkbenchSource"},"data":{"type":"array","description":"Support threads in the requested order.","items":{"$ref":"#/components/schemas/AdminSupportWorkbenchThread"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSupportProviderThreadLinkType":{"type":"string","minLength":1,"enum":["RELATED_TO","MERGED_INTO"]},"AdminSupportProviderThreadLinkCreate":{"type":"object","additionalProperties":false,"required":["source_updated_at","related_thread_id","link_type"],"properties":{"source_updated_at":{"type":"string","format":"date-time","minLength":1},"related_thread_id":{"type":"string","minLength":1,"maxLength":128},"link_type":{"$ref":"#/components/schemas/AdminSupportProviderThreadLinkType"}},"description":"MERGED_INTO marks the source Plain thread done. It does not complete Bird work or prove customer recovery. Both sources must remain authorized in the same verified scope."},"AdminSupportDiscussionCapabilities":{"type":"object","additionalProperties":false,"required":["sending_verified","slack_channel_ids","email_addresses"],"properties":{"sending_verified":{"type":"boolean"},"reason":{"type":"string","minLength":0,"maxLength":500},"slack_channel_ids":{"type":"array","items":{"type":"string","minLength":1,"maxLength":128}},"email_addresses":{"type":"array","items":{"type":"string","minLength":1,"maxLength":254}}}},"SupportWorkItemID":{"type":"string","minLength":1,"pattern":"^suw_[0-9a-hjkmnp-tv-z]{26}$","example":"suw_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSupportWorkItemReturn":{"type":"object","additionalProperties":false,"required":["revision","reason"],"properties":{"revision":{"type":"integer","format":"int64","minimum":1},"reason":{"type":"string","maxLength":2000,"minLength":1}}},"SupportCaseID":{"type":"string","minLength":1,"pattern":"^suc_[0-9a-hjkmnp-tv-z]{26}$","example":"suc_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSupportWorkKind":{"type":"string","enum":["check","blocker","handoff","commitment"],"minLength":1},"SupportCaseTargetID":{"type":"string","minLength":1,"pattern":"^sut_[0-9a-hjkmnp-tv-z]{26}$","example":"sut_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSupportWorkStatus":{"type":"string","enum":["open","accepted","completed","cancelled"],"minLength":1},"AdminSupportWorkItem":{"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","case_id","revision","kind","title","target_id","target_revision","status","stale"],"properties":{"historical_scope":{"type":"boolean","description":"This record retains an earlier case scope and cannot authorize current work."},"scope_available":{"type":"boolean","description":"Whether original-scope lifetime and authority permit historical detail. False returns only a bounded reference for reviewed disposition."},"id":{"$ref":"#/components/schemas/SupportWorkItemID"},"case_id":{"$ref":"#/components/schemas/SupportCaseID"},"revision":{"type":"integer","format":"int64","minimum":1},"kind":{"$ref":"#/components/schemas/AdminSupportWorkKind"},"title":{"type":"string","maxLength":200,"minLength":1},"owner_user_id":{"$ref":"#/components/schemas/UserID"},"team":{"type":"string","maxLength":100,"minLength":1},"due_at":{"type":"string","format":"date-time","minLength":1},"dependency_id":{"$ref":"#/components/schemas/SupportWorkItemID"},"target_id":{"$ref":"#/components/schemas/SupportCaseTargetID"},"target_revision":{"type":"integer","format":"int64","minimum":1},"status":{"$ref":"#/components/schemas/AdminSupportWorkStatus"},"stale":{"type":"boolean"},"evidence":{"type":"string","maxLength":2000,"minLength":0},"customer_update_reference":{"type":"string","maxLength":500,"minLength":0},"reason":{"type":"string","maxLength":2000,"minLength":0},"completed_by":{"$ref":"#/components/schemas/UserID"},"provider_task_id":{"type":"string","maxLength":128,"minLength":1},"provider_discussion_id":{"type":"string","maxLength":128,"minLength":1},"customer_update_state":{"type":"string","description":"provider_recorded means a customer-facing staff reply was found on this Plain thread. Delivery and customer reading remain unknown.","minLength":1},"customer_update_observed_at":{"type":"string","format":"date-time","minLength":1},"due_timezone":{"type":"string","minLength":1,"maxLength":100,"description":"Persisted IANA timezone of the promise, absent on legacy work without explicit timezone context."},"fallback_owner_user_id":{"$ref":"#/components/schemas/UserID"}}}],"unevaluatedProperties":false},"AdminSupportPublicationCreate":{"type":"object","additionalProperties":false,"properties":{"revision":{"type":"integer","format":"int64","minimum":1},"message_text":{"type":"string","minLength":1,"maxLength":19000}},"required":["revision","message_text"]},"AdminSupportPublicationPreview":{"type":"object","additionalProperties":false,"properties":{"delivery":{"$ref":"#/components/schemas/AdminSupportStaffDelivery"},"outgoing_text":{"type":"string","x-sensitive":true},"work_item_id":{"$ref":"#/components/schemas/SupportWorkItemID"},"work_revision":{"type":"integer","format":"int64"},"due_at":{"type":"string","format":"date-time","minLength":1},"due_timezone":{"type":"string","minLength":1,"maxLength":128},"update_reference":{"type":"string","minLength":1,"maxLength":128}},"required":["delivery","work_item_id","work_revision","due_at","due_timezone","update_reference"],"description":"Exact signed outgoing text is returned only while its original retention deadline permits. Preparing does not send. Confirming the displayed immutable preview is required before dispatch. The useful customer-visible update reference is generated by the server."},"AdminSupportPublishedCommitment":{"type":"object","additionalProperties":false,"properties":{"work_item_id":{"$ref":"#/components/schemas/SupportWorkItemID"},"work_revision":{"type":"integer","format":"int64"},"due_at":{"type":"string","format":"date-time","minLength":1},"due_timezone":{"type":"string","minLength":1,"maxLength":100},"update_reference":{"type":"string","minLength":1,"maxLength":128},"provider_entry_id":{"type":"string","minLength":1,"maxLength":128},"published_at":{"type":"string","format":"date-time","minLength":1}},"required":["work_item_id","work_revision","due_at","due_timezone","update_reference","provider_entry_id","published_at"],"description":"Exact published snapshot tied to a provider entry and a frozen reviewed message. Internal rescheduling never updates this snapshot; only an explicitly confirmed replacement publication does. Publication is not delivery or reading."},"AdminSupportPublishedCommitmentResult":{"type":"object","additionalProperties":false,"properties":{"published":{"$ref":"#/components/schemas/AdminSupportPublishedCommitment"}},"required":[]},"AdminSupportOwner":{"type":"object","additionalProperties":false,"required":["user_id","email"],"properties":{"user_id":{"$ref":"#/components/schemas/UserID"},"email":{"type":"string","format":"email","minLength":1}}},"AdminSupportOwnerList":{"allOf":[{"type":"object","required":["data","as_of"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportOwner"}},"as_of":{"type":"string","format":"date-time","minLength":1}}},{"$ref":"#/components/schemas/_ListEnvelope"}],"unevaluatedProperties":false},"AdminSupportCommandOperation":{"type":"string","minLength":1,"description":"Original coordination operation associated with the command key.","enum":["createCase","saveCase","transitionCase","saveWork","transitionWork","saveTarget","addEvidence","reconcileScope"]},"AdminSupportCommandRecoveryInput":{"type":"object","additionalProperties":false,"required":["operation"],"properties":{"case_id":{"$ref":"#/components/schemas/SupportCaseID"},"thread_id":{"type":"string","minLength":1,"maxLength":128,"description":"Plain thread identity. Required for creation recovery without a case; mutually exclusive with case_id."},"operation":{"$ref":"#/components/schemas/AdminSupportCommandOperation"}},"oneOf":[{"required":["case_id"],"properties":{"thread_id":{"not":{}}}},{"required":["thread_id"],"properties":{"case_id":{"not":{}},"operation":{"const":"createCase"}}}]},"AdminSupportCommandRecoveryStatus":{"type":"string","minLength":1,"description":"committed returns the original receipt; not_dispatched permanently prevents the original command from committing.","enum":["committed","not_dispatched"]},"AdminSupportCommandReceiptStatus":{"type":"string","minLength":1,"enum":["committed","absent"]},"SupportArtifactID":{"type":"string","minLength":1,"pattern":"^sua_[0-9a-hjkmnp-tv-z]{26}$","example":"sua_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSupportCommandReceipt":{"type":"object","additionalProperties":false,"required":["status","case_id","operation"],"properties":{"status":{"$ref":"#/components/schemas/AdminSupportCommandReceiptStatus"},"case_id":{"$ref":"#/components/schemas/SupportCaseID"},"operation":{"$ref":"#/components/schemas/AdminSupportCommandOperation"},"work_item_id":{"$ref":"#/components/schemas/SupportWorkItemID"},"target_id":{"$ref":"#/components/schemas/SupportCaseTargetID"},"evidence_id":{"$ref":"#/components/schemas/SupportArtifactID"},"result_revision":{"type":"integer","format":"int64","minimum":1},"case_revision":{"type":"integer","format":"int64","minimum":1},"created_at":{"type":"string","format":"date-time","minLength":1}}},"AdminSupportCommandRecovery":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"$ref":"#/components/schemas/AdminSupportCommandRecoveryStatus"},"receipt":{"description":"Original command receipt. Present only for committed recovery.","$ref":"#/components/schemas/AdminSupportCommandReceipt"}},"oneOf":[{"properties":{"status":{"const":"committed"}},"required":["receipt"]},{"properties":{"status":{"const":"not_dispatched"},"receipt":{"not":{}}}}]},"AdminSupportScopeTargetReference":{"type":"object","additionalProperties":false,"required":["id","revision"],"properties":{"id":{"$ref":"#/components/schemas/SupportCaseTargetID"},"revision":{"type":"integer","format":"int64","minimum":1}}},"AdminSupportScopeWorkReference":{"type":"object","additionalProperties":false,"required":["id","revision"],"properties":{"id":{"$ref":"#/components/schemas/SupportWorkItemID"},"revision":{"type":"integer","format":"int64","minimum":1}}},"AdminSupportScopePreview":{"type":"object","additionalProperties":false,"required":["case_id","case_revision","preview_token","source_updated_at","reviewing","targets","work_items"],"properties":{"case_id":{"$ref":"#/components/schemas/SupportCaseID"},"case_revision":{"type":"integer","format":"int64","minimum":1},"preview_token":{"type":"string","minLength":64,"maxLength":64,"pattern":"^[a-f0-9]{64}$"},"current_organization_id":{"$ref":"#/components/schemas/OrganizationID"},"proposed_organization_id":{"$ref":"#/components/schemas/OrganizationID"},"source_updated_at":{"type":"string","format":"date-time","minLength":1},"expires_at":{"type":"string","format":"date-time","minLength":1},"reviewing":{"type":"boolean"},"targets":{"type":"array","maxItems":32,"items":{"$ref":"#/components/schemas/AdminSupportScopeTargetReference"}},"work_items":{"type":"array","maxItems":1000,"items":{"$ref":"#/components/schemas/AdminSupportScopeWorkReference"}}}},"AdminSupportScopeReconciliation":{"type":"object","additionalProperties":false,"required":["preview_token","reason","finish"],"properties":{"preview_token":{"type":"string","minLength":64,"maxLength":64,"pattern":"^[a-f0-9]{64}$"},"reason":{"type":"string","minLength":1,"maxLength":2000,"description":"Reviewed reason for the scope decision; adopting scope makes this the new case goal."},"finish":{"type":"boolean","description":"Finish only after all targets and work have separately been reviewed or retired. False adopts the current Plain organization and opens scope review."}}},"SupportCaseScopeID":{"type":"string","minLength":1,"pattern":"^sus_[0-9a-hjkmnp-tv-z]{26}$","example":"sus_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSupportObservationTime":{"type":"string","format":"date-time","minLength":1,"description":"Response calculation observation time. Reads are not one atomic snapshot and do not establish freshness or completeness of Plain provider data."},"AdminSupportWorkSummary":{"type":"object","additionalProperties":false,"required":["total","open","overdue","stale","as_of"],"properties":{"total":{"type":"integer","format":"int64","description":"Eligible stored Bird obligations matching every supplied filter, excluding expired cases and deleted organizations."},"open":{"type":"integer","format":"int64","description":"Filtered obligations with open or accepted status."},"overdue":{"type":"integer","format":"int64","description":"Filtered open or accepted obligations whose due time is before as_of. Undated obligations are excluded."},"stale":{"type":"integer","format":"int64","description":"Filtered obligations whose target binding is stale, independently of work status."},"as_of":{"$ref":"#/components/schemas/AdminSupportObservationTime"}},"description":"Counts cover stored Bird obligations, not a Plain-wide queue. Each count intersects every supplied filter and is read separately."},"AdminSupportWaitingOn":{"type":"string","enum":["customer","bird","external","none"],"minLength":1},"AdminSupportCaseState":{"type":"string","enum":["open","scope_review","resolved"],"minLength":1},"AdminSupportDomainOutcome":{"type":"string","enum":["pending","verified","unknown"],"minLength":1},"AdminSupportCaseOutcome":{"type":"string","enum":["resolved","not_reproducible","no_action_needed","verified_recovery","answered","accepted_workaround","duplicate","no_response"],"minLength":1,"description":"Recorded customer outcome, independent of Plain status and product verification. Outcomes retain their distinct meaning; only verified_recovery requires current domain-owned proof."},"AdminSupportTargetPlatform":{"type":"string","enum":["new_bird","legacy","intake","retired"],"minLength":1},"AdminSupportCaseTarget":{"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","case_id","revision","platform","source","verified_by_domain"],"properties":{"historical_scope":{"type":"boolean","description":"This record retains an earlier case scope and cannot authorize current work."},"scope_available":{"type":"boolean","description":"Whether original-scope lifetime and authority permit historical detail. False returns only a bounded reference for reviewed disposition."},"id":{"$ref":"#/components/schemas/SupportCaseTargetID"},"case_id":{"$ref":"#/components/schemas/SupportCaseID"},"revision":{"type":"integer","format":"int64","minimum":1},"platform":{"$ref":"#/components/schemas/AdminSupportTargetPlatform"},"region":{"type":"string","maxLength":32,"minLength":1},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"resource_type":{"type":"string","maxLength":64,"minLength":1},"resource_id":{"type":"string","maxLength":256,"minLength":1},"source":{"type":"string","maxLength":500,"minLength":1},"verified_by_domain":{"type":"boolean"}}}],"unevaluatedProperties":false},"AdminSupportCase":{"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","revision","provider","thread_id","source_updated_at","title","goal","waiting_on","state","domain_outcome","created_by","targets"],"properties":{"scope_id":{"$ref":"#/components/schemas/SupportCaseScopeID","description":"Identifies the customer scope of this case. A reviewed organization change creates a new scope; drafts from a different scope must be reviewed again."},"scope_change_required":{"type":"boolean","description":"Present after checking the current Plain organization on a case detail read. True requires scope review before further case operations; omitted from stored list projections."},"work_summary":{"$ref":"#/components/schemas/AdminSupportWorkSummary"},"id":{"$ref":"#/components/schemas/SupportCaseID"},"revision":{"type":"integer","format":"int64","minimum":1},"provider":{"type":"string","maxLength":32,"minLength":1},"thread_id":{"type":"string","maxLength":128,"minLength":1},"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"expires_at":{"type":"string","format":"date-time","minLength":1},"source_updated_at":{"type":"string","format":"date-time","minLength":1},"title":{"type":"string","maxLength":200,"minLength":1},"goal":{"type":"string","maxLength":2000,"minLength":1},"impact":{"type":"string","maxLength":2000,"minLength":1},"owner_user_id":{"$ref":"#/components/schemas/UserID"},"waiting_on":{"$ref":"#/components/schemas/AdminSupportWaitingOn"},"state":{"$ref":"#/components/schemas/AdminSupportCaseState"},"domain_outcome":{"$ref":"#/components/schemas/AdminSupportDomainOutcome"},"created_by":{"$ref":"#/components/schemas/UserID"},"outcome":{"$ref":"#/components/schemas/AdminSupportCaseOutcome"},"outcome_evidence":{"type":"string","maxLength":2000,"minLength":1},"resolution_evidence_id":{"$ref":"#/components/schemas/SupportArtifactID"},"targets":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportCaseTarget"}},"reconciliation_required":{"type":"boolean","description":"Present after a current Plain status check on a case detail read; omitted from stored list projections."},"target":{"$ref":"#/components/schemas/AdminSupportCaseTarget"},"target_revision":{"type":"integer","format":"int64","minimum":1},"primary_target_id":{"$ref":"#/components/schemas/SupportCaseTargetID"}}}],"unevaluatedProperties":false},"AdminSupportOperationsReport":{"type":"string","enum":["missed_promises","handoffs","blockers"],"description":"Support coordination report to retrieve."},"AdminSupportOperationsWorkItem":{"type":"object","additionalProperties":false,"description":"Current work and retained deadline, handoff and return evidence. Metrics do not establish customer delivery or product recovery.","required":["work_item","missed_deadlines","reschedules","accepted_handoffs","acceptance_seconds","blocker_age_seconds"],"properties":{"work_item":{"$ref":"#/components/schemas/AdminSupportWorkItem"},"first_due_at":{"type":"string","format":"date-time","description":"First recorded due time in the current work scope. Absent when that scope has no recorded deadline."},"missed_deadlines":{"type":"integer","format":"int64","minimum":0,"description":"Distinct promise deadlines missed during the report window, including deadlines later rescheduled."},"last_missed_due_at":{"type":"string","format":"date-time","description":"Most recent missed deadline in the report window."},"reschedules":{"type":"integer","format":"int64","minimum":0,"description":"Deadline changes during the report window."},"last_rescheduled_at":{"type":"string","format":"date-time","description":"Time of the latest deadline change in the report window."},"last_reschedule_reason":{"type":"string","description":"Recorded explanation for the latest deadline change."},"accepted_handoffs":{"type":"integer","format":"int64","minimum":0,"description":"Handoff acceptances during the report window."},"acceptance_seconds":{"type":"integer","format":"int64","minimum":0,"description":"Total seconds from the relevant assignment or reopening to handoff acceptance during the report window."},"last_accepted_at":{"type":"string","format":"date-time","description":"Latest handoff acceptance in the report window."},"last_assigned_at":{"type":"string","format":"date-time","description":"Latest change to the assigned owner, or initial assignment."},"last_returned_at":{"type":"string","format":"date-time","description":"Latest handoff return during the report window."},"last_return_reason":{"type":"string","description":"Recorded explanation for the latest handoff return."},"blocker_age_seconds":{"type":"integer","format":"int64","minimum":0,"description":"Age of an unfinished blocker in seconds; zero for other work."}}},"AdminSupportOperationsWindow":{"type":"object","required":["as_of","since"],"properties":{"as_of":{"type":"string","minLength":1,"format":"date-time","description":"Observation time used for this report page."},"since":{"type":"string","minLength":1,"format":"date-time","description":"Start of the history report window. Current workload counts cover all unfinished work."}}},"AdminSupportOperationsWorkItemList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportOperationsWorkItem"}}}},{"$ref":"#/components/schemas/AdminSupportOperationsWindow"},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}],"unevaluatedProperties":false},"AdminSupportOperationsWorkload":{"type":"object","additionalProperties":false,"description":"Current team and owner workload across accessible retained cases. The group identifier is its oldest unfinished work item and may change as work closes.","required":["id","open","overdue","stale","blockers","awaiting_acceptance","oldest_created_at"],"properties":{"id":{"$ref":"#/components/schemas/SupportWorkItemID"},"owner_user_id":{"$ref":"#/components/schemas/UserID"},"team":{"type":"string","description":"Assigned specialist team. Absent when no team is set."},"open":{"type":"integer","format":"int64","minimum":0,"description":"Unfinished work in this team and owner group."},"overdue":{"type":"integer","format":"int64","minimum":0,"description":"Unfinished work past its current deadline."},"stale":{"type":"integer","format":"int64","minimum":0,"description":"Unfinished work requiring target review."},"blockers":{"type":"integer","format":"int64","minimum":0,"description":"Unfinished blockers."},"awaiting_acceptance":{"type":"integer","format":"int64","minimum":0,"description":"Open handoffs waiting for their owner to accept."},"oldest_created_at":{"type":"string","minLength":1,"format":"date-time","description":"Creation time of the oldest unfinished work in this group."}}},"AdminSupportOperationsWorkloadList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportOperationsWorkload"}}}},{"$ref":"#/components/schemas/AdminSupportOperationsWindow"},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}],"unevaluatedProperties":false},"SupportHistoryID":{"type":"string","minLength":1,"pattern":"^suh_[0-9a-hjkmnp-tv-z]{26}$","example":"suh_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSupportOperationsOutcome":{"type":"object","additionalProperties":false,"description":"Immutable case resolution report. Reopening a case preserves its earlier resolutions; a support resolution alone does not establish product recovery.","required":["id","case_id","revision","title","thread_id","domain_outcome","resolved_at"],"properties":{"id":{"$ref":"#/components/schemas/SupportHistoryID"},"case_id":{"$ref":"#/components/schemas/SupportCaseID"},"revision":{"type":"integer","format":"int64","minimum":0,"description":"Case revision at resolution."},"title":{"type":"string","minLength":1,"description":"Case title recorded at resolution."},"thread_id":{"type":"string","minLength":1,"description":"Plain conversation identifier."},"owner_user_id":{"$ref":"#/components/schemas/UserID"},"outcome":{"$ref":"#/components/schemas/AdminSupportCaseOutcome"},"domain_outcome":{"$ref":"#/components/schemas/AdminSupportDomainOutcome"},"evidence":{"type":"string","description":"Resolution explanation recorded at the time."},"resolved_at":{"type":"string","minLength":1,"format":"date-time","description":"Time the case resolution was recorded."}}},"AdminSupportOperationsOutcomeList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportOperationsOutcome"}}}},{"$ref":"#/components/schemas/AdminSupportOperationsWindow"},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}],"unevaluatedProperties":false},"AdminSupportSpecialistHandoff":{"type":"object","additionalProperties":false,"required":["id","available","can_accept","can_complete","can_return"],"properties":{"id":{"$ref":"#/components/schemas/SupportWorkItemID"},"available":{"type":"boolean","description":"Whether current assignment, source and original scope permit showing this handoff."},"title":{"type":"string","minLength":1,"maxLength":200},"context":{"type":"string","maxLength":2000,"description":"Staff coordination context for this handoff."},"revision":{"type":"integer","format":"int64","minimum":1},"status":{"$ref":"#/components/schemas/AdminSupportWorkStatus"},"due_at":{"type":"string","format":"date-time"},"due_timezone":{"type":"string","maxLength":100},"owner_user_id":{"$ref":"#/components/schemas/UserID"},"evidence":{"type":"string","maxLength":2000,"description":"Staff record of coordination completed in the existing product tools."},"can_accept":{"type":"boolean"},"can_complete":{"type":"boolean"},"can_return":{"type":"boolean"}}},"AdminSupportSpecialistHandoffList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportSpecialistHandoff"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}],"unevaluatedProperties":false},"AdminSupportSpecialistHistoryEntry":{"type":"object","additionalProperties":false,"required":["id","revision","event","actor_user_id","created_at"],"properties":{"id":{"$ref":"#/components/schemas/SupportHistoryID"},"revision":{"type":"integer","format":"int64","minimum":1},"event":{"type":"string","minLength":1,"maxLength":64},"actor_user_id":{"$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","format":"date-time","minLength":1},"evidence":{"type":"string","minLength":1,"maxLength":2000,"description":"Recorded coordination explanation, including a reason for returning the handoff."}}},"AdminSupportSpecialistHistoryList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportSpecialistHistoryEntry"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}],"unevaluatedProperties":false},"AdminSupportSpecialistTransitionStatus":{"type":"string","minLength":1,"enum":["accepted","completed","returned"]},"AdminSupportSpecialistTransition":{"type":"object","additionalProperties":false,"required":["revision","status"],"properties":{"revision":{"type":"integer","format":"int64","minimum":1},"status":{"$ref":"#/components/schemas/AdminSupportSpecialistTransitionStatus"},"evidence":{"type":"string","minLength":0,"maxLength":2000,"description":"Required and nonblank when completing the handoff. Record what was done in the existing product tools. Otherwise omit or leave blank."},"reason":{"type":"string","minLength":0,"maxLength":2000,"description":"Required and nonblank when returning the handoff to its case owner. Otherwise omit or leave blank."}},"if":{"required":["status"],"properties":{"status":{"const":"completed"}}},"then":{"required":["evidence"],"properties":{"evidence":{"minLength":1,"pattern":"\\S"},"reason":{"pattern":"^\\s*$"}}},"else":{"if":{"required":["status"],"properties":{"status":{"const":"returned"}}},"then":{"required":["reason"],"properties":{"reason":{"minLength":1,"pattern":"\\S"},"evidence":{"pattern":"^\\s*$"}}},"else":{"properties":{"evidence":{"pattern":"^\\s*$"},"reason":{"pattern":"^\\s*$"}}}}},"AdminSupportSpecialistCommandStatus":{"type":"string","minLength":1,"enum":["committed","not_dispatched"]},"AdminSupportSpecialistCommandResult":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"$ref":"#/components/schemas/AdminSupportSpecialistCommandStatus"},"revision":{"type":"integer","format":"int64","minimum":1,"description":"Original committed handoff revision. Absent when the command was not dispatched."}},"if":{"required":["status"],"properties":{"status":{"const":"committed"}}},"then":{"required":["revision"],"properties":{"revision":{}}},"else":{"properties":{"revision":{"not":{}}}}},"AdminSupportCaseList":{"allOf":[{"type":"object","required":["data","as_of"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportCase"}},"as_of":{"type":"string","format":"date-time","minLength":1}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}],"unevaluatedProperties":false},"AdminSupportCaseTargetCreate":{"type":"object","additionalProperties":false,"required":["platform","region","source"],"properties":{"platform":{"$ref":"#/components/schemas/AdminSupportTargetPlatform"},"region":{"type":"string","maxLength":32,"minLength":0},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"resource_type":{"type":"string","maxLength":64,"minLength":1},"resource_id":{"type":"string","maxLength":256,"minLength":1},"source":{"type":"string","maxLength":500,"minLength":1}},"description":"An affected target. new_bird requires workspace_id. resource_type and resource_id must be supplied together. Workspace scope verification does not establish product recovery.","dependentRequired":{"resource_type":["resource_id"],"resource_id":["resource_type"]},"oneOf":[{"properties":{"platform":{"const":"new_bird"},"region":{"minLength":1}},"required":["workspace_id"]},{"properties":{"platform":{"const":"legacy"},"region":{"minLength":1},"workspace_id":{"not":{}}}},{"properties":{"platform":{"const":"intake"},"region":{"const":""},"workspace_id":{"not":{}},"resource_type":{"not":{}},"resource_id":{"not":{}}}}]},"AdminSupportCaseCreate":{"type":"object","additionalProperties":false,"required":["thread_id","title","goal","waiting_on","target"],"properties":{"thread_id":{"type":"string","maxLength":128,"minLength":1},"title":{"type":"string","maxLength":200,"minLength":1},"goal":{"type":"string","maxLength":2000,"minLength":1},"impact":{"type":"string","maxLength":2000,"minLength":1},"owner_user_id":{"$ref":"#/components/schemas/UserID"},"waiting_on":{"$ref":"#/components/schemas/AdminSupportWaitingOn"},"target":{"$ref":"#/components/schemas/AdminSupportCaseTargetCreate"}},"description":"Find or create by exact authorized Plain thread. An existing case is returned without applying the submitted brief or creating another target. Organization is derived from the live provider thread."},"AdminSupportCaseClearField":{"type":"string","minLength":1,"enum":["impact","owner_user_id","primary_target_id"]},"AdminSupportCaseUpdate":{"type":"object","additionalProperties":false,"required":["revision","source_updated_at"],"properties":{"revision":{"type":"integer","format":"int64","minimum":1},"source_updated_at":{"type":"string","format":"date-time","minLength":1},"title":{"type":"string","maxLength":200,"minLength":1},"goal":{"type":"string","maxLength":2000,"minLength":1},"impact":{"type":"string","maxLength":2000,"minLength":0},"owner_user_id":{"$ref":"#/components/schemas/UserID"},"waiting_on":{"$ref":"#/components/schemas/AdminSupportWaitingOn"},"primary_target_id":{"$ref":"#/components/schemas/SupportCaseTargetID"},"clear_fields":{"type":"array","uniqueItems":true,"maxItems":3,"items":{"$ref":"#/components/schemas/AdminSupportCaseClearField"},"description":"Explicitly clear these optional fields. Omitted fields are preserved. A field cannot be both supplied and cleared."}},"description":"Patch the supplied fields only. Omission preserves existing values; clear_fields intentionally removes supported optional values."},"AdminSupportCaseResolution":{"type":"object","additionalProperties":false,"required":["revision","source_updated_at","evidence","outcome"],"properties":{"revision":{"type":"integer","format":"int64","minimum":1},"source_updated_at":{"type":"string","format":"date-time","minLength":1},"outcome":{"$ref":"#/components/schemas/AdminSupportCaseOutcome"},"evidence":{"type":"string","maxLength":2000,"minLength":1},"resolution_evidence_id":{"$ref":"#/components/schemas/SupportArtifactID"}}},"AdminSupportCaseReopening":{"type":"object","additionalProperties":false,"required":["revision","source_updated_at","evidence"],"properties":{"revision":{"type":"integer","format":"int64","minimum":1},"source_updated_at":{"type":"string","format":"date-time","minLength":1},"evidence":{"type":"string","maxLength":2000,"minLength":1}}},"AdminSupportHistory":{"type":"object","additionalProperties":false,"required":["id","case_id","revision","event","actor_user_id","created_at"],"properties":{"id":{"$ref":"#/components/schemas/SupportHistoryID"},"case_id":{"$ref":"#/components/schemas/SupportCaseID"},"work_item_id":{"$ref":"#/components/schemas/SupportWorkItemID"},"target_id":{"$ref":"#/components/schemas/SupportCaseTargetID"},"revision":{"type":"integer","format":"int64","minimum":1},"event":{"type":"string","maxLength":64,"minLength":1},"actor_user_id":{"$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","format":"date-time","minLength":1},"evidence":{"type":"string","minLength":1,"maxLength":2000,"description":"Recorded explanation for a case resolution, reopening or handoff return."}}},"AdminSupportHistoryList":{"allOf":[{"type":"object","required":["data","as_of"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportHistory"}},"as_of":{"type":"string","format":"date-time","minLength":1}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}],"unevaluatedProperties":false},"AdminSupportWorkItemList":{"allOf":[{"type":"object","required":["data","as_of"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportWorkItem"}},"as_of":{"type":"string","format":"date-time","minLength":1}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}],"unevaluatedProperties":false},"AdminSupportWorkItemCreate":{"type":"object","additionalProperties":false,"required":["case_id","kind","title","target_id","target_revision"],"properties":{"case_id":{"$ref":"#/components/schemas/SupportCaseID"},"kind":{"$ref":"#/components/schemas/AdminSupportWorkKind"},"title":{"type":"string","maxLength":200,"minLength":1},"owner_user_id":{"$ref":"#/components/schemas/UserID"},"team":{"type":"string","maxLength":100,"minLength":1},"due_at":{"description":"Due instant must be before 9999-01-01T00:00:00Z. Later instants are reserved for pagination.","type":"string","format":"date-time","minLength":1},"dependency_id":{"$ref":"#/components/schemas/SupportWorkItemID"},"target_id":{"$ref":"#/components/schemas/SupportCaseTargetID"},"target_revision":{"type":"integer","format":"int64","minimum":1},"reason":{"type":"string","maxLength":2000,"minLength":0},"provider_task_id":{"type":"string","maxLength":128,"minLength":1},"provider_discussion_id":{"type":"string","maxLength":128,"minLength":1},"due_timezone":{"type":"string","minLength":1,"maxLength":100,"description":"Explicit IANA timezone for the due promise; required with due_at. This does not configure business hours."},"fallback_owner_user_id":{"$ref":"#/components/schemas/UserID"}},"description":"Create a case-specific obligation. Commitment requires owner, due_at and due_timezone. Team is display context, not provider assignment or authorization.","dependentRequired":{"due_at":["due_timezone"],"due_timezone":["due_at"]},"oneOf":[{"properties":{"kind":{"const":"commitment"}},"required":["owner_user_id","due_at","due_timezone"]},{"properties":{"kind":{"const":"handoff"}},"required":["owner_user_id"]},{"properties":{"kind":{"const":"check"}}},{"properties":{"kind":{"const":"blocker"}}}]},"AdminSupportWorkClearField":{"type":"string","minLength":1,"enum":["owner_user_id","team","due_at","due_timezone","fallback_owner_user_id","dependency_id","provider_task_id","provider_discussion_id"]},"AdminSupportWorkItemUpdate":{"type":"object","additionalProperties":false,"required":["revision"],"properties":{"kind":{"$ref":"#/components/schemas/AdminSupportWorkKind"},"title":{"type":"string","maxLength":200,"minLength":1},"owner_user_id":{"$ref":"#/components/schemas/UserID"},"team":{"type":"string","maxLength":100,"minLength":1},"due_at":{"description":"Due instant must be before 9999-01-01T00:00:00Z. Later instants are reserved for pagination.","type":"string","format":"date-time","minLength":1},"dependency_id":{"$ref":"#/components/schemas/SupportWorkItemID"},"target_id":{"$ref":"#/components/schemas/SupportCaseTargetID"},"target_revision":{"type":"integer","format":"int64","minimum":1},"reason":{"type":"string","maxLength":2000,"minLength":0},"provider_task_id":{"type":"string","maxLength":128,"minLength":1},"provider_discussion_id":{"type":"string","maxLength":128,"minLength":1},"due_timezone":{"type":"string","minLength":1,"maxLength":100,"description":"Explicit IANA timezone for the due promise; required with due_at. This does not configure business hours."},"fallback_owner_user_id":{"$ref":"#/components/schemas/UserID"},"revision":{"type":"integer","format":"int64","minimum":1},"clear_fields":{"type":"array","uniqueItems":true,"maxItems":8,"items":{"$ref":"#/components/schemas/AdminSupportWorkClearField"},"description":"Explicitly clear these optional fields. Omitted fields are preserved. A field cannot be both supplied and cleared."}},"description":"Patch supplied fields; omit to preserve. Explicit clear_fields removes optional values. Changing title, owner, target or dependency invalidates acceptance/completion; provider references and customer promise history are preserved. Changing an existing due time requires a reason."},"AdminSupportWorkItemAcceptance":{"type":"object","additionalProperties":false,"required":["revision"],"properties":{"revision":{"type":"integer","format":"int64","minimum":1}}},"AdminSupportWorkItemCompletion":{"type":"object","additionalProperties":false,"required":["revision","evidence"],"properties":{"revision":{"type":"integer","format":"int64","minimum":1},"evidence":{"type":"string","maxLength":2000,"minLength":1},"customer_update_reference":{"type":"string","maxLength":128,"minLength":0}}},"AdminSupportWorkItemCancellation":{"type":"object","additionalProperties":false,"required":["revision","reason"],"properties":{"revision":{"type":"integer","format":"int64","minimum":1},"reason":{"type":"string","maxLength":2000,"minLength":1}}},"AdminSupportCaseTargetInput":{"type":"object","additionalProperties":false,"required":["platform","region","source"],"properties":{"id":{"$ref":"#/components/schemas/SupportCaseTargetID"},"revision":{"type":"integer","format":"int64","minimum":1},"platform":{"$ref":"#/components/schemas/AdminSupportTargetPlatform"},"region":{"type":"string","maxLength":32,"minLength":0},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"resource_type":{"type":"string","maxLength":64,"minLength":1},"resource_id":{"type":"string","maxLength":256,"minLength":1},"source":{"type":"string","maxLength":500,"minLength":1}},"description":"An affected target. new_bird requires workspace_id. resource_type and resource_id must be supplied together. Workspace scope verification does not establish product recovery. For an existing target, id and revision are both required; omit both to create. retired requires an existing target's id and revision.","dependentRequired":{"id":["revision"],"revision":["id"],"resource_type":["resource_id"],"resource_id":["resource_type"]},"if":{"required":["platform"],"properties":{"platform":{"const":"retired"}}},"then":{"required":["id","revision"],"properties":{"id":{},"revision":{}}},"oneOf":[{"properties":{"platform":{"const":"new_bird"},"region":{"minLength":1}},"required":["workspace_id"]},{"properties":{"platform":{"const":"legacy"},"region":{"minLength":1},"workspace_id":{"not":{}}}},{"properties":{"platform":{"enum":["intake","retired"]},"region":{"const":""},"workspace_id":{"not":{}},"resource_type":{"not":{}},"resource_id":{"not":{}}}}]},"AdminSupportCaseTargetWrite":{"type":"object","additionalProperties":false,"required":["case_revision","target"],"properties":{"case_revision":{"type":"integer","format":"int64","minimum":1},"target":{"$ref":"#/components/schemas/AdminSupportCaseTargetInput"}}},"AdminSupportEvidence":{"type":"object","additionalProperties":false,"required":["id","case_id","target_id","target_revision","kind","resource_reference","source","observed_at","actor_user_id","summary","verified_by_domain","created_at"],"properties":{"id":{"$ref":"#/components/schemas/SupportArtifactID"},"case_id":{"$ref":"#/components/schemas/SupportCaseID"},"target_id":{"$ref":"#/components/schemas/SupportCaseTargetID"},"target_revision":{"type":"integer","format":"int64","minimum":1},"kind":{"type":"string","maxLength":64,"minLength":1},"resource_reference":{"type":"string","maxLength":500,"minLength":1},"source":{"type":"string","maxLength":500,"minLength":1},"observed_at":{"type":"string","format":"date-time","minLength":1},"expires_at":{"type":"string","format":"date-time","minLength":1},"actor_user_id":{"$ref":"#/components/schemas/UserID"},"summary":{"type":"string","maxLength":2000,"minLength":1},"verified_by_domain":{"type":"boolean"},"created_at":{"type":"string","format":"date-time","minLength":1}}},"AdminSupportEvidenceList":{"allOf":[{"type":"object","required":["data","as_of"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportEvidence"}},"as_of":{"type":"string","format":"date-time","minLength":1}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}],"unevaluatedProperties":false},"AdminSupportEvidenceCreate":{"type":"object","additionalProperties":false,"required":["target_id","target_revision","kind","resource_reference","source","observed_at","summary"],"properties":{"target_id":{"$ref":"#/components/schemas/SupportCaseTargetID"},"target_revision":{"type":"integer","format":"int64","minimum":1},"kind":{"type":"string","maxLength":64,"minLength":1},"resource_reference":{"type":"string","maxLength":500,"minLength":1},"source":{"type":"string","maxLength":500,"minLength":1},"observed_at":{"type":"string","format":"date-time","minLength":1},"summary":{"type":"string","maxLength":2000,"minLength":1}}},"AdminSupportWorkbenchLabelTypeList":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["source","data"],"properties":{"source":{"$ref":"#/components/schemas/AdminSupportWorkbenchSource"},"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportWorkbenchLabelType"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSupportWorkbenchLabelTypeCreate":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":128,"description":"Label text. Leading and trailing whitespace is removed."}}},"AdminSupportWorkbenchSnippet":{"type":"object","additionalProperties":false,"required":["id","name","text","path"],"properties":{"id":{"type":"string","minLength":1,"description":"Plain shared reply snippet identifier."},"name":{"type":"string","minLength":1,"description":"Snippet title."},"text":{"type":"string","minLength":1,"description":"Plain-text reply content to insert into a draft."},"path":{"type":["string","null"],"minLength":1,"description":"Snippet folder, or null when ungrouped."}}},"AdminSupportWorkbenchSnippetList":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["source","data"],"properties":{"source":{"$ref":"#/components/schemas/AdminSupportWorkbenchSource"},"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportWorkbenchSnippet"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSupportWorkbenchPriorityUpdate":{"type":"object","additionalProperties":false,"required":["priority"],"properties":{"priority":{"type":"integer","minimum":0,"maximum":3,"description":"Plain priority: 0 urgent, 1 high, 2 normal, 3 low."}}},"AdminSupportWorkbenchMutationResult":{"type":"object","additionalProperties":false,"required":["source","thread_id"],"properties":{"source":{"$ref":"#/components/schemas/AdminSupportWorkbenchSource"},"thread_id":{"type":"string","minLength":1,"description":"Exact Plain thread identifier affected by this action."}}},"AdminSupportWorkbenchLabelUpdate":{"type":"object","additionalProperties":false,"required":["applied"],"properties":{"applied":{"type":"boolean","description":"Apply the label when true, or remove it when false. Plain also applies or removes related parent and child labels according to its hierarchy."}}},"AdminSupportWorkbenchUploadCreate":{"type":"object","additionalProperties":false,"required":["file_name","file_size_bytes","internal_note"],"properties":{"file_name":{"type":"string","minLength":1,"description":"Name of the attachment.","maxLength":255},"file_size_bytes":{"type":"integer","format":"int64","minimum":1,"maximum":104857600,"description":"File size in bytes. The selected channel’s lower attachment limit also applies."},"internal_note":{"type":"boolean","description":"Upload for an internal note instead of a customer-visible reply."}}},"AdminSupportWorkbenchUpload":{"type":"object","additionalProperties":false,"required":["attachment_id","upload_form_url","upload_form_data"],"properties":{"attachment_id":{"type":"string","minLength":1,"description":"Plain attachment identifier to include in attachment_ids."},"upload_form_url":{"x-sensitive":true,"format":"uri","type":["string","null"],"minLength":1,"description":"Presigned destination for a multipart upload. Null for a local demo, where no file bytes are uploaded."},"upload_form_data":{"x-sensitive":true,"type":"array","items":{"$ref":"#/components/schemas/SupportChatAttachmentUploadFormData"}}}},"AdminSupportWorkbenchEntryId":{"type":"string","description":"Exact Plain timeline entry identifier.","minLength":1,"maxLength":128},"AdminSupportWorkbenchDownload":{"type":"object","additionalProperties":false,"required":["download_url"],"properties":{"download_url":{"x-sensitive":true,"type":"string","minLength":1,"description":"Temporary signed URL for this attachment.","format":"uri"}}},"AdminSupportOverviewPoint":{"type":"object","additionalProperties":false,"required":["at","value"],"properties":{"at":{"type":"string","minLength":1,"format":"date-time","description":"Start of the UTC day."},"value":{"type":["number","null"],"format":"double","description":"Daily count or median duration in seconds. Null means no sample."}}},"AdminSupportOverviewMetric":{"type":"object","additionalProperties":false,"required":["name","points","thread_ids"],"properties":{"name":{"type":"string","minLength":1,"description":"Support performance metric name."},"median":{"type":"number","format":"double","description":"Median duration in seconds for the reporting period."},"p90":{"type":"number","format":"double","description":"Ninetieth percentile duration in seconds for the reporting period."},"previous":{"type":"number","format":"double","description":"Median duration in seconds for the preceding equal-length period."},"error":{"type":"string","minLength":1,"description":"Why this metric is unavailable."},"thread_ids":{"type":"array","items":{"type":"string","minLength":1},"description":"Reserved for authorized ticket membership; empty for aggregate-only reports. Use the report drilldown to load accessible tickets."},"points":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportOverviewPoint"}}}},"AdminSupportOverview":{"type":"object","additionalProperties":false,"required":["source","observed_at","from","to","threads","metrics"],"properties":{"source":{"$ref":"#/components/schemas/AdminSupportWorkbenchSource"},"observed_at":{"type":"string","minLength":1,"format":"date-time","description":"Time this overview section was read."},"from":{"type":"string","minLength":1,"format":"date-time","description":"Inclusive start of the reporting period."},"to":{"type":"string","minLength":1,"format":"date-time","description":"Exclusive end of the reporting period."},"threads":{"type":"array","description":"Current visible tickets, excluding spam and ignored tickets. Fails rather than reporting a truncated total.","items":{"$ref":"#/components/schemas/AdminSupportWorkbenchThread"}},"report_threads":{"type":"array","description":"Reserved for historical ticket details. Omitted by aggregate reads; use the report ticket collection for drilldowns.","items":{"$ref":"#/components/schemas/AdminSupportWorkbenchThread"}},"metrics":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportOverviewMetric"}}}},"AdminSupportWorkbenchDetail":{"type":"object","additionalProperties":false,"required":["source","thread"],"properties":{"source":{"$ref":"#/components/schemas/AdminSupportWorkbenchSource"},"staff_signature":{"type":"string","readOnly":true,"description":"Signature appended by the server to replies and notes from the authenticated staff member."},"thread":{"$ref":"#/components/schemas/AdminSupportWorkbenchThread"},"provider_status":{"$ref":"#/components/schemas/AdminSupportProviderStatusFacts"},"plain_url":{"type":"string","format":"uri","description":"Configured Plain workspace destination for the currently authorized thread. It is not an authorization or provider-identity signal."}}},"AdminSupportWorkbenchAttachment":{"type":"object","additionalProperties":false,"required":["id","file_name","file_size_bytes","mime_type"],"properties":{"id":{"type":"string","minLength":1,"description":"Plain attachment identifier."},"file_name":{"type":"string","minLength":1,"description":"Original file name."},"file_size_bytes":{"type":"integer","format":"int64","minimum":0,"description":"File size in bytes."},"mime_type":{"type":"string","minLength":1,"description":"Media type reported by Plain."}}},"AdminSupportWorkbenchEntry":{"type":"object","additionalProperties":false,"required":["id","kind","text","actor_name","actor_type","created_at","attachments"],"properties":{"id":{"type":"string","minLength":1,"description":"Exact Plain timeline entry identifier."},"kind":{"type":"string","minLength":1,"description":"Native Plain entry type; NoteEntry is an internal note."},"text":{"type":["string","null"],"minLength":1,"description":"Plain-text content or a description of this activity."},"actor_name":{"type":["string","null"],"minLength":1,"description":"Display name of the Plain actor."},"actor_type":{"type":["string","null"],"minLength":1,"description":"Native Plain actor type."},"created_at":{"type":"string","minLength":1,"description":"When the timeline event occurred.","format":"date-time","readOnly":true},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/AdminSupportWorkbenchAttachment"}}}},"AdminSupportWorkbenchTimeline":{"allOf":[{"type":"object","required":["source","data"],"properties":{"source":{"$ref":"#/components/schemas/AdminSupportWorkbenchSource"},"data":{"type":"array","description":"Thread activity in Plain timeline connection order.","items":{"$ref":"#/components/schemas/AdminSupportWorkbenchEntry"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSupportWorkbenchAssigneeList":{"allOf":[{"type":"object","required":["source","data"],"properties":{"source":{"$ref":"#/components/schemas/AdminSupportWorkbenchSource"},"data":{"type":"array","description":"Assignable Plain users in provider connection order.","items":{"$ref":"#/components/schemas/AdminSupportWorkbenchAssignee"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSupportWorkbenchStatusUpdate":{"type":"object","additionalProperties":false,"required":["status"],"oneOf":[{"properties":{"status":{"const":"SNOOZED"}},"required":["snooze_seconds"]},{"properties":{"status":{"not":{"const":"SNOOZED"}}},"propertyNames":{"not":{"const":"snooze_seconds"}}}],"properties":{"status":{"$ref":"#/components/schemas/AdminSupportWorkbenchStatus"},"snooze_seconds":{"type":"integer","description":"Time before Plain returns the thread to TODO. Required for SNOOZED and forbidden for TODO or DONE; invalid combinations return 422.","minimum":60,"maximum":2592000}}},"AdminSupportWorkbenchAssignmentUpdate":{"type":"object","additionalProperties":false,"required":["assignee_id"],"properties":{"assignee_id":{"type":["string","null"],"minLength":1,"maxLength":128,"description":"Exact Plain user identifier. Null removes the current assignment."}}},"AdminSupportWorkbenchMessageCreate":{"type":"object","additionalProperties":false,"required":["message_text"],"properties":{"message_text":{"type":"string","description":"Message body. The acting staff name is appended for attribution in Plain.","minLength":1,"maxLength":20000,"x-sensitive":true},"attachment_ids":{"type":"array","maxItems":100,"uniqueItems":true,"description":"Previously uploaded attachments for the same channel and composer mode. Omit for a text-only message.","items":{"type":"string","minLength":1,"description":"Plain attachment identifier.","maxLength":128}}}},"AdminEmailSuppression":{"type":"object","readOnly":true,"additionalProperties":false,"description":"One record on a workspace's email suppression list: an address Bird will not send to, why, and what created the record. A record blocks the message categories named by `applies_to`. Other suppression records, recipient preferences, and sending restrictions still apply.\n","required":["id","workspace_id","email","reason","origin","applies_to","removable","created_at","source_email_id","source_recipient_id"],"properties":{"id":{"$ref":"#/components/schemas/SuppressionID","description":"Suppression ID."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"Workspace whose sending this record blocks."},"email":{"type":"string","minLength":1,"description":"The suppressed address, stored lowercase."},"reason":{"type":"string","minLength":1,"x-extensible-enum":["hard_bounce","complaint","unsubscribe","manual"],"description":"Why the address is suppressed:\n\n- `hard_bounce`: A delivery permanently failed.\n- `complaint`: The recipient reported a message as spam.\n- `unsubscribe`: A legacy opt-out. No new record carries this reason; the\n  sweep that moves them to the preference store has not reached this one\n  yet, which is why it cannot be removed here.\n- `manual`: Added by the workspace or by staff.\n"},"origin":{"type":"string","minLength":1,"x-extensible-enum":["bounce_event","complaint_event","unsubscribe_event","unsubscribe_link","api_key","user"],"description":"What created the record:\n\n- `bounce_event`: A hard bounce reported by the receiving mail server.\n- `complaint_event`: A spam complaint reported by the mailbox provider.\n- `unsubscribe_event`: An opt-out reported on a delivery event, such as a\n  `List-Unsubscribe` header the mailbox provider acted on.\n- `unsubscribe_link`: The recipient following the unsubscribe link in a\n  message.\n- `api_key`: Added through the API with an API key.\n- `user`: Added by a person in the dashboard, or by staff.\n"},"applies_to":{"type":"string","minLength":1,"x-extensible-enum":["all","non_transactional","category"],"description":"Which sends the record blocks:\n\n- `all`: Every message category, transactional included.\n- `non_transactional`: Non-transactional messages. This record does not block transactional mail.\n- `category`: Reserved value. Currently blocks every message category, transactional included.\n\nThis list grows over time, and any value other than `non_transactional`\nblocks every category, so treat an unknown value as blocking the send.\n"},"removable":{"type":"boolean","description":"Whether staff may remove this record. False for legacy `unsubscribe` records, because staff cannot reverse a recipient's opt-out.\n"},"source_email_id":{"oneOf":[{"$ref":"#/components/schemas/EmailID"},{"type":"null"}],"description":"The message the record came from. Null for manual additions, and for delivery reports that arrived without the message identity.\n"},"source_recipient_id":{"oneOf":[{"$ref":"#/components/schemas/RecipientID"},{"type":"null"}],"description":"The recipient row on that message, under the same conditions as `source_email_id`."},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"When the address was suppressed."}}},"AdminEmailSuppressionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of the workspace's suppression records, newest first.","items":{"$ref":"#/components/schemas/AdminEmailSuppression"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminEmailMessageStatus":{"type":"string","minLength":1,"enum":["scheduled","accepted","processed","deferred","delivered","partial_failure","bounced","complained","rejected","canceled","blocked"],"description":"Aggregate delivery status of an email as staff see it. Every value of the\ncustomer-facing status, plus `blocked`.\n\nIn flight:\n\n- `scheduled`: The message is queued to send at a future time and has not been dispatched yet.\n- `accepted`: The initial status of an immediate send. The message is queued for its recipients.\n- `processed`: Delivery is underway, so at least one recipient's message is on its way out and none has failed.\n- `deferred`: At least one recipient's mailbox provider asked for a retry, and delivery attempts continue.\n\nFinal:\n\n- `delivered`: Every recipient's mail server accepted the message.\n- `bounced`: Every recipient permanently failed (bounced or was rejected).\n- `rejected`: Every recipient was rejected before a delivery attempt (for example, all recipients were suppressed).\n- `partial_failure`: Some recipients permanently failed while others were delivered or are still in flight.\n- `canceled`: A scheduled message was canceled before it was sent.\n\n`complained` takes precedence over every delivery status above: at least one recipient\nreported the message as spam, regardless of what happened to the rest. Only `blocked`\noutranks it.\n\nStaff-only:\n\n- `blocked`: Spam enforcement stopped the message before it was sent. It outranks every\n  other value here, because declining to send is not a delivery outcome that can compete\n  with one. **The sender never sees this.** Their status stays `accepted`, the message\n  still counts toward their accepted volume, and they are still billed for it. Blocking\n  is deliberately silent: only this staff-facing status, the blocked stats, and the\n  workspace growth signal report it.\n"},"AdminEmailBlock":{"type":"object","additionalProperties":false,"description":"Why spam enforcement blocked this message. Present only on a message whose status is `blocked`; null on every other message.\n","required":["blocked_at","reason"],"properties":{"blocked_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When enforcement blocked the message. Blocks land seconds after accept, so this is close to, but always after, the message's accepted time.\n"},"reason":{"type":"string","minLength":1,"readOnly":true,"description":"What triggered the block. Currently always `verdict`, meaning a high-confidence spam score. Treat this as an open set: new reasons can appear without a change to the status itself, which is reason-agnostic.\n"},"score":{"type":["number","null"],"readOnly":true,"description":"Spam score behind the block; higher is more spam-like. Null when the block was recorded without one.\n"}}},"AdminEmailMessage":{"type":"object","additionalProperties":false,"description":"Non-PII summary of a single email, for admin triage. Carries only sender and structural metadata; recipient addresses, the subject, and message content are deliberately omitted.\n","required":["id","region","workspace_id","organization_id","from","to_count","cc_count","bcc_count","html_bytes","text_bytes","category","status","recipient_count","accepted_count","provider_accepted_count","processed_count","delivered_count","bounced_count","complained_count","deferred_count","rejected_count","accepted_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/EmailID","description":"Email ID."},"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"Region this email was sent in."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that sent the email."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization that owns the sending workspace."},"from":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailAddress"}],"description":"Sender address, with display name when one was set on the send."},"to_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of primary (To) recipients."},"cc_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of CC recipients."},"bcc_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of BCC recipients."},"html_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Size of the HTML body in bytes, or 0 when the email had no stored HTML body."},"text_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Size of the plain-text body in bytes, or 0 when the email had no stored text body."},"category":{"type":"string","minLength":1,"readOnly":true,"enum":["marketing","transactional"],"description":"Content classification set on the send."},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminEmailMessageStatus"}],"description":"Aggregate delivery status derived from the recipients' states. `scheduled` and `canceled` are message-level states for scheduled sends: queued for a future time, or canceled before sending. `blocked` is staff-only and outranks the rest; the sender sees `accepted`.\n"},"block":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/AdminEmailBlock"},{"type":"null"}],"description":"Why this message was blocked, or null if it was not. Populated from the same read that sets `status`, so the two can never disagree.\n"},"recipient_count":{"type":"integer","minimum":0,"readOnly":true,"default":0,"description":"Total number of recipients across To, CC, and BCC."},"accepted_count":{"type":"integer","minimum":0,"readOnly":true,"default":0,"description":"Number of recipients Bird has accepted and is preparing to deliver."},"provider_accepted_count":{"type":"integer","minimum":0,"readOnly":true,"default":0,"description":"Number of recipients handed off to the sending provider but not yet confirmed processed."},"processed_count":{"type":"integer","minimum":0,"readOnly":true,"default":0,"description":"Number of recipients for whom Bird has processed the message and queued it for delivery."},"delivered_count":{"type":"integer","minimum":0,"readOnly":true,"default":0,"description":"Number of recipients whose messages were accepted by the remote mail server."},"bounced_count":{"type":"integer","minimum":0,"readOnly":true,"default":0,"description":"Number of recipients that resulted in a permanent delivery failure."},"complained_count":{"type":"integer","minimum":0,"readOnly":true,"default":0,"description":"Number of recipients that reported the message as spam."},"deferred_count":{"type":"integer","minimum":0,"readOnly":true,"default":0,"description":"Number of recipients in transient delivery deferral; delivery is being retried."},"rejected_count":{"type":"integer","minimum":0,"readOnly":true,"default":0,"description":"Number of recipients rejected before delivery."},"accepted_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When Bird accepted the send."},"scheduled_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the message is scheduled to send. Null for an immediate send; stays set after a scheduled send fires."}}},"AdminEmailMessageList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEmailMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminEmailContentEnvelope":{"type":"object","additionalProperties":false,"description":"Identifying details of the email whose content was returned.","properties":{"from":{"$ref":"#/components/schemas/EmailAddress"},"subject":{"type":"string","minLength":1,"readOnly":true,"description":"The subject line of the email."},"sent_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the email was accepted for sending."},"recipient_count":{"type":"integer","format":"int32","readOnly":true,"description":"Total number of recipients across To, CC, and BCC."},"recipients":{"type":"array","readOnly":true,"description":"A sample of recipient addresses, capped at the first ten.","items":{"type":"string","format":"email"}}},"required":["from","subject","sent_at","recipient_count","recipients"]},"AdminEmailContentReveal":{"type":"object","additionalProperties":false,"description":"The rendered body of an email, returned to authorized staff for review.","properties":{"message":{"$ref":"#/components/schemas/AdminEmailContentEnvelope"},"html_body":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"The HTML body of the email, or null if the email had none."},"text_body":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"The plain-text body of the email, or null if the email had none."}},"required":["message","html_body","text_body"]},"AdminEmailEvent":{"type":"object","additionalProperties":false,"description":"A single lifecycle event for one recipient of an email, with the full sending diagnostics staff need to debug a delivery. Carries no recipient address, client IP, or user agent.\n","required":["id","type","occurred_at","recipient_id"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"pattern":"^ev_[0-9a-hjkmnp-tv-z]{26}$","description":"Event ID.","example":"ev_01krdgeqcxet5s7t44vh8rt9mg"},"type":{"$ref":"#/components/schemas/EmailEventType","description":"Event type."},"occurred_at":{"type":"string","format":"date-time","minLength":1,"description":"When this event occurred."},"recipient_id":{"$ref":"#/components/schemas/RecipientID","description":"The recipient this event applies to. Identifies the recipient by id only, never by address."},"bounce_type":{"type":["string","null"],"description":"Bounce classification, present on bounce and deferral events: `hard` (permanent failure), `soft` (transient), `block` (reputation block by the receiving server), `admin` (administrative refusal), or `undetermined` (ambiguous response).\n"},"bounce_class":{"type":["integer","null"],"minimum":1,"maximum":255,"description":"Numeric bounce classification for fine-grained triage. Present on bounce and deferral events."},"bounce_code":{"type":["string","null"],"description":"SMTP status code returned by the receiving mail server. Present on bounce and deferral events.","example":"5.1.1"},"bounce_description":{"type":["string","null"],"description":"Human-readable bounce reason. Present on bounce and deferral events."},"defer_reason":{"type":["string","null"],"description":"Reason the receiving server temporarily refused the message. Present on deferral events."},"remote_response":{"type":["string","null"],"description":"The raw response returned by the receiving mail server, when one was provided."},"attempt_count":{"type":["integer","null"],"minimum":0,"description":"Number of delivery attempts made so far. Present on deferral events."},"rejection_reason":{"type":["string","null"],"description":"Specific cause of rejection (`recipient_suppressed`, `transmission_failed`, `generation_failure`, `policy_rejection`, `domain_unverified`, `quota_exceeded`, or `recipient_not_allowed`). Present on rejection events only.\n"},"sending_ip":{"type":["string","null"],"description":"The IP address used to send this message. Present on delivery, bounce, and deferral events."},"is_prefetched":{"type":["boolean","null"],"description":"True when an open was auto-fetched by an inbox privacy feature rather than a real user action. Present on open events."},"url":{"type":["string","null"],"description":"The clicked URL. Present on click events."},"link_name":{"type":["string","null"],"description":"The clicked link's own name, when the link in the message carried one. Present on the same events as `url`."},"country":{"type":["string","null"],"minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code for an engagement event, when available.","example":"US"}}},"AdminEmailEventList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of timeline events for this email send, in chronological order.","items":{"$ref":"#/components/schemas/AdminEmailEvent"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminEmailRecipient":{"type":"object","additionalProperties":false,"description":"Delivery state for one recipient of an email, identified by domain only. Carries the recipient's domain and per-recipient delivery status, never the full address.\n","required":["id","role","recipient_domain","status","open_count","click_count"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/RecipientID","description":"Recipient ID."},"role":{"$ref":"#/components/schemas/RecipientRole","description":"How this recipient appeared in the send request."},"recipient_domain":{"type":"string","minLength":1,"readOnly":true,"description":"The domain part of the recipient's address. The local part is never returned.","example":"example.com"},"status":{"type":"string","minLength":1,"readOnly":true,"description":"Delivery status for this recipient: `accepted`, `processed`, `deferred`, `delivered`, `bounced`, `complained`, or `rejected`.\n"},"rejection_reason":{"type":["string","null"],"readOnly":true,"description":"Present on rejected rows. Specifies why the recipient was rejected (`recipient_suppressed`, `transmission_failed`, `generation_failure`, `policy_rejection`, `domain_unverified`, `quota_exceeded`, or `recipient_not_allowed`).\n"},"processed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the message was processed and queued for delivery to this recipient, or null if not yet processed."},"delivered_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the recipient's mail server accepted the message, or null if not yet delivered."},"processing_latency_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Time between accepting the send and processing the message, in milliseconds. Null until processed."},"delivery_latency_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Time between processing the message and the receiving mail server accepting it, in milliseconds. Null until delivered."},"total_latency_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"End-to-end accept to delivered time for this recipient, in milliseconds. Null until delivered."},"open_count":{"type":"integer","readOnly":true,"default":0,"description":"Number of open events for this recipient."},"click_count":{"type":"integer","readOnly":true,"default":0,"description":"Number of click events for this recipient."}}},"AdminEmailRecipientList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of recipients for this email send.","items":{"$ref":"#/components/schemas/AdminEmailRecipient"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminEmailSubjectsRequest":{"type":"object","additionalProperties":false,"description":"The emails to reveal subjects for.","required":["email_ids"],"properties":{"email_ids":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/EmailID"},"description":"Emails to look up. At most one hundred per call."}}},"AdminEmailSubject":{"type":"object","additionalProperties":false,"description":"The subject line of a single email.","required":["email_id","subject"],"properties":{"email_id":{"readOnly":true,"$ref":"#/components/schemas/EmailID","description":"Email this subject belongs to."},"subject":{"type":"string","minLength":1,"readOnly":true,"description":"The email's subject line."}}},"AdminEmailSubjectList":{"type":"object","additionalProperties":false,"description":"Subject lines for the requested emails.","required":["data"],"properties":{"data":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminEmailSubject"},"description":"One entry per requested email that is still stored; unknown or expired emails are omitted.\n"}}},"AdminEmailSpamVerdict":{"type":"object","additionalProperties":false,"description":"The spam assessment of a single email: an automated score and, when staff have classified the email by hand, that classification. Higher scores mean more spam-like; the action summarizes how a receiving spam filter would treat the message at that score.\n","required":["email_id","score","symbols","scanned_at","source","label","exempt_reason"],"properties":{"email_id":{"readOnly":true,"$ref":"#/components/schemas/EmailID","description":"Email this assessment belongs to."},"score":{"type":["number","null"],"readOnly":true,"description":"Total spam score; higher is more spam-like. Null when the email has a staff classification but no automated assessment.\n"},"action":{"type":"string","readOnly":true,"description":"What a receiving filter would do at this score, such as `no action`, `add header`, or `reject`. Absent when the email has a staff classification but no automated assessment.\n"},"symbols":{"type":"array","readOnly":true,"items":{"type":"string","minLength":1},"description":"Detection rules that fired, as `NAME:score` entries ordered by impact.\n"},"scanned_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the assessment was made."},"source":{"type":"string","minLength":1,"readOnly":true,"enum":["machine","admin"],"description":"Whether the current state is the automated assessment or a staff classification.\n"},"label":{"type":["string","null"],"readOnly":true,"enum":["spam","ham",null],"description":"Staff classification of the email, or null when it has not been classified by hand.\n"},"exempt_reason":{"type":["string","null"],"readOnly":true,"enum":["member","sending_domain","member_domain",null],"description":"Why enforcement let this send through despite the score, or null when no exemption applied. `member` means every recipient holds an account in the sending workspace; `sending_domain`, that they sit on a domain the workspace has verified for sending; `member_domain`, only that they share a business email domain with a member. `member_domain` assumes the most and is the one to check when reviewing whether a workspace is abusing the exemption. A send qualifying under several rules is reported under the weakest one it needed.\n"}}},"AdminEmailSpamVerdictList":{"type":"object","additionalProperties":false,"description":"Spam assessments for the requested emails.","required":["data"],"properties":{"data":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminEmailSpamVerdict"},"description":"One entry per requested email that has an assessment; emails never scanned and never classified are omitted.\n"}}},"AdminEmailSpamLabelCreate":{"type":"object","additionalProperties":false,"description":"A staff classification of an email.","required":["label"],"properties":{"label":{"type":"string","minLength":1,"enum":["spam","ham"],"description":"The classification: `spam` for unwanted or abusive mail, `ham` for legitimate mail.\n"}}},"AdminEmailSpamLabel":{"type":"object","additionalProperties":false,"description":"A recorded staff classification of an email, including whether the spam filter has been taught with it yet.\n","required":["email_id","label","labeled_by","created_at","trained"],"properties":{"email_id":{"readOnly":true,"$ref":"#/components/schemas/EmailID","description":"Email the classification belongs to."},"label":{"type":"string","minLength":1,"readOnly":true,"enum":["spam","ham"],"description":"The recorded classification."},"labeled_by":{"type":"string","minLength":1,"readOnly":true,"description":"Staff member who classified the email."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the classification was recorded."},"trained":{"type":"boolean","readOnly":true,"description":"Whether the spam filter has been taught with this classification. When false, the classification is saved and training is retried the next time the same classification is submitted.\n"},"training_error":{"type":["string","null"],"readOnly":true,"description":"Why training did not complete, or null when it succeeded. The classification itself is always saved.\n"}}},"AdminInboundEmailMessage":{"type":"object","additionalProperties":false,"description":"Non-PII summary of a single received (inbound) email, for admin triage. Carries the external sender, structural recipient counts, and the authentication verdicts; the subject, recipient addresses, and message body are deliberately omitted.\n","required":["id","region","workspace_id","organization_id","from","to_count","cc_count","authentication","spf_pass","dkim_pass","dmarc_pass","attachment_count","received_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/InboundEmailMessageID","description":"Received message ID."},"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"Region this email was received in."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that received the email."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization that owns the receiving workspace."},"from":{"$ref":"#/components/schemas/EmailAddress","description":"External sender address parsed from the message."},"to_count":{"type":"integer","description":"Number of addresses on the message's To header."},"cc_count":{"type":"integer","description":"Number of addresses on the message's Cc header."},"authentication":{"type":["string","null"],"enum":["pass","fail","unknown",null],"description":"Whether the sender of the received message was authenticated. `pass` means the sender's identity was verified; `fail` means it was checked and did not verify; `unknown` means no verdict is available and the sender should not be treated as verified.\n"},"spf_pass":{"type":["boolean","null"],"description":"Whether the receiving provider reports that SPF authorized the envelope sender for the sending server. A soft failure is `false`. Missing, neutral and inconclusive results are `null`."},"dkim_pass":{"type":["boolean","null"],"description":"Whether the receiving provider verified a DKIM signature. A passing signature makes this `true` even when another signature fails. Missing signatures and inconclusive verification results are `null`."},"dmarc_pass":{"type":["boolean","null"],"description":"Whether SPF or DKIM passed and aligned with the domain in the message's `From` header. The receiving provider currently supplies no DMARC result, so this is `null`."},"attachment_count":{"type":"integer","description":"Number of attachments found on the message."},"received_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When Bird received the message."}}},"AdminInboundEmailMessageList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminInboundEmailMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminMailbox":{"type":"object","description":"A staff-facing view of one agent mailbox, across tenants. Carries the mailbox's own configuration and counts plus its owning organization and workspace for attribution — never the contents of the mail it holds.\n","additionalProperties":false,"required":["id","address","domain","state","organization_id","workspace_id","retention_tier","message_count","size_bytes","thread_count","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/MailboxID"},"address":{"type":"string","minLength":1,"description":"The mailbox's email address.","readOnly":true},"domain":{"type":"string","minLength":1,"description":"The domain the address is on (a shared inbox.ai domain, or a customer receiving-enabled domain).","readOnly":true},"display_name":{"type":["string","null"],"description":"Sender display name configured on the mailbox. Null when unset.","readOnly":true},"state":{"type":"string","minLength":1,"enum":["active","suspended"],"description":"Lifecycle state. A suspended mailbox stops sending and its inbound mail is retained as blocked.","readOnly":true},"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"ip_pool_id":{"oneOf":[{"$ref":"#/components/schemas/IPPoolID"},{"type":"null"}],"description":"The dedicated IP pool this mailbox sends on, isolating its sending reputation. Null means it sends on the organization's default pool. The binding is customer-managed; it surfaces here for deliverability triage. A bound pool with no sendable dedicated IP rejects the send rather than falling back to the shared IPs.\n","readOnly":true},"retention_tier":{"type":"string","minLength":1,"description":"How long the mailbox remembers message metadata, extracted text, and attachments. Message bodies and raw MIME stay available for 30 days regardless of tier.","readOnly":true},"message_count":{"type":"integer","format":"int64","description":"Number of retained messages across all of the mailbox's threads.","readOnly":true},"thread_count":{"type":"integer","format":"int64","description":"Number of retained threads.","readOnly":true},"size_bytes":{"type":"integer","format":"int64","description":"Stored bytes across the mailbox's retained messages: the metadata and extracted text kept for the retention tier plus attachment bytes. Message bodies and raw MIME expire after 30 days and do not count. Maintained with each message written or deleted, so the value is current; messages stored before the counter existed are not counted.","readOnly":true},"last_message_at":{"type":["string","null"],"format":"date-time","description":"When the mailbox last sent or received a message, since this field started being recorded. Null when the mailbox has never sent or received one since then, which also covers a mailbox that predates this field and has had no activity since.\n","readOnly":true},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"When the mailbox was created.","readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"description":"When the mailbox was last updated.","readOnly":true},"deleted_at":{"type":["string","null"],"format":"date-time","description":"When the mailbox was deleted, or null if it is live. A deleted mailbox holds its address for a 30-day restore window before the address is released.","readOnly":true}}},"AdminMailboxList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMailbox"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"MailboxReceivePolicy":{"type":"string","enum":["open","replies_only","allowlist","drop"],"default":"open","description":"Which inbound mail a mailbox accepts:\n\n- `open`: Accepts everything not blocked by a rule.\n- `replies_only`: Accepts only replies to messages this mailbox has sent. A\n  reply must match a message the mailbox sent. Landing in an existing thread\n  by itself does not count.\n- `allowlist`: Accepts only senders matching an allow rule. Replies to\n  prior outbound mail are always admitted unless blocked.\n- `drop`: Stores nothing.\n"},"MailboxRetentionTier":{"type":"string","enum":["30d","90d","1y"],"default":"30d","description":"How long message metadata, extracted text, and attachments are kept. Original bodies and inbound raw MIME are limited to 30 days on every tier."},"AdminMailboxCreate":{"type":"object","description":"Parameters for provisioning a mailbox into a customer's workspace on staff authority. The workspace decides which organization owns the mailbox and which region holds it.\n","additionalProperties":false,"required":["workspace_id"],"properties":{"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"local_part":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9._-]+$","description":"The local part of the address (the part before `@`). Letters, digits, dots, underscores, and hyphens; stored lowercase. Unlike the customer create, a handle on the shared domain need not be five characters or keep separators between letters and digits, so short and non-canonical handles are available here. Omit to generate a random one.","example":"esim"},"domain":{"type":"string","minLength":1,"maxLength":255,"default":"inbox.ai","description":"The domain the address lives under. Defaults to the shared mailbox domain, where creating the mailbox claims the address for the workspace's organization. A customer domain must belong to this workspace and have receiving enabled. Staff cannot place a mailbox on a domain the workspace does not hold.","example":"mail.acme.com"},"display_name":{"type":"string","minLength":1,"maxLength":255,"description":"Display name used as the sender name on mail from this mailbox.","example":"Acme Concierge"},"default_reply_to":{"type":"string","format":"email","minLength":5,"description":"Default `Reply-To` address stamped on mail sent from this mailbox."},"receive_policy":{"$ref":"#/components/schemas/MailboxReceivePolicy"},"retention_tier":{"$ref":"#/components/schemas/MailboxRetentionTier"},"metadata":{"type":"object","additionalProperties":true,"description":"Key/value data to attach to the mailbox. Up to 2 KB. Keys starting with `__bird` are reserved."}},"example":{"workspace_id":"ws_01krdgeqcxet5s7t44vh8rt9mg","local_part":"esim","display_name":"eSIM"}},"AdminRealtimeWorkspace":{"type":"object","additionalProperties":false,"description":"One workspace using Bird Realtime in this region, with how many apps it holds. Realtime app rows are regional and carry only the workspace, so the owning organization and the display names are resolved by the caller from the global workspace admin read (GET /admin/workspaces/{workspace_id}).\n","required":["cursor","workspace_id","app_count","first_app_created_at","last_app_created_at"],"properties":{"cursor":{"type":"string","minLength":1,"description":"Opaque pagination boundary for this exact row, encoding (last_app_created_at, workspace_id). Reusable across regions; pass it unchanged as `starting_after` or `ending_before`.\n"},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"app_count":{"type":"integer","format":"int64","minimum":1,"description":"Realtime apps the workspace currently holds. Apps are hard-deleted, so this is a live count, never a lifetime total.","example":3},"regions":{"type":"array","items":{"$ref":"#/components/schemas/Region"},"description":"The Bird regions this workspace's apps are pinned to. Usually one; a workspace may hold apps in several."},"first_app_created_at":{"type":"string","format":"date-time","minLength":1,"description":"When the workspace created its first Realtime app (its adoption date)."},"last_app_created_at":{"type":"string","format":"date-time","minLength":1,"description":"When the workspace most recently created a Realtime app."}}},"AdminRealtimeAdoptionSummary":{"type":"object","additionalProperties":false,"description":"Region-wide Realtime adoption totals, computed over every workspace in the region rather than the returned page, so they stay stable while paging.\n","required":["total_workspaces","total_apps"],"properties":{"total_workspaces":{"type":"integer","format":"int64","description":"Workspaces in this region holding at least one Realtime app.","example":12},"total_apps":{"type":"integer","format":"int64","description":"Realtime apps in this region, across every workspace.","example":27}}},"AdminRealtimeWorkspaceList":{"allOf":[{"type":"object","required":["data","summary"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminRealtimeWorkspace"}},"summary":{"$ref":"#/components/schemas/AdminRealtimeAdoptionSummary"}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminEmailCompetitiveWorkspace":{"type":"object","additionalProperties":false,"description":"One workspace watching competitor brands in this region, with the size and age of its watchlist. The organization travels on the row because the competitor quota is organization-scoped, so no second lookup is needed to attribute it; the display names for both still come from the global admin reads (GET /admin/workspaces/{workspace_id}, GET /admin/organizations/{organization_id}).\n","required":["cursor","workspace_id","organization_id","brand_count","first_brand_added_at","last_brand_added_at"],"properties":{"cursor":{"type":"string","minLength":1,"description":"Opaque pagination boundary for this exact row, encoding (last_brand_added_at, workspace_id). Reusable across regions; pass it unchanged as `starting_after` or `ending_before`.\n"},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"brand_count":{"type":"integer","format":"int64","minimum":1,"description":"Competitor brands the workspace currently watches. Brands are removed outright rather than archived, so this is a live count, never a lifetime total.","example":4},"first_brand_added_at":{"type":"string","format":"date-time","minLength":1,"description":"When the oldest brand still on this watchlist was added. Not the date the workspace first used the product, since a brand added and later removed leaves no record."},"last_brand_added_at":{"type":"string","format":"date-time","minLength":1,"description":"When the workspace most recently added a brand to its watchlist."}}},"AdminEmailCompetitiveAdoptionSummary":{"type":"object","additionalProperties":false,"description":"Region-wide Competitive Insights adoption totals, computed over every watchlist in the region rather than the returned page, so they stay stable while paging. Filters on the list do not narrow them.\n","required":["total_workspaces","total_brands","total_organizations"],"properties":{"total_workspaces":{"type":"integer","format":"int64","description":"Workspaces in this region watching at least one competitor brand.","example":9},"total_brands":{"type":"integer","format":"int64","description":"Competitor brands watched in this region, across every workspace. The same brand watched in two workspaces counts twice, which is how the quota counts it too.","example":31},"total_organizations":{"type":"integer","format":"int64","description":"Organizations in this region with at least one workspace watching a brand. Lower than the workspace total wherever a customer runs several workspaces.","example":6}}},"AdminEmailCompetitiveWorkspaceList":{"allOf":[{"type":"object","required":["data","summary"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEmailCompetitiveWorkspace"}},"summary":{"$ref":"#/components/schemas/AdminEmailCompetitiveAdoptionSummary"}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSurfacesClass":{"type":"string","enum":["query","mutation"],"x-enum-varnames":["AdminSurfacesClassQuery","AdminSurfacesClassMutation"],"description":"Restrict counts to query (GET/HEAD) or mutation (all other methods) traffic. Omit to count both. Rows captured before the class field existed count as neither.\n","example":"mutation"},"AdminEmailCompetitiveUsageSummary":{"type":"object","additionalProperties":false,"description":"Competitive Insights request, error and latency figures for the range, with the tenants that reached it.","required":["total_requests","error_requests","latency_p95_ms","active_organizations","active_workspaces"],"properties":{"total_requests":{"type":"integer","minimum":0,"description":"Requests customers made to Competitive Insights over the range.","example":4820},"error_requests":{"type":"integer","minimum":0,"description":"Requests that failed with a server error. On this product they are mostly the email panel being unreachable, which is reported as a retryable failure, so a rise here usually reflects the panel rather than this API.","example":37},"latency_p95_ms":{"type":"integer","minimum":0,"description":"The 95th-percentile request duration in milliseconds. Every figure on this product is fetched from the email panel while the request runs, so this is the measure of how close those reads sit to their deadline. Reported as 0 when the range holds no requests.","example":2380},"active_organizations":{"type":"integer","minimum":0,"description":"Organizations that made at least one request over the range.","example":6},"active_workspaces":{"type":"integer","minimum":0,"description":"Workspaces that made at least one request. Higher than the organization count wherever a customer uses the product in several workspaces.","example":9}}},"AdminEmailCompetitiveUsageBucket":{"type":"object","additionalProperties":false,"description":"One day of Competitive Insights traffic.","required":["bucket","requests","errors","latency_p95_ms"],"properties":{"bucket":{"type":"string","format":"date-time","minLength":1,"description":"Start of the day, in UTC."},"requests":{"type":"integer","minimum":0,"description":"Requests served that day.","example":210},"errors":{"type":"integer","minimum":0,"description":"Requests that failed with a server error that day.","example":3},"latency_p95_ms":{"type":"integer","minimum":0,"description":"The 95th-percentile request duration that day, in milliseconds. Merged from the underlying distribution rather than averaged across hours, so it is the day's real percentile.","example":2410}}},"AdminEmailCompetitiveUsageOperation":{"type":"object","additionalProperties":false,"description":"One Competitive Insights operation's traffic and health over the range.","required":["operation","requests","errors","latency_p95_ms"],"properties":{"operation":{"type":"string","minLength":1,"description":"The API operation, by the name it carries in the specification.","example":"getEmailCompetitiveWatchlist"},"requests":{"type":"integer","minimum":0,"description":"Requests to this operation over the range.","example":1840},"errors":{"type":"integer","minimum":0,"description":"Requests to this operation that failed with a server error.","example":21},"latency_p95_ms":{"type":"integer","minimum":0,"description":"The 95th-percentile duration of this operation, in milliseconds.","example":3120}}},"AdminEmailCompetitiveUsage":{"type":"object","additionalProperties":false,"description":"One region's Competitive Insights usage over a reporting range: the totals, a daily trend, and a row per operation.\nRequest and error counts come from a pre-aggregated rollup and are estimates within about a percent, which is accurate enough to read as traffic but not to reconcile against another source request by request. The tenant counts are exact.\n","required":["region","range_from","range_to","summary","series","operations"],"properties":{"region":{"type":"string","minLength":1,"description":"The region these figures cover.","example":"eu1"},"range_from":{"type":"string","format":"date-time","minLength":1,"description":"Start of the reporting range, inclusive. Rounded down to the hour, since the figures are rolled up hourly."},"range_to":{"type":"string","format":"date-time","minLength":1,"description":"End of the reporting range, exclusive."},"summary":{"$ref":"#/components/schemas/AdminEmailCompetitiveUsageSummary"},"series":{"type":"array","description":"One entry per day in the range, oldest first. A day the product served no requests is absent rather than present as a zero, so a chart can show a gap as a gap.","items":{"$ref":"#/components/schemas/AdminEmailCompetitiveUsageBucket"}},"operations":{"type":"array","description":"The operations customers reached, busiest first. This is what localises a slow or failing endpoint, which a product-wide figure cannot.","items":{"$ref":"#/components/schemas/AdminEmailCompetitiveUsageOperation"}}}},"AdminEmailCompetitiveUsageOrg":{"type":"object","additionalProperties":false,"description":"One organization's Competitive Insights traffic over the range.","required":["organization_id","requests","errors","latency_p95_ms"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"requests":{"type":"integer","minimum":0,"description":"Requests this organization made over the range.","example":940},"errors":{"type":"integer","minimum":0,"description":"Requests from this organization that failed with a server error.","example":8},"latency_p95_ms":{"type":"integer","minimum":0,"description":"The 95th-percentile duration of this organization's requests, in milliseconds. A customer watching many brands pays a longer read than one watching a few, so this varies by tenant rather than only by operation.","example":2890}}},"AdminEmailCompetitiveUsageOrgs":{"type":"object","additionalProperties":false,"description":"One region's Competitive Insights traffic by organization, busiest first.\nThese counts are exact, unlike the totals on the usage read, which are estimated from a rollup. A tenant's requests will therefore not sum to that total request for request.\n","required":["region","range_from","range_to","data"],"properties":{"region":{"type":"string","minLength":1,"description":"The region these figures cover.","example":"eu1"},"range_from":{"type":"string","format":"date-time","minLength":1,"description":"Start of the reporting range, inclusive."},"range_to":{"type":"string","format":"date-time","minLength":1,"description":"End of the reporting range, exclusive."},"data":{"type":"array","description":"Organizations that reached the product, busiest first. Names are resolved separately by the caller.","items":{"$ref":"#/components/schemas/AdminEmailCompetitiveUsageOrg"}}}},"AdminStatsGrain":{"type":"string","minLength":1,"enum":["day","hour","month"],"readOnly":true,"description":"The bucket grain of the series, one of `day`, `hour`, or `month`.","example":"day"},"AdminEmailStatsPeriod":{"type":"object","additionalProperties":false,"description":"The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to.\n","required":["from","to","grain"],"properties":{"from":{"type":"string","minLength":1,"readOnly":true,"description":"Inclusive start of the window. A calendar day (YYYY-MM-DD) on the day and month grains, an RFC 3339 timestamp on the hour grain.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"readOnly":true,"description":"Inclusive end of the window. A calendar day (YYYY-MM-DD) on the day and month grains, an RFC 3339 timestamp on the hour grain.","example":"2026-05-31"},"grain":{"$ref":"#/components/schemas/AdminStatsGrain","readOnly":true},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The instant the statistics in this response are current to. Statistics are served from a rolling aggregation that refreshes every few seconds. A single series aggregating every organization in the region can be up to 5 minutes behind that; figures for one organization, including a ranking that reports each organization separately, are within those few seconds. Null when the freshness boundary is not being reported.\n","example":"2026-05-31T14:03:10Z"}}},"AdminEmailBlockStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Blocked volume for the scope of the containing row (a time bucket, a breakdown dimension, or the whole period). Staff-only: no customer stats surface reports blocked volume, and a blocked message still counts toward the sender's accepted volume and their bill.\n","required":["blocked"],"properties":{"blocked":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"default":0,"description":"Distinct **messages** spam enforcement blocked before sending. Counted at the message level, unlike `block_rate` beside it. See that field.\n","example":318},"block_rate":{"type":["number","null"],"readOnly":true,"description":"Blocked **recipients** as a share of accepted recipients, between 0 and 1, or null when nothing was accepted or the request's `fields` left out `delivery`, which carries the denominator.\nRecipient-denominated, unlike the `blocked` count above it. That is deliberate: every other rate in this response (`delivery_rate`, `bounce_rate`, `complaint_rate`, `open_rate`) is computed over recipients, so a message-denominated rate here would invite a comparison it cannot support.\nThe denominator **includes** the blocked recipients. A message is accepted in processing, before enforcement decides whether to send it, so a blocked recipient has already been counted as accepted. So this reads \"what share of what we took in did we block\", never \"blocked over actually-sent\", and it cannot exceed 1.\n","example":0.0072}}},"AdminEmailStatsTotals":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Aggregate totals across the whole window. Each count is the sum of per-bucket distinct counts, so a recipient (or message) active in two buckets contributes one to each; this matches common provider reporting and is not a window-distinct count. Latency percentiles, by contrast, are computed across the whole window.\n","required":["sends_accepted"],"properties":{"sends_accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct email messages accepted across the window, counted at the message level (one per accepted send regardless of recipient count).","example":1240100},"delivery":{"readOnly":true,"description":"Present unless left out of the request's `fields`.","allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"description":"Present unless left out of the request's `fields`.","allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"description":"Present unless left out of the request's `fields`.","allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"block":{"readOnly":true,"description":"Present unless left out of the request's `fields`.","allOf":[{"$ref":"#/components/schemas/AdminEmailBlockStats"}]}}},"AdminEmailStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Aggregate stats for one time bucket (a UTC calendar day, hour, or month), bucketed by event time. Buckets with no activity are included with zero counts.\n","required":["bucket","sends_accepted"],"properties":{"bucket":{"type":"string","minLength":1,"readOnly":true,"description":"The day or month start (YYYY-MM-DD) or hour (RFC 3339, on the hour, UTC) this point covers, matching the requested grain.","example":"2026-05-25"},"sends_accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct email messages accepted in this bucket, counted at the message level.","example":41200},"delivery":{"readOnly":true,"description":"Present unless left out of the request's `fields`.","allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"description":"Present unless left out of the request's `fields`.","allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"description":"Present unless left out of the request's `fields`.","allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"block":{"readOnly":true,"description":"Present unless left out of the request's `fields`.","allOf":[{"$ref":"#/components/schemas/AdminEmailBlockStats"}]}}},"AdminEmailStatsResponse":{"type":"object","additionalProperties":false,"description":"Email sending, volume, deliverability, and engagement statistics over time, for one customer organization or platform-wide within a single region. `series` is one row per bucket in chronological order; `totals` aggregates the whole window.\n","required":["region","period","totals","series"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/AdminEmailStatsPeriod"},"totals":{"$ref":"#/components/schemas/AdminEmailStatsTotals"},"series":{"type":"array","readOnly":true,"description":"One row per bucket (day or hour, per the grain) in chronological order. Buckets with no activity are included with zero counts.","items":{"$ref":"#/components/schemas/AdminEmailStatsPoint"}}}},"AdminEmailStatsByMailboxProviderResponse":{"type":"object","additionalProperties":false,"description":"Per-mailbox-provider breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `delivered`) descending and capped at the requested `limit` (default 50, max 200).\n","required":["region","period","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Mailbox-provider breakdown rows, ranked by the `sort` metric (default `delivered`) descending. Empty when no eligible activity occurred in the period.","items":{"$ref":"#/components/schemas/EmailMailboxProviderStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct mailbox providers with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":14}}},"AdminEmailStatsByBounceCodeResponse":{"type":"object","additionalProperties":false,"description":"Per-SMTP-code bounce breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `bounced`) descending and capped at the requested `limit` (default 50, max 200).\n","required":["region","period","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Bounce-code breakdown rows, ranked by the `sort` metric (default `bounced`) descending. Empty when no bounces occurred in the period.","items":{"$ref":"#/components/schemas/EmailBounceCodeStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct SMTP error codes with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":17}}},"AdminEmailStatsSortMetric":{"type":"string","default":"processed","description":"Metric to rank an admin breakdown's rows by, applied descending. This is the shared customer sortable-metric set (EmailStatsSortMetric) plus the admin-only `blocked`, because the by-workspace ranking that uses it reports blocked volume beside the same full delivery, engagement, and latency block a customer row carries. Any count or rate can be used. A row whose rate is undefined, because its denominator was zero, sorts last. A bounce sub-type is nested under `bounces` in each row, so its sort name reflects that, for example `bounces.hard` and `bounces.hard_rate`.\n","enum":["processed","delivered","bounced","complained","deferred","rejected","blocked","oob_bounces","bounces.hard","bounces.soft","bounces.admin","bounces.block","bounces.undetermined","opens","opens_non_prefetched","unique_opens","unique_opens_non_prefetched","clicks","unique_clicks","unsubscribes","delivery_rate","bounce_rate","complaint_rate","open_rate","click_rate","unsubscribe_rate","bounces.hard_rate","bounces.soft_rate","bounces.admin_rate","bounces.block_rate","bounces.undetermined_rate"]},"AdminEmailWorkspaceStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One workspace's traffic within the window. Ranking workspaces against each other is staff-only: it compares senders, which no customer-facing surface may do.\n","required":["workspace_id","delivery","engagement","latency","block"],"properties":{"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"The workspace this row aggregates."},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EmailLatencyStats"}]},"block":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminEmailBlockStats"}]}}},"AdminEmailStatsByWorkspaceResponse":{"type":"object","additionalProperties":false,"description":"Per-workspace breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `blocked`) descending and capped at the requested `limit` (default 50, max 200). Ranking senders against each other is what makes this staff-only.\n","required":["region","period","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/EmailStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Breakdown rows, ranked by the `sort` metric descending. Empty when nothing was sent in the period.","items":{"$ref":"#/components/schemas/AdminEmailWorkspaceStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct workspaces with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window.","example":42}}},"AdminEmailSmtpConnection":{"allOf":[{"$ref":"#/components/schemas/EmailSmtpConnection"},{"type":"object","additionalProperties":false,"required":["organization_id","workspace_id"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"}}}]},"AdminEmailSmtpConnectionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEmailSmtpConnection"},"description":"Live SMTP submission connections in this region. Unfiltered this spans every organization; with `organization_id` it is scoped to one.\n"}}},"AdminTopOrganization":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One organization in the top-senders ranking, with its message volume and delivery rate over the requested window.\n","required":["organization_id","name","volume","delivered"],"properties":{"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"The organization these figures are for."},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the organization.","example":"Acme Retail Group"},"volume":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of messages the organization had accepted for sending in the window.","example":96200000},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of the organization's messages confirmed delivered in the window.","example":94800000},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of accepted messages that were delivered, between 0 and 1, or null when no messages were accepted in the window.","example":0.986}}},"AdminTopOrganizationList":{"type":"object","additionalProperties":false,"description":"The organizations sending the most email in a single region over the requested window, ranked by volume. Combine the regions to see a global ranking.\n","required":["region","period","organizations"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these figures are drawn from."},"period":{"$ref":"#/components/schemas/AdminEmailStatsPeriod"},"organizations":{"type":"array","readOnly":true,"description":"Organizations ranked by email volume, highest first.","items":{"$ref":"#/components/schemas/AdminTopOrganization"}}}},"AdminCurrencyTotal":{"type":"object","additionalProperties":false,"readOnly":true,"description":"A monetary total in a single currency. Totals are reported per currency and never summed across currencies.","required":["currency_code","amount","invoice_count"],"properties":{"currency_code":{"type":"string","minLength":3,"maxLength":3,"readOnly":true,"description":"ISO 4217 currency code, uppercase.","example":"EUR"},"amount":{"type":"string","minLength":1,"readOnly":true,"description":"Total amount as a decimal string, in major currency units.","example":"12345.00"},"invoice_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of invoices contributing to this total.","example":12}}},"AdminChannelInterest":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Notify-me interest tally for a single not-yet-live dashboard channel.","required":["channel","total","last_interested_at"],"properties":{"channel":{"type":"string","minLength":1,"readOnly":true,"description":"The channel users asked to be notified about when it ships.","example":"whatsapp"},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of distinct users who asked to be notified about this channel.","example":42},"last_interested_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the most recent interest was registered, or null when no users are interested yet.","example":"2026-06-18T10:00:00Z"}}},"AdminOverview":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Platform-wide operator KPIs aggregated from global data. Regional figures (email volume, wallet balances) are fetched separately.","required":["organization_count","new_organization_count_30d","active_subscription_count","churned_subscription_count_30d","accounts_receivable","invoiced_revenue_30d","channel_interest"],"properties":{"organization_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of active organizations on the platform.","example":1280},"new_organization_count_30d":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations created in the last 30 days.","example":42},"active_subscription_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations with an active plan subscription.","example":540},"churned_subscription_count_30d":{"type":"integer","minimum":0,"readOnly":true,"description":"Plan subscriptions canceled in the last 30 days.","example":7},"accounts_receivable":{"type":"array","readOnly":true,"description":"Outstanding (issued but not fully paid) invoice exposure, broken down by currency.","items":{"$ref":"#/components/schemas/AdminCurrencyTotal"}},"invoiced_revenue_30d":{"type":"array","readOnly":true,"description":"Total invoiced in the last 30 days, broken down by currency. Annualize for a revenue run-rate.","items":{"$ref":"#/components/schemas/AdminCurrencyTotal"}},"channel_interest":{"type":"array","readOnly":true,"description":"Notify-me interest per not-yet-live channel — how many users asked to be notified and when the latest request came in. Includes channels with no interest yet.","items":{"$ref":"#/components/schemas/AdminChannelInterest"}}}},"AdminConsistencyLsnWriter":{"type":"object","additionalProperties":false,"readOnly":true,"x-audiences":["admin"],"description":"The primary write position and the cost of reading it.","required":["latency_ms","error"],"properties":{"lsn":{"type":"string","minLength":1,"readOnly":true,"description":"The primary's current write log position. Absent when the probe failed.","example":"0/16B3748"},"latency_ms":{"type":"number","format":"double","readOnly":true,"description":"Round-trip time to read the write position, in milliseconds. A high value points to a cross-region primary.","example":12.4},"error":{"type":["string","null"],"readOnly":true,"description":"The probe error, or null when the write position was read successfully.","example":null}}},"AdminConsistencyLsnReader":{"type":"object","additionalProperties":false,"readOnly":true,"x-audiences":["admin"],"description":"The in-region read replica's applied position and the cost of reading it.","required":["latency_ms","error"],"properties":{"durable_lsn":{"type":"string","minLength":1,"readOnly":true,"description":"The in-region replica's applied (durable) log position. Absent when the probe failed.","example":"0/16B3720"},"latency_ms":{"type":"number","format":"double","readOnly":true,"description":"Round-trip time to read the replica's applied position, in milliseconds.","example":1.1},"error":{"type":["string","null"],"readOnly":true,"description":"The probe error, or null when the replica position was read successfully.","example":null}}},"AdminConsistencyLsnDiagnostics":{"type":"object","additionalProperties":false,"readOnly":true,"x-audiences":["admin"],"description":"A point-in-time replication-consistency probe for one region: the current write and read log positions, the lag between them, and how long a read waited to catch up to the latest write. Each response covers the single region that served it; query each region separately for a platform-wide view.","required":["region","writer","reader","lag_bytes","lag_valid","caught_up","caught_up_waited_ms"],"properties":{"region":{"type":"string","minLength":1,"readOnly":true,"description":"The region this probe was measured in.","example":"eu1"},"writer":{"$ref":"#/components/schemas/AdminConsistencyLsnWriter"},"reader":{"$ref":"#/components/schemas/AdminConsistencyLsnReader"},"lag_bytes":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"How far the in-region replica trails the primary, in bytes. Meaningful only when lag_valid is true.","example":40},"lag_valid":{"type":"boolean","readOnly":true,"description":"Whether both positions were read and comparable, so lag_bytes is meaningful.","example":true},"caught_up":{"type":"boolean","readOnly":true,"description":"Whether the in-region replica reached the latest write position within the catch-up wait window.","example":true},"caught_up_waited_ms":{"type":"number","format":"double","readOnly":true,"description":"How long the catch-up wait actually took, in milliseconds.","example":3.2}}},"AdminGrowthMrr":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Monthly recurring revenue in a single currency. Annual subscriptions are normalized to a monthly figure; figures are never summed across currencies.","required":["currency_code","mrr","at_risk","subscription_count","at_risk_subscription_count"],"properties":{"currency_code":{"type":"string","minLength":1,"readOnly":true,"description":"ISO 4217 currency code for this revenue total.","example":"USD"},"mrr":{"type":"string","minLength":1,"readOnly":true,"description":"Monthly recurring revenue, as a decimal string in currency units.","example":"12450.00"},"at_risk":{"type":"string","minLength":1,"readOnly":true,"description":"The portion of this currency's recurring revenue set to cancel at the end of the current period, as a decimal string in currency units. Still billed this period but lost at renewal, so net forward revenue is mrr minus at_risk.","example":"1200.00"},"subscription_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of active paid subscriptions contributing to this total.","example":124},"at_risk_subscription_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of subscriptions in this currency set to cancel at the end of the current period.","example":6}}},"AdminGrowthPlanTier":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The number of organizations currently subscribed to a single plan tier.","required":["slug","name","weight","active_count"],"properties":{"slug":{"type":"string","minLength":1,"readOnly":true,"description":"Stable identifier of the plan product.","example":"plan_growth_100k"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the plan.","example":"Growth 100k"},"tier":{"type":"string","readOnly":true,"description":"Tier label of the plan, or empty when the plan has no tier configured.","example":"growth"},"weight":{"type":"integer","readOnly":true,"description":"Ordering weight, ascending from entry-level to top tier.","example":30},"active_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations with an active subscription to this plan.","example":18}}},"AdminGrowthFunnel":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Platform-wide activation funnel for the window's signup cohort, by milestone reached. Each stage counts organizations from the same cohort.\n","required":["signed_up","added_domain","verified_domain","sent_test","sent_message","converted_to_paid","dedicated_ip"],"properties":{"signed_up":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations created in the window.","example":142},"added_domain":{"type":"integer","minimum":0,"readOnly":true,"description":"Cohort organizations that added a sending domain, whether or not it is verified yet.","example":72},"verified_domain":{"type":"integer","minimum":0,"readOnly":true,"description":"Cohort organizations that verified a sending domain.","example":58},"sent_test":{"type":"integer","minimum":0,"readOnly":true,"description":"Cohort organizations that sent at least a test message from the shared onboarding domain, including those that went on to send from their own domain.","example":49},"sent_message":{"type":"integer","minimum":0,"readOnly":true,"description":"Cohort organizations that sent a message from their own verified domain.","example":41},"converted_to_paid":{"type":"integer","minimum":0,"readOnly":true,"description":"Cohort organizations that started a paid plan.","example":6},"dedicated_ip":{"type":"integer","minimum":0,"readOnly":true,"description":"Cohort organizations that bought a dedicated IP.","example":1}}},"AdminGrowthBoardRow":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One organization on the hot-accounts or enterprise-leads board.\n\nEvery figure on the row is a precomputed snapshot recomputed hourly, so it can trail\nlive sending by up to an hour. For the authoritative current volume of one\norganization, read that channel's own stats endpoint.\n","required":["organization_id","name","plan","volume","volume_email","volume_sms","volume_whatsapp","volume_verify","trend_pct","verified_domain","dedicated_ip","utilization_pct","score","band"],"properties":{"organization_id":{"type":"string","minLength":1,"readOnly":true,"description":"Organization identifier."},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Organization display name.","example":"Northwind Logistics"},"owner_email":{"type":"string","readOnly":true,"description":"Email address of the organization owner, for outreach.","example":"jordan@northwind.io"},"first_domain":{"type":"string","readOnly":true,"description":"The first sending domain the organization added; empty when none has been added.","example":"mail.northwind.io"},"plan":{"type":"string","minLength":1,"readOnly":true,"description":"The organization's current plan name.","example":"Growth 250k"},"volume":{"type":"integer","minimum":0,"readOnly":true,"description":"Accepted messages across every channel over the selected window. This is the authoritative total for the row; the per-channel figures below break it down and sum to it.","example":360000},"volume_email":{"type":"integer","minimum":0,"readOnly":true,"description":"Accepted email messages over the selected window.","example":320000},"volume_sms":{"type":"integer","minimum":0,"readOnly":true,"description":"Accepted SMS messages over the selected window.","example":28000},"volume_whatsapp":{"type":"integer","minimum":0,"readOnly":true,"description":"Accepted WhatsApp messages over the selected window.","example":9000},"volume_verify":{"type":"integer","minimum":0,"readOnly":true,"description":"Verifications created over the selected window.","example":3000},"trend_pct":{"type":["number","null"],"readOnly":true,"description":"Week-over-week change in volume, as a percentage; null when the prior week had no volume.","example":18.4},"verified_domain":{"type":"boolean","readOnly":true,"description":"Whether the organization has a verified sending domain."},"dedicated_ip":{"type":"boolean","readOnly":true,"description":"Whether the organization has a dedicated IP."},"utilization_pct":{"type":["number","null"],"readOnly":true,"description":"Trailing-30-day usage as a percentage of the plan's monthly cap; null when the plan has no cap.","example":86},"monthly_cap":{"type":["integer","null"],"readOnly":true,"description":"The plan's monthly message cap, for showing usage against the limit; null when the plan has no cap.","example":2500000},"score":{"type":"integer","minimum":0,"readOnly":true,"description":"Lead score, weighted across the growth signals the organization meets.","example":13},"band":{"type":"string","minLength":1,"enum":["cold","warm","hot","qualified"],"readOnly":true,"description":"Qualitative bucket the score falls into.","example":"qualified"}}},"AdminGrowthSummary":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Platform-wide growth metrics for a reporting window — acquisition, revenue, the activation funnel, and the outreach boards. Period figures cover the selected range; snapshot figures are as of now.","required":["window","range_from","range_to","as_of","new_organizations","new_organizations_prev","active_paid_subscriptions","non_renewing_subscriptions","churned_subscriptions","churned_subscriptions_prev","churned_mrr","free_to_paid_conversions","free_to_paid_conversions_prev","mrr","plans_by_tier","channel_interest","median_volume_per_org","activation_cohort_signups","activation_cohort_sent","time_to_first_send_seconds","cohort_volume","at_risk_accounts","funnel","hot_band_accounts","hot_accounts","enterprise_leads"],"properties":{"window":{"type":["string","null"],"enum":["today","yesterday","7d","30d",null],"readOnly":true,"description":"The preset reporting window these figures cover, or null when a custom from/to range was used.","example":"30d"},"range_from":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"Start of the reporting range these period figures cover, inclusive.","example":"2026-06-21T00:00:00Z"},"range_to":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"End of the reporting range these period figures cover, exclusive.","example":"2026-06-28T00:00:00Z"},"as_of":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"The moment this response was assembled. Snapshot figures do not depend on the reporting range. Billing snapshots (active subscriptions, MRR, plan tiers) are read live at this moment; the per-organization figures (the boards, the activation funnel, the cohort, at-risk, the median) come from a rollup recomputed hourly, so they are normally under an hour older than this timestamp, and older still whenever a recompute is skipped or fails every attempt.\n","example":"2026-06-28T09:30:00Z"},"new_organizations":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations created during the range.","example":142},"new_organizations_prev":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations created during the equal-length period immediately before the range, for comparison.","example":118},"active_paid_subscriptions":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations with an active paid plan subscription, as of now.","example":540},"non_renewing_subscriptions":{"type":"integer","minimum":0,"readOnly":true,"description":"Active paid plan subscriptions set to not renew — announced churn that has not yet lapsed. As of now.","example":12},"churned_subscriptions":{"type":"integer","minimum":0,"readOnly":true,"description":"Paid plan subscriptions canceled during the range.","example":7},"churned_subscriptions_prev":{"type":"integer","minimum":0,"readOnly":true,"description":"Paid plan subscriptions canceled during the equal-length period immediately before the range, for comparison.","example":5},"churned_mrr":{"type":"array","readOnly":true,"description":"Monthly recurring revenue lost per currency to paid plan subscriptions canceled during the range.","items":{"$ref":"#/components/schemas/AdminGrowthMrr"}},"free_to_paid_conversions":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations whose first paid plan subscription started during the range.","example":9},"free_to_paid_conversions_prev":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations whose first paid plan subscription started during the equal-length period immediately before the range, for comparison.","example":6},"mrr":{"type":"array","readOnly":true,"description":"Monthly recurring revenue per currency from active paid subscriptions, as of now.","items":{"$ref":"#/components/schemas/AdminGrowthMrr"}},"plans_by_tier":{"type":"array","readOnly":true,"description":"Active subscription counts by plan tier, ordered from entry-level to top. As of now.","items":{"$ref":"#/components/schemas/AdminGrowthPlanTier"}},"channel_interest":{"type":"array","readOnly":true,"description":"Demand for not-yet-live channels — distinct users who asked to be notified when each channel ships, over the range.","items":{"$ref":"#/components/schemas/AdminChannelInterest"}},"median_volume_per_org":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Median accepted-message volume among sending organizations over the range. Exact for the preset windows; null for a custom range, where the regional figures carry an estimate instead.","example":12},"activation_cohort_signups":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations in the current onboarding cohort (created recently). As of now, independent of the selected range.","example":142},"activation_cohort_sent":{"type":"integer","minimum":0,"readOnly":true,"description":"Current-cohort organizations that have since sent a real (non-test) message. As of now.","example":41},"time_to_first_send_seconds":{"type":["integer","null"],"readOnly":true,"description":"Median seconds from signup to first send among current-cohort organizations that have sent; null when none have. As of now.","example":18720},"cohort_volume":{"type":"integer","minimum":0,"readOnly":true,"description":"Accepted messages from the current onboarding cohort — the email volume coming from recently created accounts. As of now.","example":1840},"at_risk_accounts":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations still sending in the last 7 days whose volume dropped 25% or more week-over-week — an early churn-risk signal. Refreshed hourly, independent of the selected range.","example":6},"funnel":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminGrowthFunnel"}]},"hot_band_accounts":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations scoring in the hot band or above. The platform-wide count, independent of how many rows the hot-accounts board shows. Refreshed hourly, independent of the selected range.","example":23},"hot_accounts":{"type":"array","readOnly":true,"description":"The self-serve organizations most worth proactive outreach, highest score first, capped at ten. Only organizations that have sent a real message and score at least warm appear, so a quiet week returns fewer rows rather than low-value ones. As of now.","items":{"$ref":"#/components/schemas/AdminGrowthBoardRow"}},"enterprise_leads":{"type":"array","readOnly":true,"description":"Organizations near the top self-serve ceiling, to hand to sales. As of now.","items":{"$ref":"#/components/schemas/AdminGrowthBoardRow"}}}},"AdminGrowthAccounts":{"type":"object","additionalProperties":false,"readOnly":true,"description":"A page of the organization leaderboard together with the total number of tracked organizations.","required":["data","total"],"properties":{"data":{"type":"array","readOnly":true,"description":"Organizations on this page, ranked by lead score from highest to lowest.","items":{"$ref":"#/components/schemas/AdminGrowthBoardRow"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of tracked organizations. The data array may be capped below this by the requested limit.","example":1280}}},"AdminGrowthSparkPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One bucket of a volume card's daily sparkline.","required":["bucket","count"],"properties":{"bucket":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Start of the bucket."},"count":{"type":"integer","minimum":0,"readOnly":true,"description":"The bucket's count, in the unit the enclosing card's sparkline states: accepted messages on the email, SMS and WhatsApp cards, verification sessions created on the Verify card.\n","example":1820}}},"AdminGrowthEmailCard":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Email-volume summary for the window, for the responding region.","required":["messages","messages_prev","delivery_rate","sparkline"],"properties":{"messages":{"type":"integer","minimum":0,"readOnly":true,"description":"Accepted messages in the window.","example":482000},"blocked":{"type":"integer","minimum":0,"readOnly":true,"default":0,"description":"Messages spam enforcement stopped before sending, within the window. Counted at the message level. These are also included in `messages`: a blocked message was accepted before the gate decided, so it counts toward the sender's accepted volume and their bill exactly as any other.\n","example":318},"messages_prev":{"type":"integer","minimum":0,"readOnly":true,"description":"Accepted messages in the equal-length period immediately before the window.","example":401000},"delivery_rate":{"type":["number","null"],"readOnly":true,"description":"Effective delivery rate as a fraction in [0, 1]; (delivered minus out-of-band bounces) over (delivered plus bounced). Out-of-band bounces are late rejections reported after an initial acceptance. Null when there were no delivery outcomes.","example":0.994},"sparkline":{"type":"array","readOnly":true,"description":"Per-day accepted-message counts across the window.","items":{"$ref":"#/components/schemas/AdminGrowthSparkPoint"}}}},"AdminGrowthVolumeCard":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Channel-volume summary for the window, for the responding region.","required":["messages","messages_prev","delivery_rate","sparkline"],"properties":{"messages":{"type":"integer","minimum":0,"readOnly":true,"description":"Accepted messages in the window.","example":482000},"messages_prev":{"type":"integer","minimum":0,"readOnly":true,"description":"Accepted messages in the equal-length period immediately before the window.","example":401000},"delivery_rate":{"type":["number","null"],"readOnly":true,"description":"Delivered messages as a fraction in [0, 1] of accepted messages. Null when there were no delivery outcomes.","example":0.985},"sparkline":{"type":"array","readOnly":true,"description":"Per-day accepted-message counts across the window.","items":{"$ref":"#/components/schemas/AdminGrowthSparkPoint"}}}},"AdminGrowthVerifyCard":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Verification-session summary for the window, for the responding region. This card counts verification sessions, not messages: the OTP messages a verification sends are counted on the SMS, WhatsApp and email cards instead. Every figure here is a distinct count of verification ids taken with a probabilistic aggregate, so it can differ slightly from an exact count at high volume.\n","required":["messages","messages_prev","delivery_rate","sparkline"],"properties":{"messages":{"type":"integer","minimum":0,"readOnly":true,"description":"Verification sessions created in the window. Named `messages` for the shape the channel cards share; the unit is sessions, and one session can send several messages.\n","example":48200},"messages_prev":{"type":"integer","minimum":0,"readOnly":true,"description":"Verification sessions created in the equal-length period immediately before the window.","example":40100},"delivery_rate":{"type":["number","null"],"readOnly":true,"description":"The verified rate, not a delivery rate: sessions verified in the window over sessions created in the window. Each leg is counted at its own event time, so a session created before the window and verified inside it counts in the numerator only. That makes it a ratio over the window rather than a conversion rate for the window's cohort, and it can exceed 1 when completions outnumber starts. Null when no session was created in the window.\n","example":0.82},"sparkline":{"type":"array","readOnly":true,"description":"Per-day counts of verification sessions created across the window.","items":{"$ref":"#/components/schemas/AdminGrowthSparkPoint"}}}},"AdminGrowthRealtimeCard":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Realtime usage summary for the window, for the responding region. Realtime publishes into live channels rather than to an address, so there is no delivery outcome to report; the concurrency gauge takes that slot instead.\n","required":["messages","messages_prev","peak_connections","sparkline"],"properties":{"messages":{"type":"integer","minimum":0,"readOnly":true,"description":"Messages published into Realtime channels in the window.","example":1920000},"messages_prev":{"type":"integer","minimum":0,"readOnly":true,"description":"Messages published in the equal-length period immediately before the window.","example":1370000},"peak_connections":{"type":"integer","minimum":0,"readOnly":true,"description":"The busiest moment of the window, in concurrent connections. Connections are a concurrency gauge, not a count: apps' peaks add within a reporting bucket, because they were connected at the same time, and buckets take the maximum across the window rather than a sum. The platform figure is the maximum across regions.\n","example":14200},"sparkline":{"type":"array","readOnly":true,"description":"Per-day published-message counts across the window.","items":{"$ref":"#/components/schemas/AdminGrowthSparkPoint"}}}},"AdminGrowthVolumeBucket":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One bucket of the per-organization email volume distribution, for estimating the median email volume per organization across regions when a custom range is in effect.\n","required":["le","count"],"properties":{"le":{"type":"integer","minimum":0,"readOnly":true,"description":"Upper bound of the bucket, inclusive: organizations whose accepted-email volume is at most this and above the previous bucket's bound.","example":100},"count":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations whose email volume falls in this bucket.","example":37}}},"AdminGrowthWalletTopUpCurrency":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One currency's wallet top-up aggregates for the window, with the equal-length prior period for deltas. Amounts are never summed across currencies. An organization's wallet holds a single currency and lives in one region, so per-currency and cross-region organization counts add without double-counting.","required":["currency_code","amount","amount_prev","count","count_prev","organizations","organizations_prev"],"properties":{"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode"},"amount":{"type":"string","minLength":1,"readOnly":true,"description":"Total gross amount topped up in the window, as a decimal string in currency units.","example":"1250.00"},"amount_prev":{"type":"string","minLength":1,"readOnly":true,"description":"Total gross amount topped up in the equal-length period immediately before the window, as a decimal string in currency units.","example":"980.00"},"count":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of top-up transactions in the window.","example":42},"count_prev":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of top-up transactions in the prior period.","example":35},"organizations":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct organizations that topped up in the window.","example":18},"organizations_prev":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct organizations that topped up in the prior period.","example":15}}},"AdminGrowthWalletTopUpDay":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One (day, currency) bucket of the wallet top-up chart. Days at the edges of a window that starts or ends mid-day cover only the in-window portion.","required":["bucket","currency_code","amount","count"],"properties":{"bucket":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Start of the day bucket (UTC midnight)."},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode"},"amount":{"type":"string","minLength":1,"readOnly":true,"description":"Gross amount topped up in the bucket, as a decimal string in currency units.","example":"120.00"},"count":{"type":"integer","minimum":0,"readOnly":true,"description":"Top-up transactions in the bucket.","example":4}}},"AdminGrowthRegional":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One region's volume, sender, and wallet top-up figures for the window, combined across regions for the platform view.","required":["region","window","email","sms","whatsapp","verify","amb","realtime","distinct_sending_orgs","distinct_sending_orgs_prev","consistent_senders","consistent_senders_prev","top_org_volume","volume_histogram","wallet_topups","wallet_topup_daily"],"properties":{"region":{"type":"string","minLength":1,"readOnly":true,"description":"The region these figures cover.","example":"eu1"},"window":{"type":["string","null"],"enum":["today","yesterday","7d","30d",null],"readOnly":true,"description":"The preset reporting window, or null when a custom from/to range was used.","example":"30d"},"email":{"$ref":"#/components/schemas/AdminGrowthEmailCard"},"sms":{"$ref":"#/components/schemas/AdminGrowthVolumeCard"},"whatsapp":{"$ref":"#/components/schemas/AdminGrowthVolumeCard"},"verify":{"$ref":"#/components/schemas/AdminGrowthVerifyCard"},"amb":{"readOnly":true,"$ref":"#/components/schemas/AdminGrowthVolumeCard","description":"Apple Messages for Business volume. The channel has no delivery receipt, so `delivery_rate` here is the fraction of accepted messages handed to Apple (`sent` over `accepted`)."},"realtime":{"readOnly":true,"$ref":"#/components/schemas/AdminGrowthRealtimeCard","description":"Realtime usage: messages published into live channels, plus the window's peak concurrent connections. The channel has no delivery outcome, so this card carries no rate."},"distinct_sending_orgs":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations in this region that sent at least one email in the window. Email only: the sender figures are folded from email volume, so an organization sending only SMS, WhatsApp, Apple Messages for Business or verifications is not counted here even though its volume is on the cards above.\n","example":312},"distinct_sending_orgs_prev":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations in this region that sent at least one email in the equal-length period immediately before the window.","example":298},"consistent_senders":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations in this region that sent email on at least 70% of the days in the window: steady senders rather than one-off sends.","example":64},"consistent_senders_prev":{"type":"integer","minimum":0,"readOnly":true,"description":"Consistent email senders in the equal-length period immediately before the window.","example":59},"top_org_volume":{"type":"integer","minimum":0,"readOnly":true,"description":"The largest single organization's accepted-email volume in this region over the window.","example":120000},"volume_histogram":{"type":"array","readOnly":true,"description":"Per-organization email volume distribution for the window, bucketed, so the platform median can be estimated across regions.","items":{"$ref":"#/components/schemas/AdminGrowthVolumeBucket"}},"wallet_topups":{"type":"array","readOnly":true,"description":"Wallet top-up aggregates for the window, one entry per currency, with the prior period for deltas. Wallets are region-local, so the platform view sums entries per currency across regions.","items":{"$ref":"#/components/schemas/AdminGrowthWalletTopUpCurrency"}},"wallet_topup_daily":{"type":"array","readOnly":true,"description":"Per-day, per-currency wallet top-up amounts and counts across the window, for the top-up chart.","items":{"$ref":"#/components/schemas/AdminGrowthWalletTopUpDay"}}}},"AdminSurfacesSummary":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One region's cross-surface usage counts for a reporting range. Counts are returned raw so a platform view sums them across regions and derives the shares afterwards.","required":["region","range_from","range_to","total_requests","agent_requests","human_requests","programmatic_requests","error_requests","agent_harnesses","active_orgs"],"properties":{"region":{"type":"string","minLength":1,"readOnly":true,"description":"The region these figures cover.","example":"eu1"},"range_from":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"Start of the reporting range, inclusive."},"range_to":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"End of the reporting range, exclusive."},"total_requests":{"type":"integer","minimum":0,"readOnly":true,"description":"Total requests recorded in this region over the range.","example":152340},"agent_requests":{"type":"integer","minimum":0,"readOnly":true,"description":"Requests an AI agent drove — those on the MCP surface or from a recognized agent caller.","example":4120},"human_requests":{"type":"integer","minimum":0,"readOnly":true,"description":"Requests a person drove interactively — the web dashboard, an interactive command-line session, or a hand-operated client such as a browser or an API workbench.","example":103800},"programmatic_requests":{"type":"integer","minimum":0,"readOnly":true,"description":"Requests an application drove programmatically — an SDK or HTTP library embedded in code, or a custom integration. Excludes agent- and human-driven traffic.","example":44300},"error_requests":{"type":"integer","minimum":0,"readOnly":true,"description":"Requests that returned a server error over the range.","example":120},"agent_harnesses":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct recognized agent harnesses seen in this region over the range.","example":6},"active_orgs":{"type":"integer","minimum":0,"readOnly":true,"description":"Organizations with at least one request in this region over the range.","example":210}}},"AdminSurfacesTimeseriesDimension":{"type":"string","minLength":1,"enum":["surface","caller","driver"],"x-enum-varnames":["AdminSurfacesTimeseriesDimensionSurface","AdminSurfacesTimeseriesDimensionCaller","AdminSurfacesTimeseriesDimensionDriver"],"description":"The dimension usage buckets are broken down by — by surface, by caller (harness), or by driver (agent, human, or programmatic).","example":"surface"},"AdminSurfacesTimeseriesBucket":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One time bucket for one dimension value — the request and error counts for that value in that bucket.","required":["bucket_start","key","requests","errors"],"properties":{"bucket_start":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"Start of the period (UTC) this bucket covers. Buckets are daily for shorter ranges and weekly for longer ones."},"key":{"type":"string","minLength":1,"readOnly":true,"description":"The dimension value this bucket is for.","example":"cli"},"requests":{"type":"integer","minimum":0,"readOnly":true,"description":"Requests for this value in this bucket.","example":820},"errors":{"type":"integer","minimum":0,"readOnly":true,"description":"Requests for this value in this bucket that returned a server error.","example":1}}},"AdminSurfacesTimeseries":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One region's usage buckets over time broken down by the requested dimension, combined across regions for the platform view.","required":["region","dimension","buckets"],"properties":{"region":{"type":"string","minLength":1,"readOnly":true,"description":"The region these buckets cover.","example":"eu1"},"dimension":{"$ref":"#/components/schemas/AdminSurfacesTimeseriesDimension"},"buckets":{"type":"array","readOnly":true,"description":"Time buckets, one per (bucket start, dimension value).","items":{"$ref":"#/components/schemas/AdminSurfacesTimeseriesBucket"}}}},"AdminSurfaceName":{"type":"string","minLength":1,"readOnly":true,"description":"The client surface a request came through — one of dashboard, cli, mcp, sdk-go, sdk-js, sdk-python, or unknown today. An open set: new surfaces may appear over time, so treat any unlisted value as a valid surface rather than an error. `unknown` covers requests with no first-party client header (raw API, browser, or a client predating the header).\n","example":"cli"},"AdminSurfacesKeyCount":{"type":"object","additionalProperties":false,"readOnly":true,"description":"A request count for one dimension value (a surface, caller, or operation), with the error count for the same value.","required":["key","requests","errors"],"properties":{"key":{"type":"string","minLength":1,"readOnly":true,"description":"The dimension value these counts are for (a surface, caller, or operation).","example":"cli"},"requests":{"type":"integer","minimum":0,"readOnly":true,"description":"Requests recorded for this value over the range.","example":3200},"errors":{"type":"integer","minimum":0,"readOnly":true,"description":"Requests for this value that returned a server error.","example":4}}},"AdminSurfacesOperations":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One region's most-used operations for a reporting range, combined across regions for the platform view.","required":["region","operations"],"properties":{"region":{"type":"string","minLength":1,"readOnly":true,"description":"The region these figures cover.","example":"eu1"},"operations":{"type":"array","readOnly":true,"description":"Operations ranked by request count, with the error count for each. Each region returns extra candidates so the merged platform ranking is correct.","items":{"$ref":"#/components/schemas/AdminSurfacesKeyCount"}}}},"AdminSurfacesOrgRow":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One organization's usage totals for the range, with its agent-driven share. The organization name is resolved separately by the caller.","required":["organization_id","total_requests","agent_requests","surfaces"],"properties":{"organization_id":{"type":"string","minLength":1,"readOnly":true,"description":"The organization these figures are for.","example":"org_01hqtest0000000000000000"},"total_requests":{"type":"integer","minimum":0,"readOnly":true,"description":"Total requests from this organization over the range.","example":2867},"agent_requests":{"type":"integer","minimum":0,"readOnly":true,"description":"Requests from this organization an AI agent drove over the range.","example":15},"surfaces":{"type":"array","readOnly":true,"description":"The surfaces this organization used over the range.","items":{"$ref":"#/components/schemas/AdminSurfaceName"},"example":["dashboard","cli","mcp"]}}},"AdminSurfacesOrgs":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One region's organizations ranked by usage, combined across regions for the platform view. Each region returns extra candidates so the merged platform ranking is correct.","required":["region","orgs"],"properties":{"region":{"type":"string","minLength":1,"readOnly":true,"description":"The region these organizations belong to.","example":"eu1"},"orgs":{"type":"array","readOnly":true,"description":"Organizations ranked by the requested sort, capped to the requested limit's candidate set.","items":{"$ref":"#/components/schemas/AdminSurfacesOrgRow"}}}},"AdminSurfacesOrgDetail":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One organization's usage breakdown for the range in this region — its surface mix, harness mix, and top operations.","required":["region","organization_id","total_requests","agent_requests","surfaces","callers","operations"],"properties":{"region":{"type":"string","minLength":1,"readOnly":true,"description":"The region these figures cover.","example":"eu1"},"organization_id":{"type":"string","minLength":1,"readOnly":true,"description":"The organization these figures are for.","example":"org_01hqtest0000000000000000"},"total_requests":{"type":"integer","minimum":0,"readOnly":true,"description":"Total requests from this organization over the range.","example":2867},"agent_requests":{"type":"integer","minimum":0,"readOnly":true,"description":"Requests from this organization an AI agent drove over the range.","example":15},"surfaces":{"type":"array","readOnly":true,"description":"Request counts by surface for this organization.","items":{"$ref":"#/components/schemas/AdminSurfacesKeyCount"}},"callers":{"type":"array","readOnly":true,"description":"Request counts by caller for this organization (agent-surface traffic that carries a caller).","items":{"$ref":"#/components/schemas/AdminSurfacesKeyCount"}},"operations":{"type":"array","readOnly":true,"description":"Top operations by request count for this organization.","items":{"$ref":"#/components/schemas/AdminSurfacesKeyCount"}}}},"AdminChannelInterestUserOrg":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One organization owned by a user interested in a channel.","required":["organization_id","organization_name"],"properties":{"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"organization_name":{"type":"string","minLength":1,"readOnly":true,"description":"Name of the organization."}}},"AdminChannelInterestUser":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One user who asked to be notified when an upcoming channel ships.","required":["user_id","email","full_name","requested_at","owned_organizations"],"properties":{"user_id":{"readOnly":true,"$ref":"#/components/schemas/UserID"},"email":{"type":"string","minLength":1,"format":"email","readOnly":true,"description":"Email address of the interested user."},"full_name":{"type":["string","null"],"readOnly":true,"description":"Full name of the interested user, or null when they have not set one."},"requested_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the user asked to be notified about this channel."},"owned_organizations":{"type":"array","readOnly":true,"description":"Organizations the user owns. Empty when the user owns no organization.","items":{"$ref":"#/components/schemas/AdminChannelInterestUserOrg"}}}},"AdminChannelInterestUserList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminChannelInterestUser"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminChannelInterestDailyPoint":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The number of users who registered interest in a channel on a single day.","required":["bucket","count"],"properties":{"bucket":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Start of the day, in UTC."},"count":{"type":"integer","minimum":0,"readOnly":true,"description":"Users who first registered interest in the channel on this day.","example":12}}},"AdminChannelInterestDaily":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Daily counts of users who registered interest in a channel over the last 30 days.","required":["data"],"properties":{"data":{"type":"array","readOnly":true,"description":"One entry per day, oldest first. Every day in the 30-day window is present; a day on which no one registered interest has a count of zero.\n","items":{"$ref":"#/components/schemas/AdminChannelInterestDailyPoint"}}}},"AdminDomain":{"type":"object","additionalProperties":false,"readOnly":true,"description":"A sending domain with the verification state of a representative organization that claimed it. A registered-but-unclaimed domain has has_grant set to false, no organization, no verification status, and grant_count 0. When several organizations claim the same domain it still appears once: the organization fields describe a representative claim and grant_count is how many organizations claim it.\n","required":["id","domain","sending_region","created_at","has_grant","grant_count","verified"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier of the domain."},"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The sending domain name.","example":"mail.acme.com"},"sending_region":{"type":"string","minLength":1,"readOnly":true,"description":"Region whose sending infrastructure holds this domain.","example":"eu"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the domain was registered."},"has_grant":{"type":"boolean","readOnly":true,"description":"Whether an organization has claimed this domain. False for a registered-but-unclaimed domain."},"grant_count":{"type":"integer","minimum":0,"readOnly":true,"description":"How many organizations currently claim this domain. 0 when unclaimed, 1 for a normally claimed domain, more when several organizations share it."},"organization_id":{"type":["string","null"],"readOnly":true,"description":"A representative organization that claimed the domain, or null when unclaimed. When several organizations claim the domain this is one of them; see grant_count."},"organization_name":{"type":["string","null"],"readOnly":true,"description":"Display name of the organization in organization_id, or null when unclaimed."},"dkim_status":{"type":["string","null"],"readOnly":true,"description":"DKIM verification status for the claiming organization, or null when unclaimed.","example":"verified"},"dmarc_status":{"type":["string","null"],"readOnly":true,"description":"DMARC verification status for the claiming organization, or null when unclaimed.","example":"verified"},"return_path_status":{"type":["string","null"],"readOnly":true,"description":"Return-path (bounce) verification status for the claiming organization, or null when unclaimed or no return path is configured.","example":"verified"},"dkim_failing_since":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When DKIM first failed a re-check while still verified, or null when it is not currently failing. While set, the domain is in a warning grace period and sending is unaffected."},"dmarc_failing_since":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When DMARC first failed a re-check while still verified, or null when it is not currently failing. While set, the domain is in a warning grace period and sending is unaffected."},"return_path_failing_since":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When a return path first failed a re-check while still verified, or null when none is currently failing. While set, the domain is in a warning grace period and sending is unaffected."},"verified":{"type":"boolean","readOnly":true,"description":"True when DKIM, DMARC, and the return path are all verified for the claiming organization — the requirements for the domain to send."},"last_checked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the representative organization's verification was last checked, whether or not the outcome changed, or null when never checked."},"verified_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the representative organization's domain became auth-verified: when both DKIM and DMARC were confirmed (the later of the two), or null until both have. The return path is not part of this timestamp; full sending readiness, which also requires the return path, is reported by `verified`.\n"}}},"AdminDomainList":{"type":"object","additionalProperties":false,"readOnly":true,"description":"A page of sending domains together with the total number matching the filters.","required":["data","total"],"properties":{"data":{"type":"array","readOnly":true,"description":"The matching domains, newest first, capped at the requested limit.","items":{"$ref":"#/components/schemas/AdminDomain"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of domains matching the filters. The data array may be capped below this by the requested limit.","example":312}}},"AdminDomainEvent":{"type":"object","additionalProperties":false,"readOnly":true,"description":"A single event in a sending domain's activity log — a registration, a verification status change, or a configuration update — together with the domain it belongs to and the organization that owns it.\n","required":["id","domain_id","domain","sending_region","organization_id","event_type","summary","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/DomainEventID","description":"Identifier of the event."},"domain_id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier of the domain the event belongs to."},"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The sending domain name.","example":"mail.acme.com"},"sending_region":{"type":"string","minLength":1,"readOnly":true,"description":"Region whose sending infrastructure holds this domain.","example":"eu"},"organization_id":{"type":"string","minLength":1,"readOnly":true,"description":"Organization that owns the domain."},"organization_name":{"type":["string","null"],"readOnly":true,"description":"Display name of the organization that owns the domain, or null if that organization no longer exists."},"workspace_id":{"type":["string","null"],"readOnly":true,"description":"Workspace the event relates to, or null for organization-level events."},"event_type":{"type":"string","minLength":1,"readOnly":true,"description":"The kind of event.","example":"domain.dkim_status_changed"},"summary":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable description of what happened."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the event occurred."}}},"AdminDomainEventList":{"type":"object","additionalProperties":false,"readOnly":true,"description":"A page of sending-domain events together with the total number matching the filters.","required":["data","total"],"properties":{"data":{"type":"array","readOnly":true,"description":"The matching events, newest first, capped at the requested limit.","items":{"$ref":"#/components/schemas/AdminDomainEvent"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of events matching the filters. The data array may be capped below this by the requested limit.","example":4096}}},"AdminDomainInboundStatus":{"type":"string","minLength":1,"enum":["not_configured","pending","verified","warning","failed"],"description":"Status of an inbound (receiving) claim, scoped to what the field hangs on: the domain on `AdminDomainDetail`, one organization on `AdminDomainGrant`. `not_configured`: the subject holds no claim. On `AdminDomainDetail` that means no organization holds one, either because receiving was never enabled or because a prior claim was disabled and released. On `AdminDomainGrant` it means that organization holds none, which includes a domain a different organization has claimed. `pending`: claimed, but still awaiting verified DKIM and a resolving MX record. `verified`: the MX record resolves to a Bird mail host and the domain can receive mail. `warning`: the claim verified before and a re-check has since found the MX not resolving, so mail may already be bouncing. Those re-checks begin only once the 12h post-verification propagation grace has expired. `failed`: receiving could not be provisioned, or a `warning` claim's MX stayed broken for the whole 24h failure buffer that runs from the first failing re-check."},"AdminDomainGrant":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One organization's claim on a sending domain, with its DKIM and DMARC verification detail.","required":["id","organization_id","dkim_mode","dkim_status","dkim_selector","dkim_key_id","dmarc_status","verified","created_at","dns_records"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier of the grant."},"organization_id":{"type":"string","minLength":1,"readOnly":true,"description":"Organization that holds this grant."},"dkim_mode":{"type":"string","minLength":1,"readOnly":true,"description":"How DKIM keys are provisioned for this grant."},"dkim_status":{"type":"string","minLength":1,"readOnly":true,"description":"DKIM verification status.","example":"verified"},"dkim_error":{"type":["string","null"],"readOnly":true,"description":"Most recent DKIM verification error, or null when there is none."},"dkim_selector":{"type":"string","minLength":1,"readOnly":true,"description":"DKIM selector used for this grant."},"dkim_key_id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier of the DKIM key used for this grant."},"dmarc_status":{"type":"string","minLength":1,"readOnly":true,"description":"DMARC verification status.","example":"verified"},"dmarc_error":{"type":["string","null"],"readOnly":true,"description":"Most recent DMARC verification error, or null when there is none."},"dmarc_policy_domain":{"type":["string","null"],"readOnly":true,"description":"Domain whose DMARC policy applies, or null when not set."},"dkim_failing_since":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When DKIM first failed a re-check while still verified, or null when it is not currently failing. While set, the domain is in a warning grace period and sending is unaffected."},"dmarc_failing_since":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When DMARC first failed a re-check while still verified, or null when it is not currently failing. While set, the domain is in a warning grace period and sending is unaffected."},"dns_records":{"type":"array","readOnly":true,"description":"The DNS records this grant requires the customer to publish — DKIM TXT, DMARC TXT, and a CNAME per return-path and tracking hostname — each with its expected value and most recent stored verification status. Computed from the grant's DKIM key, DMARC config, and aux hostnames.\n","items":{"$ref":"#/components/schemas/DNSRecord"}},"return_path_status":{"type":["string","null"],"readOnly":true,"description":"Verification status of the grant's return-path (bounce) record, or null when no return path is configured.","example":"verified"},"return_path_hostname":{"type":["string","null"],"readOnly":true,"description":"The return-path (bounce) hostname for this grant, or null when no return path is configured."},"return_path_error":{"type":["string","null"],"readOnly":true,"description":"Most recent return-path verification error, or null when there is none."},"return_path_verified_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the return path was last confirmed verified, or null when never verified."},"return_path_failing_since":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the return path first failed a re-check while still verified, or null when it is not currently failing. While set, the domain is in a warning grace period and sending is unaffected."},"verified":{"type":"boolean","readOnly":true,"description":"True when DKIM, DMARC, and the return path are all verified for this grant — the requirements for the domain to send."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the organization claimed the domain."},"last_checked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When verification was last checked, whether or not the outcome changed, or null when never checked."},"dkim_verified_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When DKIM was last confirmed verified, or null when never verified."},"dmarc_verified_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When DMARC was last confirmed verified, or null when never verified."},"inbound_status":{"allOf":[{"$ref":"#/components/schemas/AdminDomainInboundStatus"}],"readOnly":true,"description":"This organization's inbound (receiving) status for the domain. Only the organization holding the domain's exclusive inbound claim reads anything but `not_configured`.\n","example":"verified"}}},"AdminDomainDetail":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The full admin view of a sending domain — its registry details and every organization grant on it.","required":["id","domain","sending_region","created_at","grants","inbound_status","inbound_organization_id","inbound_records"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier of the domain."},"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The sending domain name.","example":"mail.acme.com"},"sending_region":{"type":"string","minLength":1,"readOnly":true,"description":"Region whose sending infrastructure holds this domain.","example":"eu"},"dns_provider_apex":{"type":["string","null"],"readOnly":true,"description":"The apex identifying the domain's DNS provider, or null when not detected."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the domain was registered."},"grants":{"type":"array","readOnly":true,"description":"Every organization grant on this domain.","items":{"$ref":"#/components/schemas/AdminDomainGrant"}},"inbound_status":{"allOf":[{"$ref":"#/components/schemas/AdminDomainInboundStatus"}],"readOnly":true,"description":"The domain's inbound (receiving) status. Receiving is a domain-level fact, not a per-grant one: the inbound claim is exclusive, so at most one organization holds it. `not_configured` when no organization holds a claim, otherwise the holding organization's status.\n","example":"verified"},"inbound_organization_id":{"type":["string","null"],"readOnly":true,"description":"Organization holding the domain's inbound claim, or null when no organization holds one."},"inbound_records":{"type":"array","readOnly":true,"description":"The MX records routing mail for this domain to Bird, for the region named by `sending_region`. They are present whether or not receiving is enabled. When no organization has claimed inbound they are a reference only, and each is marked optional.\n","items":{"$ref":"#/components/schemas/DNSRecord"}}}},"AdminDnsObserved":{"type":"object","additionalProperties":false,"readOnly":true,"description":"An expected DNS record resolved against live DNS and compared to its expected value.","required":["host","type","purpose","expected_value","observed_values","found","matches_expected"],"properties":{"host":{"type":"string","minLength":1,"readOnly":true,"description":"The fully qualified hostname that was resolved."},"type":{"type":"string","minLength":1,"readOnly":true,"description":"The DNS record type that was resolved.","enum":["TXT","CNAME","MX"]},"purpose":{"type":"string","minLength":1,"readOnly":true,"description":"What this expected record is for.","enum":["dkim","dmarc","return_path","tracking","inbound_mx"]},"expected_value":{"type":"string","minLength":1,"readOnly":true,"description":"The value Bird expects to find published at this host."},"observed_values":{"type":"array","readOnly":true,"description":"The value(s) actually resolved at this host. Empty when nothing was found.","items":{"type":"string"}},"found":{"type":"boolean","readOnly":true,"description":"Whether any record of this type was resolved at the host."},"matches_expected":{"type":"boolean","readOnly":true,"description":"Whether the resolved value matches this record's own expected value. For an `MX` record only the exchange is compared, because a Bird mail host published at a different preference still receives mail. Read an inbound `MX` row as \"the customer published this host\", not as \"receiving works\": receiving needs only one of the domain's inbound records to resolve, so a `false` here can sit beside a verified `inbound_status`.\n"},"error":{"type":["string","null"],"readOnly":true,"description":"Reason the lookup could not complete (timeout, resolver error), or null on success."}}},"AdminDnsDiagnostic":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Raw live-resolved result for one entry in the email-relevant probe set, independent of what Bird expects. Surfaces stray or conflicting records (e.g. a pre-existing SPF record, a wrong DKIM key, a mis-pointed CNAME).\n","required":["label","host","type","values","found"],"properties":{"label":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable label for this probe (e.g. \"Apex TXT (SPF)\", \"DKIM key\")."},"host":{"type":"string","minLength":1,"readOnly":true,"description":"The fully qualified hostname that was probed."},"type":{"type":"string","minLength":1,"readOnly":true,"description":"The DNS record type that was probed.","enum":["TXT","CNAME","MX"]},"values":{"type":"array","readOnly":true,"description":"The raw value(s) resolved at this host. Empty when nothing was found.","items":{"type":"string"}},"found":{"type":"boolean","readOnly":true,"description":"Whether any record of this type was resolved at the host."},"error":{"type":["string","null"],"readOnly":true,"description":"Reason the probe could not complete, or null on success."}}},"AdminDomainDnsLookup":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Live DNS resolution for a sending domain, for staff debugging. `observed` is one entry per expected record (what Bird requires) resolved against live DNS; `diagnostics` is a curated, sending-focused probe of the zone regardless of what Bird expects, to surface stray or conflicting records.\n","required":["observed","diagnostics"],"properties":{"observed":{"type":"array","readOnly":true,"description":"One entry per expected record, resolved live and compared to the expected value.","items":{"$ref":"#/components/schemas/AdminDnsObserved"}},"diagnostics":{"type":"array","readOnly":true,"description":"Raw live-resolved results for a curated email-relevant probe set.","items":{"$ref":"#/components/schemas/AdminDnsDiagnostic"}}}},"AdminCnameRawVerdict":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The unmapped result of the check behind a stored CNAME status. Kept because the mapping to a status is lossy: any verdict the mail provider reports that Bird does not recognise is stored as `pending`, and the provider's message is dropped unless the record failed.\n","required":["source","checked_at"],"properties":{"status":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The verdict verbatim, or null when the check returned none. For a `sparkpost` check this is the provider's own status string (`valid`, `invalid`, `unverified`, `pending`, or anything newer). For a `dns` check there is no verdict string, so this is the CNAME target actually resolved, null when no record is published. For a `derived` check it is Bird's own label for the conclusion, not provider text.\n"},"error":{"type":["string","null"],"readOnly":true,"description":"The provider's failure message verbatim, retained even when the stored status is not `failed`. Null when the check reported none.\n"},"source":{"type":"string","minLength":1,"readOnly":true,"description":"What produced this verdict, which is what makes `status` interpretable: `sparkpost` means the provider's own status string, `dns` means the CNAME target Bird's resolver saw, and `derived` means a conclusion Bird reached from how the provider behaved rather than anything it returned.\n","enum":["sparkpost","dns","derived"]},"checked_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this verdict was observed."}}},"AdminProviderResource":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One resource at the mail infrastructure provider that backs a sending domain, paired with Bird's stored view of the same thing so a disagreement between them is visible directly.\n","required":["kind","hostname","subaccount_id","found","provider_verified","disagrees"],"properties":{"kind":{"type":"string","minLength":1,"readOnly":true,"description":"Which provider resource this is. `sending_domain` is the domain itself, which carries no verification verdict of its own because a domain shared between organizations is signed by one `dkim_key` per organization, each verified separately. `return_path` and `tracking` are separate hostnames the provider verifies by CNAME. This is the resource's kind at the provider, not the purpose of a DNS record the customer publishes, so it does not carry the `dmarc` or `inbound_mx` values a DNS record's purpose can.\n","enum":["sending_domain","dkim_key","return_path","tracking"]},"hostname":{"type":"string","minLength":1,"readOnly":true,"description":"The hostname this resource is registered under at the provider. For a DKIM key it is the TXT record name the organization publishes, so it lines up with the DNS records table.\n"},"subaccount_id":{"type":"integer","readOnly":true,"description":"The provider subaccount the resource lives under. Tracking domains are subaccount-scoped and cannot be moved after creation; sending domains and return-paths are parent-scoped and report 0. A tracking domain reports 0 only when no subaccount is recorded for it, in which case no read was attempted and `skipped_reason` says so.\n"},"found":{"type":"boolean","readOnly":true,"description":"Whether the provider has this resource at all. False with no error means the provider reported it absent, which, for a hostname Bird has recorded as registered, is itself the finding.\n"},"provider_status":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The provider's verdict verbatim, for whatever this resource is verified on: the key's DKIM status for a DKIM key, the CNAME status for a return-path or tracking domain. Always null for the apex sending domain, whose only readable DKIM status belongs to the provider's own default key and so describes no organization's signing key. Null when the provider reported none, which for a verified resource happens when it returns the verdict as a flag rather than a status string.\n"},"provider_verified":{"type":"boolean","readOnly":true,"description":"Whether the provider considers this resource verified."},"compliance_status":{"type":["string","null"],"readOnly":true,"description":"The provider's compliance verdict for a tracking domain, which is distinct from verification: a compliance block reads as a permission failure, not a DNS problem. Null for other resource kinds.\n"},"secure":{"type":"boolean","readOnly":true,"description":"Whether the tracking domain is registered for HTTPS. Meaningful only for tracking."},"port":{"type":"integer","readOnly":true,"description":"The port the tracking domain serves on. Meaningful only for tracking."},"stored_status":{"type":["string","null"],"readOnly":true,"description":"Bird's own stored status for this resource. Null for the apex sending domain, whose status is per organization grant rather than per hostname.\n"},"stored_verdict":{"oneOf":[{"$ref":"#/components/schemas/AdminCnameRawVerdict"},{"type":"null"}],"readOnly":true,"description":"The raw verdict behind Bird's stored status. Null when no check has produced it yet."},"stored_record_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"Identifier of the record behind this resource, for use when re-checking it. Null for the apex sending domain, which is not a re-checkable record.\n"},"disagrees":{"type":"boolean","readOnly":true,"description":"Whether the provider and Bird are telling different stories about this resource: the provider has no such resource, or exactly one of the two considers it verified. False when the lookup failed or Bird holds no status to compare.\n"},"skipped_reason":{"type":["string","null"],"readOnly":true,"description":"Why no provider read was attempted, or null when one was. Set when the resource cannot be addressed at all, for example a tracking hostname with no subaccount recorded for it. Reading the wrong account would report the resource as absent, so the reason is returned instead of a verdict, and such a resource never counts as a disagreement.\n"},"error":{"type":["string","null"],"readOnly":true,"description":"Reason the provider read could not complete, or null on success. Distinct from `found`: an absent resource is a successful read.\n"}}},"AdminDomainProviderState":{"type":"object","additionalProperties":false,"readOnly":true,"description":"What the mail infrastructure provider holds for every resource backing a sending domain, read without asking it to re-verify anything. For staff debugging a domain whose stored status and published DNS appear to disagree.\n","required":["resources"],"properties":{"resources":{"type":"array","readOnly":true,"description":"One entry per provider resource behind the domain: the apex sending domain, each return-path hostname, and each tracking hostname.\n","items":{"$ref":"#/components/schemas/AdminProviderResource"}}}},"AdminDomainReverifyRequest":{"type":"object","additionalProperties":false,"description":"Selects the single record to re-check at the mail infrastructure provider.","required":["record_id"],"properties":{"record_id":{"type":"string","format":"uuid","minLength":1,"description":"Identifier of the return-path or tracking record to re-check, as reported on the domain's records. Only one record is re-checked, so the domain's other signals are left untouched.\n"}}},"AdminDomainReverifyResult":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One record's state after a provider re-check, including the provider's raw verdict.","required":["record_id","kind","hostname","status","checked"],"properties":{"record_id":{"type":"string","format":"uuid","minLength":1,"readOnly":true,"description":"Identifier of the record that was re-checked."},"kind":{"type":"string","minLength":1,"readOnly":true,"description":"Which kind of record was re-checked.","enum":["return_path","tracking"]},"hostname":{"type":"string","minLength":1,"readOnly":true,"description":"The hostname that was re-checked."},"status":{"type":"string","minLength":1,"readOnly":true,"description":"The record's stored status after the re-check. `pending` means no check has ever succeeded, `verified` means the provider accepts the record, and `failed` means it was accepted once and no longer is. Deliberately three values where a customer-facing DNS record has four: `warning` is computed when composing a customer view from a verified record inside its failure grace period, and is never a stored status, so it cannot appear here.\n","enum":["pending","verified","failed"]},"error":{"type":["string","null"],"readOnly":true,"description":"The stored failure reason after the re-check, or null when the record is not failed."},"verdict":{"oneOf":[{"$ref":"#/components/schemas/AdminCnameRawVerdict"},{"type":"null"}],"readOnly":true,"description":"The raw verdict the re-check received. When `checked` is false this is whatever was already stored, or null if the record had never been checked.\n"},"checked":{"type":"boolean","readOnly":true,"description":"Whether the mail provider was actually asked. False when a precondition stopped the check before it ran, in which case the stored status is unchanged and `skipped_reason` says why. Without this a skipped attempt is indistinguishable from one where the provider ran and still rejected the record.\n"},"skipped_reason":{"type":["string","null"],"readOnly":true,"description":"Why no provider check ran, or null when `checked` is true."}}},"AdminBanCategory":{"type":"string","minLength":1,"enum":["acceptable_use_other","account_takeover","automated_fraud_signal","ban_evasion","brand_impersonation","malware_distribution","payment_fraud","phishing","prohibited_content","smishing","traffic_pumping","unsolicited_bulk_messaging"],"description":"The policy category a suspension is recorded under. `automated_fraud_signal` is reserved for suspensions Bird's automated fraud detection made without a more specific category, and is not selectable by staff."},"AdminOrganization":{"allOf":[{"$ref":"#/components/schemas/Organization"},{"type":"object","properties":{"created_by_user_id":{"readOnly":true,"description":"User who created the organization. Null for organizations created before this attribution was recorded or after that user is deleted.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"state":{"type":"string","minLength":1,"enum":["active","suspended","banned","pending_deletion","deleted"],"readOnly":true,"description":"Lifecycle state of the organization, used by staff tooling to show deleted and pending-deletion organizations read-only."},"banned_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp when the organization was banned by Bird staff, or null. A banned organization's API keys and member access are suspended until unbanned."},"last_ban_category":{"oneOf":[{"$ref":"#/components/schemas/AdminBanCategory"},{"type":"null"}],"readOnly":true,"description":"The category of the organization's most recent suspension. Retained after reinstatement, so a non-null value does not mean the organization is currently suspended."},"last_ban_note":{"type":["string","null"],"readOnly":true,"description":"Staff note recorded with the organization's most recent suspension. Retained after reinstatement, so a non-null value does not mean the organization is currently suspended."},"last_banned_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the organization's most recent suspension was applied. Unlike banned_at, this is retained after reinstatement (not cleared), so a non-null value does not mean the organization is currently suspended."},"sparkpost_subaccount_id":{"type":["integer","null"],"readOnly":true,"description":"The organization's subaccount id on SparkPost, the email sending provider. Null when the organization has not been provisioned for email sending yet. Omitted entirely when the mapping could not be read. That failure is transient and confined to this one read, saying nothing about the organization, so retry the request."},"cp_customer_id":{"type":["integer","null"],"readOnly":true,"description":"The organization's customer id on the Connectivity Platform (CP), the SMS sending provider. Null when the organization has not been provisioned for SMS sending yet. Omitted entirely when the mapping could not be read. That failure is transient and confined to this one read, saying nothing about the organization, so retry the request."}}}]},"AdminOrganizationStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Operational figures for one organization, included in the admin organization list when requested.\n","required":["member_count","domain_count","owner_email_domain","subscription_plan","subscription_status","state"],"properties":{"member_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of people with a role in the organization or any of its workspaces.","example":24},"domain_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of sending domains the organization has configured.","example":3},"owner_email_domain":{"type":["string","null"],"readOnly":true,"description":"Email domain of the organization's owner, or null when no owner is on record.","example":"acmeretail.com"},"subscription_plan":{"type":["string","null"],"readOnly":true,"description":"Display name of the organization's active plan subscription, or null when it has none.","example":"Scale"},"subscription_status":{"type":["string","null"],"readOnly":true,"description":"Status of the active plan subscription, such as active or trialing. Null when the organization has no plan subscription.","example":"active"},"state":{"type":"string","minLength":1,"enum":["active","suspended","banned","pending_deletion","deleted"],"readOnly":true,"description":"Lifecycle state of the organization. Banned takes precedence over a billing suspension; an organization pending deletion or already deleted takes precedence over both.","example":"active"}}},"AdminOrganizationListItem":{"allOf":[{"$ref":"#/components/schemas/AdminOrganization"},{"type":"object","properties":{"stats":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminOrganizationStats"}]}}}]},"AdminOrganizationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminOrganizationListItem"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminOrganizationMessageActivity":{"type":"object","additionalProperties":false,"description":"Accepted message activity across email, SMS, WhatsApp, and Apple Messages for Business for one organization.","required":["organization_id","messages_30d","last_sent_at"],"properties":{"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"messages_30d":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Messages accepted for sending during the 30-day window. Very large counts are approximate.","example":4200},"last_sent_at":{"type":["string","null"],"format":"date","readOnly":true,"description":"Latest UTC day with accepted messages in the window, or null when there were none.","example":"2026-09-08"}}},"AdminOrganizationMessageActivityResponse":{"type":"object","additionalProperties":false,"description":"Message activity for a batch of organizations in one region. A channel failure returns an error rather than a partial total.","required":["from","to","computed_at","organizations"],"properties":{"from":{"type":"string","minLength":1,"format":"date","readOnly":true,"description":"First UTC day included in the window.","example":"2026-08-10"},"to":{"type":"string","minLength":1,"format":"date","readOnly":true,"description":"Last UTC day included in the window.","example":"2026-09-08"},"computed_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the summary was computed. Newly accepted messages may not yet appear in the statistics.","example":"2026-09-08T12:00:00Z"},"organizations":{"type":"array","readOnly":true,"description":"One summary per distinct requested organization.","items":{"$ref":"#/components/schemas/AdminOrganizationMessageActivity"}}}},"SystemEmailID":{"type":"string","minLength":1,"pattern":"^sem_[0-9a-hjkmnp-tv-z]{26}$","example":"sem_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSystemEmail":{"type":"object","additionalProperties":false,"required":["id","template_name","recipient","status","created_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SystemEmailID"}]},"template_name":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier of the email template that was sent, for example `password-reset` or `team-invite`."},"recipient":{"type":"string","minLength":1,"readOnly":true,"description":"Email address the message was sent to."},"from":{"type":["string","null"],"readOnly":true,"description":"Sender address the message was sent from. Null for older messages recorded before the sender was tracked."},"subject":{"type":["string","null"],"readOnly":true,"description":"Rendered subject line of the message. Null when the message could not be rendered."},"body":{"type":["string","null"],"readOnly":true,"description":"Rendered plain-text body of the message. For authentication emails (those carrying a sign-in link or one-time code) this is `**REDACTED**`. Null when the message could not be rendered."},"status":{"type":"string","minLength":1,"enum":["sent","failed"],"readOnly":true,"description":"Whether the message was accepted for delivery (`sent`) or could not be sent (`failed`)."},"error_detail":{"type":["string","null"],"readOnly":true,"description":"Reason the message failed. Null when the message was sent."},"user_id":{"readOnly":true,"description":"The user the message relates to, when known.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"organization_id":{"readOnly":true,"description":"The organization the message relates to, when known.","oneOf":[{"$ref":"#/components/schemas/OrganizationID"},{"type":"null"}]},"workspace_id":{"readOnly":true,"description":"The workspace the message relates to, when known.","oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}]},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the message was sent."}}},"AdminSystemEmailList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSystemEmail"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminOrganizationBanRequest":{"type":"object","additionalProperties":false,"properties":{"mode":{"type":"string","enum":["graceful"],"default":"graceful","x-enum-varnames":["AdminOrganizationBanRequestModeGraceful"],"description":"How the ban escalates. Every ban is a reversible `graceful` suspension: the organization stops sending immediately, then can be reinstated during the grace window, after which it is permanently removed. There is no zero-grace option — an irreversible removal is never triggered by a ban."},"category":{"$ref":"#/components/schemas/AdminBanCategory"},"note":{"type":"string","maxLength":2000,"description":"Optional free-text detail recorded alongside the category, for staff."}},"required":["category"]},"AdminBanEvidenceWindow":{"type":"object","additionalProperties":false,"description":"The inclusive date range the evidence covers, anchored on the organization's last_banned_at (never banned_at, which an unban clears) so a reinstated account's evidence composes identically to a live ban's.","required":["from","to"],"properties":{"from":{"type":"string","minLength":1,"format":"date-time"},"to":{"type":"string","minLength":1,"format":"date-time","description":"Equal to the organization's last_banned_at."}}},"AdminBanEvidenceChannelStatus":{"type":"string","minLength":1,"enum":["ok","no_traffic","failed","unsupported"],"description":"Outcome of reading this channel's counters for the evidence window. `ok`: the counters below are populated. `no_traffic`: the read succeeded, but the organization sent nothing on this channel during the window. `failed`: the read itself errored, so the counters are zero, but that must never be taken as an absence of abuse. `unsupported`: no reader is wired for this channel in this deployment. Only `ok` channels reach the rendered statement, which is written for a payment processor: this field is where staff see that a channel was left out because the read broke rather than because the account was quiet."},"AdminBanEvidenceDimension":{"type":"object","additionalProperties":false,"description":"One row of a channel's destination or error-code breakdown over the evidence window.","required":["key","count"],"properties":{"key":{"type":"string","minLength":1,"description":"The dimension value (e.g. a destination country code or error code)."},"count":{"type":"integer","format":"int64","minimum":0,"description":"Count for this dimension value over the window."}}},"AdminBanEvidenceChannel":{"type":"object","additionalProperties":false,"description":"One channel's message-volume counters over the evidence window, plus the read's own status. Every supported channel always gets an entry (see AdminBanEvidence.channels). A channel the organization never used is still represented, with `status: no_traffic`, rather than omitted. A `failed` channel's zero counters must never be read as an absence of abuse: the read errored, it did not observe silence.","required":["status","submitted","delivered","failed"],"properties":{"status":{"$ref":"#/components/schemas/AdminBanEvidenceChannelStatus"},"submitted":{"type":"integer","format":"int64","minimum":0,"description":"Messages submitted on this channel over the window. Zero when status is not `ok`."},"delivered":{"type":"integer","format":"int64","minimum":0,"description":"Messages delivered on this channel over the window. Zero when status is not `ok`. For `amb` this counts messages handed to Apple, which issues no delivery receipt, and the rendered `statement` says \"sent to Apple\" rather than \"delivered\"."},"failed":{"type":"integer","format":"int64","minimum":0,"description":"Messages that failed (bounced/undelivered, or for `amb` failed to send) on this channel over the window. Zero when status is not `ok`."},"blocked":{"type":"integer","format":"int64","minimum":0,"description":"Recipients this channel's enforcement blocked before delivery over the window: accepted and billed, then stopped by Bird's automated abuse detection, and still reported to the customer as accepted. A subset of `submitted`, not a separate bucket. Absent on an `ok` or `no_traffic` channel that counts no enforcement blocks, and on a `failed` or `unsupported` channel, where nothing was read at all: `status` tells those two apart, so read them together. A zero is not evidence that nothing was blocked: block counting began 2026-08-21 and cannot be backfilled, so a window opening before that date reports zero."},"top_destinations":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminBanEvidenceDimension"},"description":"Reserved for a future destination/error-code breakdown; empty in this version."}}},"AdminBanEvidenceReviewedMessageChannel":{"type":"string","minLength":1,"enum":["email","sms","whatsapp","amb"],"description":"The channel the reviewed message was sent on."},"AdminBanEvidenceReviewedMessage":{"type":"object","additionalProperties":false,"description":"One message a staff member individually opened before the ban, from the audited content-reveal trail (admin.{email,sms,whatsapp}_content.viewed). Records that the message was reviewed. It does not record that the message was judged abusive.","required":["channel","message_id","viewed_at","actor_id"],"properties":{"channel":{"$ref":"#/components/schemas/AdminBanEvidenceReviewedMessageChannel"},"message_id":{"type":"string","minLength":1,"description":"The reviewed message's ID (the audited event's target)."},"viewed_at":{"type":"string","minLength":1,"format":"date-time","description":"When the content was revealed to staff."},"actor_id":{"type":"string","minLength":1,"description":"The staff member who viewed the content. Reported regardless of who applied the ban, since a colleague's review is still a review."}}},"AdminBanEvidenceActorKind":{"type":"string","minLength":1,"enum":["admin","automated","unknown"],"description":"What recorded the ban. `admin`: a platform admin initiated it. `automated`: Bird's automated fraud detection (Sift) did. `unknown`: no ban event was readable in this region, which records only that the event could not be found here and says nothing about whether an admin was involved."},"AdminBanEvidenceActor":{"type":"object","additionalProperties":false,"description":"Who or what recorded the ban, resolved from the audit trail's `admin.organization.banned` event rather than stored on the organization. Because it is read back from that event, it is only as reachable as the event is: a Sift cascade writes its audit record in the region of the process that ran the decision, which is one fixed region regardless of where the organization is homed, while this endpoint reads the organization's own region. A cross-region automated ban therefore resolves to `unknown`, which records only that the ban event was unreadable from here and carries no information about whether an admin was involved.\n","required":["kind"],"properties":{"kind":{"$ref":"#/components/schemas/AdminBanEvidenceActorKind"},"id":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"User ID of the admin who initiated the ban, when the event named one. Null for an automated ban, for `unknown`, and for an admin-initiated cascade (an admin banning the organization's sole owner), where the event naming the individual targets the user rather than the organization."},"email":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Email of the admin named by `id`, snapshotted on the audit event at write time. Null whenever `id` is, and also when the snapshot was not captured."}}},"AdminBanEvidence":{"type":"object","additionalProperties":false,"description":"The dispute-evidence read for an organization's most recent ban: the category and rendered statement, the evidence window, per-channel message counters, and the trail of messages staff individually reviewed before the ban. Computed at read time from indefinitely-retained rollups and the audit log rather than a frozen snapshot, so it works at any age. Carries no message bodies. Revealing content stays on the existing admin:email_content path, which has its own audit event.\n","required":["category","label","policy_ref","banned_at","window","channels","reviewed_messages","banned_by","statement"],"properties":{"category":{"$ref":"#/components/schemas/AdminBanCategory"},"label":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable label for the category (e.g. \"Phishing\")."},"note":{"type":["string","null"],"readOnly":true,"description":"Staff note recorded alongside this ban's category, or null."},"policy_ref":{"type":"string","minLength":1,"readOnly":true,"description":"Anchor into Bird's policy documents this category cites."},"banned_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When this ban was applied (the organization's last_banned_at)."},"window":{"$ref":"#/components/schemas/AdminBanEvidenceWindow"},"channels":{"type":"object","readOnly":true,"additionalProperties":{"$ref":"#/components/schemas/AdminBanEvidenceChannel"},"description":"Per-channel counters keyed by channel. Always carries exactly one entry for every channel this endpoint supports (currently email, sms, whatsapp, amb), and the key is present regardless of status, including `unsupported` (no reader wired for that channel). A missing key would mean this response version dropped support for a channel outright, not that the organization happened not to use it.","example":{"email":{"status":"ok","submitted":41208,"delivered":38900,"failed":2308}}},"reviewed_messages":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminBanEvidenceReviewedMessage"},"description":"Messages staff individually reviewed before the ban, across all channels."},"banned_by":{"$ref":"#/components/schemas/AdminBanEvidenceActor"},"statement":{"type":"string","minLength":1,"readOnly":true,"description":"The rendered dispute statement: the category, the window, per-channel volumes, and the review-trail count. Written to be sent to a bank or card scheme as-is, so it states only what Bird can stand behind: a channel with no traffic, and a channel whose counters could not be read, are both left out rather than named as gaps, and with nothing readable it is the category and its policy citation alone. Read `status` on each channel to tell the two apart, and tell staff which channels the statement leaves out. Describes the account's conduct only: it carries neither the ban's staff note, nor who applied the ban, nor whether Bird preserved any message bodies, all of which are facts about Bird rather than the account and are surfaced separately for staff."}}},"AdminOrganizationRisk":{"type":"object","additionalProperties":false,"description":"An organization's aggregate trust & safety risk scores. Each score ranges from 0 to 1, where higher is riskier, and is null when not computed or unavailable.","properties":{"account_abuse":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Highest Sift account_abuse score among the organization's members. Null when no member has a score or it could not be fetched."},"payment_abuse":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Highest Sift payment_abuse score among the organization's members. Null when no member has a score or it could not be fetched."},"content_abuse":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Sift content_abuse score for the organization's outbound message history. Null when no score has been computed or it could not be fetched."},"overall":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"The maximum of the available scores above — a single headline risk figure. Null when none of the scores are available."}}},"AdminOrganizationBackgroundCheckSearch":{"type":"object","additionalProperties":false,"description":"One web search the background check ran, reported so a reviewer can see what it looked for.","required":["query"],"properties":{"query":{"type":"string","minLength":1,"readOnly":true,"description":"What was searched for."}}},"AdminOrganizationBackgroundCheckStatus":{"type":"string","minLength":1,"enum":["verified","plausible","refuted","unknown"],"description":"What an independent source said about a claim. The same scale carries a single\ncheck's result and the report's overall verdict.\n\n- `verified`: a source the organization does not control confirms it.\n- `plausible`: nothing contradicts it and it fits what was found, but no independent source confirms it.\n- `refuted`: a source the organization does not control contradicts it.\n- `unknown`: nothing was found either way. The honest answer for a small or young business, and not a negative signal.\n"},"AdminOrganizationBackgroundCheckItem":{"type":"object","additionalProperties":false,"description":"One row of a background check: what was checked, against which authority, and what that authority said. A row is sent as soon as its status is known and re-sent as the rest of it arrives, so a row still in flight carries only the keys that have arrived.","required":["index","status"],"properties":{"index":{"type":"integer","minimum":0,"readOnly":true,"description":"The row's position in the report. A row is sent as soon as its status is known and re-sent as the rest of it arrives, so a consumer replaces the row at this position rather than appending a second copy of it."},"planned_total":{"type":"integer","minimum":1,"readOnly":true,"description":"How many rows the check said it would report, this one included. Which checks are possible depends on what the organization filled in, so this is the researcher's own estimate rather than a fixed programme, and it can grow when a doubt turns into a row that was not planned. A row carries it from the first time that row is sent, alongside its status, rather than arriving on a later re-send like the other keys. An increase reaches only the rows sent after it, so earlier rows keep the figure that was current when they were sent: take the highest value seen so far as the total, and never read it from one row alone."},"check":{"type":"string","minLength":1,"readOnly":true,"description":"What was tested, as a short label (a registry, a domain, mail infrastructure), or the doubt this row settles, quoted."},"subject":{"type":"string","minLength":1,"readOnly":true,"description":"The value that was checked, exactly as it was checked. A tax identifier, a domain, an email domain, or an address."},"source":{"type":"string","minLength":1,"readOnly":true,"description":"The authority that answered, named rather than linked: a company register, a live registry query, WHOIS, a dated press release. A reviewer weighs the row by who said it, so a source the organization controls is named as such."},"status":{"$ref":"#/components/schemas/AdminOrganizationBackgroundCheckStatus","readOnly":true,"description":"What this source said about the check."},"result":{"type":"string","minLength":1,"readOnly":true,"description":"What the source said, in a sentence."},"source_url":{"type":"string","format":"uri","minLength":1,"readOnly":true,"description":"The page the result was read on. Absent for a check answered by the organization's own record."}}},"AdminOrganizationBackgroundCheckSource":{"type":"object","additionalProperties":false,"description":"A page the background check read while researching the organization.","required":["url"],"properties":{"url":{"type":"string","format":"uri","minLength":1,"readOnly":true,"description":"The page's address."},"title":{"type":"string","minLength":1,"readOnly":true,"description":"The page's title. Absent when the search result carried none."}}},"AdminOrganizationBackgroundCheckSources":{"type":"object","additionalProperties":false,"description":"The pages one of the background check's web searches returned.","required":["sources"],"properties":{"sources":{"type":"array","readOnly":true,"description":"The pages the search returned, in the order it returned them.","items":{"$ref":"#/components/schemas/AdminOrganizationBackgroundCheckSource"}}}},"AdminOrganizationBackgroundCheck":{"type":"object","additionalProperties":false,"description":"The verdict of an organization background check, sent once its checks are complete.","required":["status"],"properties":{"status":{"$ref":"#/components/schemas/AdminOrganizationBackgroundCheckStatus","readOnly":true,"description":"Whether the organization is a real business operating under the name it gave, on the same scale as the individual checks."},"summary":{"type":"string","minLength":1,"readOnly":true,"description":"A few sentences a reviewer can read instead of the rows, naming what decided the verdict. When the status is `unknown` it says what was looked for and not found. Absent in the one case the check produced nothing legible at all."}}},"AdminOrganizationBackgroundCheckError":{"type":"object","additionalProperties":false,"description":"The terminal event of a background check that could not be produced. It says nothing about the organization: the check did not run, so no verdict follows from it.","required":["message"],"properties":{"message":{"type":"string","minLength":1,"readOnly":true,"description":"Why the check could not be produced, in a sentence a reviewer can act on."}}},"AdminOrganizationBackgroundCheckEvent":{"description":"One event of a background check's stream. Which shape arrives is named by the event's own type: a `searching` event carries a query, a `sources` event the pages that search returned, a `check` one row, and the terminal `complete` the verdict. A check that could not be produced ends with `error` instead.","oneOf":[{"$ref":"#/components/schemas/AdminOrganizationBackgroundCheckSearch"},{"$ref":"#/components/schemas/AdminOrganizationBackgroundCheckItem"},{"$ref":"#/components/schemas/AdminOrganizationBackgroundCheckSources"},{"$ref":"#/components/schemas/AdminOrganizationBackgroundCheck"},{"$ref":"#/components/schemas/AdminOrganizationBackgroundCheckError"}]},"BusinessEntityID":{"type":"string","minLength":1,"pattern":"^biz_[0-9a-hjkmnp-tv-z]{26}$","example":"biz_01krdgeqcxet5s7t44vh8rt9mg"},"UpdateAdminOrganizationBusinessEntity":{"type":"object","additionalProperties":false,"description":"Reassigns the organization's invoicing business entity. Changing the entity\nrecomputes the organization's tax treatment and affects which entity issues\nfuture invoices; existing invoices are unaffected. The new entity's default\ncurrency may differ from the organization's current billing currency.\n","required":["business_entity_id"],"properties":{"business_entity_id":{"$ref":"#/components/schemas/BusinessEntityID"}}},"AdminBusinessEntity":{"type":"object","additionalProperties":false,"description":"A Bird legal entity that invoices organizations. Determines the tax treatment and invoice numbering applied to an org's charges.","required":["id","name","country","default_currency_code","is_default"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/BusinessEntityID"}]},"name":{"type":"string","minLength":1,"readOnly":true,"description":"The entity's legal name."},"country":{"type":"string","minLength":2,"maxLength":2,"readOnly":true,"description":"ISO-3166 alpha-2 (uppercase) where the entity is established."},"default_currency_code":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/CurrencyCode"}]},"is_default":{"type":"boolean","readOnly":true,"description":"Whether this is the default entity assigned to new organizations."}}},"AdminTaxCategoryRate":{"type":"object","additionalProperties":false,"description":"A single active per-category tax rate resolved for the organization (used when tax is rated per product category, e.g. US sales tax).","required":["category","tax_type","tax_rate","source","effective_from"],"properties":{"category":{"type":"string","minLength":1,"readOnly":true,"description":"The product category this rate applies to.","enum":["telecom","messaging_saas","subscription_saas"]},"tax_type":{"type":"string","minLength":1,"readOnly":true,"enum":["none","vat","sales_tax","gst","vat_reverse_charge","exempt"]},"tax_rate":{"type":"string","minLength":1,"readOnly":true,"description":"Decimal-as-string (e.g. \"0.0925\" for 9.25%)."},"source":{"type":"string","minLength":1,"readOnly":true,"description":"Where the rate came from (e.g. the external tax provider)."},"effective_from":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminTaxProfileHistoryEntry":{"type":"object","additionalProperties":false,"description":"One entry in the organization's tax-profile change history, newest first.","required":["actor_type","tax_country","tax_type","tax_number_status","tax_rate_source","tax_rate_status","created_at"],"properties":{"actor_type":{"type":"string","minLength":1,"readOnly":true,"description":"What drove the change.","enum":["user","validate_tax_number","revalidation","avalara_fetch","address_change","entity_change"]},"actor_id":{"type":["string","null"],"readOnly":true,"description":"The acting user, when the change was made by a person."},"tax_country":{"type":["string","null"],"readOnly":true},"tax_number":{"type":["string","null"],"readOnly":true},"tax_type":{"type":"string","minLength":1,"readOnly":true,"enum":["none","vat","sales_tax","gst","vat_reverse_charge","exempt"]},"tax_rate":{"type":["string","null"],"readOnly":true,"description":"Decimal-as-string, or null for orgs rated per category."},"tax_number_status":{"type":"string","minLength":1,"readOnly":true,"enum":["none","pending","valid","invalid","admin_override"]},"tax_rate_source":{"type":"string","minLength":1,"readOnly":true,"enum":["country","org_category"]},"tax_rate_status":{"type":"string","minLength":1,"readOnly":true,"enum":["ready","pending","failed"]},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminTaxProfile":{"type":"object","additionalProperties":false,"description":"An organization's full tax profile for staff review: the stored profile, the\nper-category rates currently in effect, and the recent change history.\n","required":["configured","tax_type","tax_rate","tax_number_status","tax_exempt","tax_rate_source","tax_rate_status","category_rates","history"],"properties":{"configured":{"type":"boolean","readOnly":true,"description":"False when no tax profile has been set for the organization; the other fields then show the applied defaults."},"tax_country":{"type":["string","null"],"minLength":2,"maxLength":2,"readOnly":true,"description":"ISO-3166 alpha-2 (uppercase). Null if no profile is configured."},"tax_type":{"type":"string","minLength":1,"readOnly":true,"enum":["none","vat","sales_tax","gst","vat_reverse_charge","exempt"]},"tax_rate":{"type":"string","minLength":1,"readOnly":true,"description":"Decimal-as-string (e.g. \"0.21\"). Always \"0\" for orgs rated per category — see category_rates."},"tax_number":{"type":["string","null"],"readOnly":true},"tax_number_status":{"type":"string","minLength":1,"readOnly":true,"enum":["none","pending","valid","invalid","admin_override"]},"tax_number_validated_at":{"type":["string","null"],"format":"date-time","readOnly":true},"tax_number_validation_msg":{"type":["string","null"],"readOnly":true},"tax_exempt":{"type":"boolean","readOnly":true},"tax_rate_source":{"type":"string","minLength":1,"readOnly":true,"description":"Whether the rate comes from the country table or per-category rating.","enum":["country","org_category"]},"tax_rate_status":{"type":"string","minLength":1,"readOnly":true,"enum":["ready","pending","failed"]},"tax_rate_status_msg":{"type":["string","null"],"readOnly":true},"category_rates":{"type":"array","readOnly":true,"description":"Per-category rates currently in effect. Empty unless the org is rated per category.","items":{"$ref":"#/components/schemas/AdminTaxCategoryRate"}},"history":{"type":"array","readOnly":true,"description":"Recent tax-profile changes, newest first.","items":{"$ref":"#/components/schemas/AdminTaxProfileHistoryEntry"}},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true}}},"BillingTaxProfileUpdate":{"type":"object","additionalProperties":false,"description":"Request body for updating the tax profile. The tax country is not set here;\nit is always derived from the organization's business profile (the billing\naddress country if a billing address is set, otherwise the registration\naddress country). For `tax_number`, omission leaves the existing value\nunchanged, `null` clears it, and a non-empty string sets it.\n\nAn EU VAT number is rejected when its country prefix does not match the tax\ncountry derived from the address (for example, a German `DE…` number for an\norganization whose address is in France).\n","properties":{"tax_number":{"type":["string","null"],"description":"Tax registration number. For EU VAT numbers the country prefix must match the organization's address country (Greek numbers use the `EL` prefix for Greece). Send `null` to clear, or omit to leave unchanged.\n"}}},"AdminOrganizationBillingTerms":{"type":"object","additionalProperties":false,"required":["payment_terms_days"],"properties":{"payment_terms_days":{"type":"integer","format":"int32","minimum":0,"maximum":180,"description":"Days after the invoice date that a postpaid invoice falls due. This applies only while the organization has a credit limit. A prepaid organization is invoiced for information and its invoices carry no due date. 0 means the platform default of 30 days.","example":30}}},"AdminOrganizationBillingTermsUpdate":{"type":"object","additionalProperties":false,"required":["payment_terms_days"],"properties":{"payment_terms_days":{"type":"integer","format":"int32","minimum":0,"maximum":180,"description":"Days after the invoice date that a postpaid invoice falls due. Set 0 to restore the platform default of 30 days.","example":30}}},"DealQuoteID":{"type":"string","minLength":1,"pattern":"^dqt_[0-9a-hjkmnp-tv-z]{26}$","example":"dqt_01krdgeqcxet5s7t44vh8rt9mg"},"ContractID":{"type":"string","minLength":1,"pattern":"^ctr_[0-9a-hjkmnp-tv-z]{26}$","example":"ctr_01krdgeqcxet5s7t44vh8rt9mg"},"ContractVersionID":{"type":"string","minLength":1,"pattern":"^ctv_[0-9a-hjkmnp-tv-z]{26}$","example":"ctv_01krdgeqcxet5s7t44vh8rt9mg"},"AdminContractEffect":{"type":"string","minLength":1,"description":"Effect of the contract at the requested instant.\n\n- `scheduled` means its first version starts later.\n- `in_force` means it currently governs the organization.\n- `superseded` means a later agreement has commenced, including when that later agreement has since expired or terminated.\n- `expired` means the latest version that has started has ended.\n- `terminated` means the agreement ended early.\n","enum":["scheduled","in_force","superseded","expired","terminated"]},"AdminContractBillingEffect":{"type":"string","minLength":1,"description":"Whether billing is explicitly governed by an adopted contract. Managed describes ownership, not successful application.","enum":["not_enabled","managed"]},"AdminContractVersionKind":{"type":"string","minLength":1,"description":"Reason this contract version was recorded.\n\n- `initial` means the version records the signed agreement.\n- `amendment` means the parties changed an existing agreement.\n- `renewal` means the parties agreed to a new term.\n","enum":["initial","amendment","renewal"]},"AdminContractPaymentMode":{"type":"string","minLength":1,"description":"How the organization pays under the contract. `prepaid` uses an existing balance and has no credit limit. `postpaid` permits invoiced usage up to the positive `credit_limit`.","enum":["prepaid","postpaid"]},"AdminContractCreditLimit":{"description":"Positive credit limit in major currency units for postpaid terms, with up to six decimal places and a maximum of 9223372036854.775807. Larger amounts are rejected with 422. Omit or set to null for prepaid terms.","type":["string","null"],"maxLength":20,"pattern":"^[0-9]+(\\.[0-9]{1,6})?$","example":"9007199254.740993"},"AdminContractPricingMode":{"type":"string","minLength":1,"description":"Pricing classification of the agreement. `fixed` supports explicit attachment of agreed usage rate-card versions before adoption. `list_relative` remains reference-only; no published-list linkage is applied.","enum":["fixed","list_relative"]},"AdminContractTerms":{"type":"object","additionalProperties":false,"required":["payment_mode","currency_code","payment_terms_days","auto_renew","cola_bps","renewal_notice_days","pricing_mode"],"oneOf":[{"title":"Prepaid terms","properties":{"payment_mode":{"enum":["prepaid"]},"credit_limit":{"not":{"type":"string"}}}},{"title":"Postpaid terms","required":["credit_limit"],"properties":{"payment_mode":{"enum":["postpaid"]},"credit_limit":{"type":"string","minLength":1,"pattern":"[1-9]"}}}],"properties":{"payment_mode":{"$ref":"#/components/schemas/AdminContractPaymentMode"},"credit_limit":{"$ref":"#/components/schemas/AdminContractCreditLimit"},"currency_code":{"description":"Must match the organization's wallet currency.","$ref":"#/components/schemas/CurrencyCode"},"payment_terms_days":{"type":"integer","format":"int32","minimum":0,"description":"Number of days after an invoice is issued that payment is due."},"auto_renew":{"type":"boolean","description":"Records the parties' renewal intent. No renewal is created or activated automatically."},"cola_bps":{"type":"integer","format":"int32","minimum":0,"description":"Contracted annual cost-of-living adjustment in basis points."},"renewal_notice_days":{"type":"integer","format":"int32","minimum":0,"description":"Number of days before the end date when renewal notice is due."},"pricing_mode":{"$ref":"#/components/schemas/AdminContractPricingMode"},"notes":{"type":"string","default":"","description":"Staff notes recorded with these terms."}}},"AdminContractVersion":{"readOnly":true,"type":"object","additionalProperties":false,"required":["id","version","kind","automatic","starts_on","ends_on","terms","created_by","created_at"],"properties":{"amends_version_id":{"description":"Previous version this amendment builds on. Omitted for initial and legacy unlinked versions.","$ref":"#/components/schemas/ContractVersionID"},"id":{"$ref":"#/components/schemas/ContractVersionID"},"version":{"type":"integer","format":"int32","minimum":1},"kind":{"$ref":"#/components/schemas/AdminContractVersionKind"},"automatic":{"type":"boolean","description":"Whether the version was created by an automated process."},"starts_on":{"type":"string","minLength":1,"format":"date","description":"First UTC calendar date covered by this version."},"ends_on":{"type":"string","minLength":1,"format":"date","description":"Last UTC calendar date covered by this version."},"terms":{"$ref":"#/components/schemas/AdminContractTerms"},"created_by":{"description":"Staff member who recorded the version, or null when an automated process recorded it.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"created_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminContractSummary":{"readOnly":true,"unevaluatedProperties":false,"allOf":[{"type":"object","required":["id","organization_id","starts_on","effect","billing_effect","current_version"],"properties":{"id":{"$ref":"#/components/schemas/ContractID"},"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"adopted_at":{"type":"string","format":"date-time","minLength":1,"description":"When staff explicitly adopted this agreement for billing. Omitted for reference-only records."},"adopted_version_id":{"description":"Immutable initial version pinned when staff adopted the agreement. Later recorded amendments do not change this reference or its billing terms. Omitted before adoption.","$ref":"#/components/schemas/ContractVersionID"},"adopted_by":{"$ref":"#/components/schemas/UserID"},"starts_on":{"type":"string","minLength":1,"format":"date","description":"First UTC calendar date covered by the agreement."},"effect":{"description":"State at the requested instant, accounting for organization-wide precedence, termination, and whether the governing agreement has an unexpired applicable version.","$ref":"#/components/schemas/AdminContractEffect"},"billing_effect":{"description":"Whether this agreement was explicitly adopted for billing. Managed does not guarantee successful application; inspect the organization billing status.","$ref":"#/components/schemas/AdminContractBillingEffect"},"current_version":{"description":"This agreement's latest started version at the requested UTC date, including when superseded. Null before commencement, after expiry, or after termination.","oneOf":[{"$ref":"#/components/schemas/AdminContractVersion"},{"type":"null"}]}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminContractSummaryList":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminContractSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminContractBillingApplicationStatus":{"type":"string","minLength":1,"description":"Contract credit and payment-term application status. `not_enabled` means no agreement has been adopted; `scheduled` means adopted terms have not started; `pending` means application or date reconciliation remains; `applied` means both credit and payment terms were acknowledged; `blocked` means a prerequisite prevents application.","enum":["not_enabled","scheduled","pending","applied","blocked"]},"AdminContractBillingValues":{"type":"object","readOnly":true,"additionalProperties":false,"required":["currency_code","credit_limit","payment_terms_days"],"properties":{"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"credit_limit":{"type":"string","minLength":1,"description":"Credit limit in major currency units, preserving up to six decimal places. Zero means no credit.","example":"1000.25"},"payment_terms_days":{"type":"integer","format":"int32","minimum":0,"description":"Invoice payment-day override. Zero retains the billing platform default; it does not mean payment is due immediately."}}},"AdminContractBillingFailureCode":{"type":"string","minLength":1,"description":"Reason contract pricing, credit or payment terms could not be applied. `credit_limit_too_low` requires funding or settling usage; `currency_conversion_incomplete` requires completing wallet conversion; `wallet_unavailable` requires restoring the wallet; `wallet_currency_mismatch` requires engineering to investigate the currencies; `billing_temporarily_unavailable` retries automatically; `contract_boundary_due` awaits date reconciliation; `pricing_conflict` requires resolving competing or inconsistent rate cards; `pricing_unavailable` retries automatically.","enum":["credit_limit_too_low","currency_conversion_incomplete","wallet_unavailable","wallet_currency_mismatch","billing_temporarily_unavailable","contract_boundary_due","pricing_conflict","pricing_unavailable"]},"AdminOrganizationBillingStatus":{"type":"object","readOnly":true,"additionalProperties":false,"required":["organization_id","status","governing_contract_id","version_id","desired","revision","applied_revision","pricing_applied_revision","wallet_revision","failure_code","last_attempt_at","next_attempt_at","next_boundary_at"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"status":{"$ref":"#/components/schemas/AdminContractBillingApplicationStatus"},"governing_contract_id":{"description":"Adopted governing agreement selected at the last reconciliation, retained after expiry. Null before any adopted agreement starts.","oneOf":[{"$ref":"#/components/schemas/ContractID"},{"type":"null"}]},"version_id":{"description":"Adopted version selected at the last reconciliation. Null when that reconciliation found no effective version. A pending date boundary can make this reference stale.","oneOf":[{"$ref":"#/components/schemas/ContractVersionID"},{"type":"null"}]},"desired":{"description":"Terms requested by the last reconciliation. Null before the first adopted agreement starts. A pending date boundary can make these values stale.","oneOf":[{"$ref":"#/components/schemas/AdminContractBillingValues"},{"type":"null"}]},"revision":{"type":"integer","format":"int64","minimum":0,"description":"Current desired revision."},"applied_revision":{"type":"integer","format":"int64","minimum":0,"description":"Last revision fully applied to attached pricing, credit and payment terms."},"pricing_applied_revision":{"type":"integer","format":"int64","minimum":0,"description":"Last revision whose contract pricing was fully reconciled. Pricing withdrawal can complete while outstanding debt blocks the credit reset."},"wallet_revision":{"type":"integer","format":"int64","minimum":0,"description":"Last acknowledged credit-limit revision. Pending work can have changed credit before its acknowledgement arrives."},"failure_code":{"oneOf":[{"$ref":"#/components/schemas/AdminContractBillingFailureCode"},{"type":"null"}],"description":"Reason application is incomplete. credit_limit_too_low requires funding or settling outstanding usage; currency_conversion_incomplete requires completing the wallet currency conversion; wallet_unavailable requires restoring the wallet; wallet_currency_mismatch requires engineering to investigate inconsistent contract and wallet currencies before retrying; billing_temporarily_unavailable retries automatically; contract_boundary_due awaits date reconciliation; pricing_conflict requires resolving competing or inconsistent rate cards; pricing_unavailable retries automatically. Null when no failure is recorded."},"last_attempt_at":{"type":["string","null"],"format":"date-time","minLength":1,"description":"Last billing application attempt."},"next_attempt_at":{"type":["string","null"],"format":"date-time","minLength":1,"description":"Next scheduled automatic retry, if any."},"next_boundary_at":{"type":["string","null"],"format":"date-time","minLength":1,"description":"Next UTC contract date or termination boundary, if any."}}},"ProductID":{"type":"string","minLength":1,"pattern":"^prd_[0-9a-hjkmnp-tv-z]{26}$","example":"prd_01krdgeqcxet5s7t44vh8rt9mg"},"RateCardID":{"type":"string","minLength":1,"pattern":"^rtc_[0-9a-hjkmnp-tv-z]{26}$","example":"rtc_01krdgeqcxet5s7t44vh8rt9mg"},"RateCardVersionID":{"type":"string","minLength":1,"pattern":"^rcv_[0-9a-hjkmnp-tv-z]{26}$","example":"rcv_01krdgeqcxet5s7t44vh8rt9mg"},"AdminBillingRateCardStatus":{"type":"string","minLength":1,"enum":["draft","active","archived"],"description":"Publication lifecycle of a rate card."},"AdminContractRateCard":{"type":"object","readOnly":true,"additionalProperties":false,"required":["contract_version_id","product_id","rate_card_id","rate_card_version_id","status"],"properties":{"contract_version_id":{"$ref":"#/components/schemas/ContractVersionID"},"product_id":{"$ref":"#/components/schemas/ProductID"},"rate_card_id":{"$ref":"#/components/schemas/RateCardID"},"rate_card_version_id":{"$ref":"#/components/schemas/RateCardVersionID"},"status":{"$ref":"#/components/schemas/AdminBillingRateCardStatus","description":"Draft pricing is frozen for the contract; active pricing is published; archived pricing has been withdrawn permanently."}}},"AdminContractPricing":{"type":"object","readOnly":true,"additionalProperties":false,"required":["rate_cards"],"properties":{"rate_cards":{"type":"array","maxItems":100,"description":"Complete set of fixed usage rate cards attached to this contract. An empty set means no attached pricing.","items":{"$ref":"#/components/schemas/AdminContractRateCard"}}}},"AdminContractPricingUpdate":{"type":"object","additionalProperties":false,"required":["rate_card_version_ids"],"properties":{"rate_card_version_ids":{"type":"array","maxItems":100,"uniqueItems":true,"description":"Complete replacement set of dedicated draft rate-card versions, at most one per product. Each must belong to this organization and contain prices only in the contract currency. An empty set detaches all pricing before adoption.","items":{"$ref":"#/components/schemas/RateCardVersionID"}}}},"AdminContractAmendmentCreate":{"type":"object","additionalProperties":false,"required":["expected_version_id","starts_on","payment_mode","payment_terms_days"],"oneOf":[{"title":"Prepaid amendment","properties":{"payment_mode":{"enum":["prepaid"]},"credit_limit":{"not":{"type":"string"}}}},{"title":"Postpaid amendment","required":["credit_limit"],"properties":{"payment_mode":{"enum":["postpaid"]},"credit_limit":{"type":"string","minLength":1,"pattern":"[1-9]"}}}],"properties":{"expected_version_id":{"description":"Latest version being amended. A matching retry preserves the original amendment; stale or conflicting changes return 409.","$ref":"#/components/schemas/ContractVersionID"},"starts_on":{"type":"string","minLength":1,"format":"date","description":"First UTC date for the new terms. Must be today or later, strictly after the latest version starts and before the existing end date."},"payment_mode":{"$ref":"#/components/schemas/AdminContractPaymentMode"},"credit_limit":{"$ref":"#/components/schemas/AdminContractCreditLimit"},"payment_terms_days":{"type":"integer","format":"int32","minimum":0,"maximum":180,"description":"Invoice payment term in days. Zero uses the platform default."},"notes":{"type":"string","default":"","description":"Staff notes recorded with the amended terms."}}},"AdminContractAmendment":{"readOnly":true,"type":"object","additionalProperties":false,"required":["version","provisioning"],"properties":{"version":{"$ref":"#/components/schemas/AdminContractVersion"},"provisioning":{"$ref":"#/components/schemas/AdminOrganizationBillingStatus"}}},"AdminContractTerminationCreate":{"type":"object","additionalProperties":false,"required":["reason"],"properties":{"reason":{"type":"string","minLength":1,"maxLength":2000,"description":"Reason for ending the agreement. Must contain non-whitespace text."}}},"DealID":{"type":"string","minLength":1,"pattern":"^dl_[0-9a-hjkmnp-tv-z]{26}$","example":"dl_01krdgeqcxet5s7t44vh8rt9mg"},"AdminContractCreate":{"type":"object","additionalProperties":false,"required":["starts_on","ends_on","terms"],"properties":{"starts_on":{"type":"string","minLength":1,"format":"date","description":"First UTC calendar date covered by the initial contract version."},"ends_on":{"type":"string","minLength":1,"format":"date","description":"Last UTC calendar date covered by the initial contract version. Must be after `starts_on`."},"terms":{"$ref":"#/components/schemas/AdminContractTerms"}}},"AdminContractExecutionCreate":{"type":"object","additionalProperties":false,"required":["deal_id","quote_id","contract","signed_at","signing_evidence","signed_document"],"properties":{"deal_id":{"$ref":"#/components/schemas/DealID"},"quote_id":{"$ref":"#/components/schemas/DealQuoteID"},"contract":{"$ref":"#/components/schemas/AdminContractCreate"},"signed_at":{"type":"string","minLength":1,"format":"date-time","description":"Timestamp when all parties completed signing, no later than now."},"signing_evidence":{"type":"string","minLength":1,"maxLength":10000,"x-sensitive":true,"description":"Staff attestation identifying the signers, signing method and external signing reference."},"signed_document":{"type":"string","minLength":1,"format":"byte","maxLength":13981016,"writeOnly":true,"x-sensitive":true,"description":"Base64-encoded signed PDF, at most 10 MiB decoded. Stored privately without modifying its bytes."}}},"AdminContractExecutionSource":{"type":"string","minLength":1,"enum":["external","native"],"description":"Signing evidence source. The native value identifies historical executions recorded before native signing was retired."},"AdminContractExecution":{"type":"object","readOnly":true,"additionalProperties":false,"required":["contract_id","deal_id","quote_id","quote_fingerprint","source","signed_at","signing_evidence","document_sha256","document_size","recorded_by","created_at"],"properties":{"contract_id":{"$ref":"#/components/schemas/ContractID"},"deal_id":{"$ref":"#/components/schemas/DealID"},"quote_id":{"$ref":"#/components/schemas/DealQuoteID"},"quote_fingerprint":{"type":"string","minLength":64,"maxLength":73,"pattern":"^(legal-v1:)?[0-9a-f]{64}$","description":"Fingerprint of accepted quote inputs at recording. Historical executions may retain a version prefix."},"source":{"$ref":"#/components/schemas/AdminContractExecutionSource"},"signed_at":{"type":"string","minLength":1,"format":"date-time"},"signing_evidence":{"type":"string","minLength":1,"x-sensitive":true,"description":"Staff attestation of signers, signing method and external signing reference."},"document_sha256":{"type":"string","minLength":64,"maxLength":64,"pattern":"^[0-9a-f]{64}$","description":"Server-calculated SHA-256 of the exact signed PDF bytes."},"document_size":{"type":"integer","format":"int32","minimum":1,"description":"Signed PDF size in bytes."},"recorded_by":{"$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminContractStatus":{"type":"string","minLength":1,"description":"Agreement status at the requested instant. `signed` means the agreement has not been terminated. `terminated` means the agreement ended early.","enum":["signed","terminated"]},"AdminContract":{"readOnly":true,"unevaluatedProperties":false,"allOf":[{"type":"object","required":["id","starts_on","status","created_by","effect","billing_effect","current_version","versions"],"properties":{"id":{"$ref":"#/components/schemas/ContractID"},"terminated_at":{"type":"string","format":"date-time","minLength":1,"description":"Actual termination time, omitted until the agreement is terminated."},"terminated_by":{"$ref":"#/components/schemas/UserID"},"termination_reason":{"type":"string","minLength":1,"maxLength":2000,"description":"Recorded reason for termination, omitted until the agreement is terminated."},"adopted_at":{"type":"string","format":"date-time","minLength":1,"description":"When staff explicitly adopted this agreement for billing. Omitted for reference-only records."},"adopted_version_id":{"description":"Immutable initial version pinned when staff adopted the agreement. Later recorded amendments do not change this reference or its billing terms. Omitted before adoption.","$ref":"#/components/schemas/ContractVersionID"},"adopted_by":{"$ref":"#/components/schemas/UserID"},"starts_on":{"type":"string","minLength":1,"format":"date","description":"First UTC calendar date covered by the agreement."},"status":{"$ref":"#/components/schemas/AdminContractStatus"},"created_by":{"$ref":"#/components/schemas/UserID"},"effect":{"description":"State at the requested instant, accounting for organization-wide precedence, termination, and whether the governing agreement has an unexpired applicable version.","$ref":"#/components/schemas/AdminContractEffect"},"billing_effect":{"description":"Whether this agreement was explicitly adopted for billing. Managed does not guarantee successful application; inspect the organization billing status.","$ref":"#/components/schemas/AdminContractBillingEffect"},"current_version":{"description":"Latest version of this agreement that has started by the requested UTC date. Null before commencement, after the latest started version has expired, or after termination. Can be present when the agreement is superseded.","oneOf":[{"$ref":"#/components/schemas/AdminContractVersion"},{"type":"null"}]},"versions":{"type":"array","description":"All recorded versions in descending version order, latest first.","items":{"$ref":"#/components/schemas/AdminContractVersion"}}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminContractList":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminContract"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminContractBillingBlocker":{"type":"string","x-extensible-enum":["wallet_missing","currency_mismatch","credit_limit_below_usage"],"description":"A missing wallet prevents comparison. A currency mismatch requires a separate conversion. A credit limit below the current reserved usage minus balance cannot cover existing usage."},"AdminContractBillingPreview":{"type":"object","readOnly":true,"additionalProperties":false,"required":["as_of","governing_contract_id","current","proposed","blockers","billing_effect"],"properties":{"as_of":{"type":"string","format":"date-time","minLength":1,"description":"Instant used to evaluate recorded agreements, using UTC calendar dates."},"governing_contract_id":{"description":"Agreement that would govern at the evaluated instant. Null when none has commenced; expired or terminated agreements retain precedence over predecessors.","oneOf":[{"$ref":"#/components/schemas/ContractID"},{"type":"null"}]},"current":{"description":"Billing values read now. Null when the organization has no wallet.","oneOf":[{"$ref":"#/components/schemas/AdminContractBillingValues"},{"type":"null"}]},"proposed":{"description":"Terms that would apply if recorded agreements governed billing. Expired or terminated agreements propose zero credit and zero payment days. Null when no agreement governs, or when expiry defaults cannot be denominated because no wallet exists.","oneOf":[{"$ref":"#/components/schemas/AdminContractBillingValues"},{"type":"null"}]},"blockers":{"type":"array","description":"Known reasons the proposed values cannot be applied to the current wallet. Empty does not authorize activation. Currency mismatches are not converted; usage is compared only for matching currencies.","items":{"$ref":"#/components/schemas/AdminContractBillingBlocker"}},"billing_effect":{"$ref":"#/components/schemas/AdminContractBillingEffect"}}},"OrganizationPaymentSettings":{"type":"object","additionalProperties":false,"required":["three_ds_action"],"properties":{"three_ds_action":{"type":"string","minLength":1,"enum":["FORCE_3DS","SKIP_3DS","AUTO"],"description":"3-D Secure behavior for this organization's card payments.\n\n- `FORCE_3DS`: the default. It always requests a verification\n  challenge.\n- `SKIP_3DS`: it requests that the challenge be skipped when the card\n  network allows it.\n- `AUTO`: it lets the payment provider decide based on risk.\n"}}},"AdminOrganizationWatchlist":{"type":"object","additionalProperties":false,"required":["action","has_customer"],"properties":{"action":{"type":"string","minLength":1,"description":"Risk-watchlist decision applied to this organization's payments. One of none, allow, block, or request_3ds. none applies no override (the fraud engine decides); allow forces acceptance; block always declines; request_3ds always requests a verification challenge."},"has_customer":{"type":"boolean","readOnly":true,"description":"Whether the organization has a payment customer yet. When false, no watchlist decision can be set."},"reason":{"type":["string","null"],"description":"Optional note recorded with the watchlist decision."},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the current watchlist decision was last changed."}}},"AdminOrganizationWatchlistUpdate":{"type":"object","additionalProperties":false,"required":["action"],"properties":{"action":{"type":"string","minLength":1,"description":"Risk-watchlist decision to apply to this organization's payments. One of none, allow, block, or request_3ds. none applies no override (the fraud engine decides); allow forces acceptance; block always declines; request_3ds always requests a verification challenge."},"reason":{"type":["string","null"],"description":"Optional note recorded with the watchlist decision."}}},"PaymentConsentID":{"type":"string","minLength":1,"pattern":"^pyc_[0-9a-hjkmnp-tv-z]{26}$","example":"pyc_01krdgeqcxet5s7t44vh8rt9mg"},"PaymentIntentID":{"type":"string","minLength":1,"pattern":"^pyi_[0-9a-hjkmnp-tv-z]{26}$","example":"pyi_01krdgeqcxet5s7t44vh8rt9mg"},"AdminBillingPaymentConsentLastCharge":{"type":"object","additionalProperties":false,"description":"The most recent charge that succeeded against a saved payment method.","required":["payment_intent_id","amount","charged_at"],"properties":{"payment_intent_id":{"readOnly":true,"$ref":"#/components/schemas/PaymentIntentID"},"amount":{"$ref":"#/components/schemas/Money","readOnly":true,"description":"What was charged."},"charged_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the charge succeeded, taken from the settlement that wrote the succeeded status rather than from when the charge was first attempted."}}},"AdminBillingPaymentConsent":{"type":"object","additionalProperties":false,"required":["id","provider","external_id","status","is_default","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PaymentConsentID"},"provider":{"type":"string","minLength":1,"readOnly":true,"description":"Payment provider name.","example":"airwallex"},"external_id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier of this saved payment method at the payment provider.","example":"cst_hkdmr7v9rg1j5g4azy6"},"status":{"type":"string","minLength":1,"readOnly":true,"description":"Lifecycle status of the saved payment method.","enum":["pending","verified","disabled","unusable"],"example":"verified"},"card_brand":{"type":["string","null"],"readOnly":true,"description":"Card brand (e.g. visa, mastercard).","example":"visa"},"card_last_four":{"type":["string","null"],"readOnly":true,"description":"Last four digits of the card number.","example":"4242"},"card_bin":{"type":["string","null"],"readOnly":true,"description":"Bank identification number (first digits) of the card.","example":"424242"},"card_bank_name":{"type":["string","null"],"readOnly":true,"description":"Name of the issuing bank, when known.","example":"CHASE BANK USA, N.A."},"card_holder_name":{"type":["string","null"],"readOnly":true,"description":"Cardholder name as entered at verification, when known.","example":"Jane Doe"},"is_default":{"type":"boolean","readOnly":true,"description":"Whether this is the organization's default payment method."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"disabled_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the payment method was disabled. Null while active."},"last_charge":{"readOnly":true,"description":"The most recent charge that succeeded against this saved card. Explicitly null when the card has never been charged off-session, and also when its charges predate the link between a charge and the card it used, so a null does not prove the card has never been used.","oneOf":[{"$ref":"#/components/schemas/AdminBillingPaymentConsentLastCharge"},{"type":"null"}]}}},"AdminBillingPaymentConsentList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingPaymentConsent"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"WalletID":{"type":"string","minLength":1,"pattern":"^wal_[0-9a-hjkmnp-tv-z]{26}$","example":"wal_01krdgeqcxet5s7t44vh8rt9mg"},"ResourceLink":{"type":"object","additionalProperties":false,"description":"A labelled external link attached to a resource. Rendered by clients as-is; the server owns the URL scheme.\n","required":["name","title","url"],"properties":{"name":{"type":"string","minLength":1,"readOnly":true,"description":"Stable machine key identifying the link (for example, for icon selection).","example":"airwallex_intent"},"title":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable label to display for the link.","example":"View on Airwallex"},"url":{"type":"string","minLength":1,"format":"uri","readOnly":true,"description":"Absolute URL the link points to.","example":"https://www.airwallex.com/app/acquiring/list/details/bacda621-fe6b-40c9-bc5f-9117dac0ffa9/int_abc123"}}},"AdminBillingPaymentIntentRefundStatus":{"type":"string","minLength":1,"description":"How much of a payment has been refunded: `none` (nothing refunded), `partially_refunded` (some but not all of the amount refunded), or `refunded` (fully refunded).\n","enum":["none","partially_refunded","refunded"],"example":"none"},"AdminBillingPaymentIntent":{"type":"object","additionalProperties":false,"required":["id","wallet_id","provider","amount","currency_code","status","source","created_at","updated_at","refunded_amount","refund_status"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PaymentIntentID"},"wallet_id":{"readOnly":true,"$ref":"#/components/schemas/WalletID","description":"Wallet this payment was destined to credit."},"provider":{"type":"string","minLength":1,"readOnly":true,"description":"Payment provider name.","example":"airwallex"},"external_id":{"type":["string","null"],"readOnly":true,"description":"Identifier of this payment at the payment provider. Null until the provider has registered the payment.","example":"int_hkdmr7v9rg1j5g4azy6"},"amount":{"type":"string","minLength":1,"readOnly":true,"description":"Payment amount as a decimal string.","example":"50.00"},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code of the payment.","example":"USD"},"status":{"type":"string","minLength":1,"readOnly":true,"description":"Lifecycle status of the payment. `pending`: created, awaiting the payer. `requires_action`: waiting on payer authentication (e.g. 3-D Secure). `processing`: submitted to the provider. `succeeded`: settled and credited. `blocked`: the last attempt was declined by risk screening; the payment can still be retried. `declined`: the last attempt failed, whether the issuer declined it or authentication did not complete; the payment can still be retried, usually with a different card. `failed`: the provider rejected the charge request or an error ended the payment; terminal. `canceled`: abandoned before completion.\n","enum":["pending","requires_action","processing","succeeded","blocked","declined","failed","canceled"],"example":"succeeded"},"failure_reason":{"type":["string","null"],"readOnly":true,"description":"Provider-reported reason for a failed payment. Null unless the payment failed."},"source":{"type":"string","minLength":1,"readOnly":true,"description":"How the payment was initiated or collected.\n\n- `manual`: a user-initiated top-up.\n- `auto_topup`: triggered automatically by the balance threshold.\n- `overage_collection`: a legacy overage sweep collecting a wallet deficit off-session.\n- `automatic_collection`: a shared automatic payment funding renewals, unpaid usage, or the automatic top-up buffer.\n","enum":["manual","auto_topup","overage_collection","automatic_collection"],"example":"manual"},"metadata":{"type":"object","additionalProperties":true,"readOnly":true,"description":"Additional context recorded with the payment, such as automatic top-up attempt details."},"urls":{"type":"array","readOnly":true,"description":"Contextual external links for this payment. Payments registered at the provider include a \"View on Airwallex\" link; others have none.\n","items":{"$ref":"#/components/schemas/ResourceLink"}},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"refunded_amount":{"type":"string","minLength":1,"readOnly":true,"description":"Total refunded against this payment as a decimal string. \"0.00\" when nothing has been refunded.","example":"0.00"},"refund_status":{"allOf":[{"$ref":"#/components/schemas/AdminBillingPaymentIntentRefundStatus"}],"readOnly":true}}},"AdminBillingPaymentIntentList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingPaymentIntent"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"PaymentRefundID":{"type":"string","minLength":1,"pattern":"^pyr_[0-9a-hjkmnp-tv-z]{26}$","example":"pyr_01krdgeqcxet5s7t44vh8rt9mg"},"WalletTransactionID":{"type":"string","minLength":1,"pattern":"^wtx_[0-9a-hjkmnp-tv-z]{26}$","example":"wtx_01krdgeqcxet5s7t44vh8rt9mg"},"AdminBillingPaymentRefund":{"type":"object","additionalProperties":false,"required":["id","payment_intent_id","amount","currency_code","status","reason","wallet_clawback","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PaymentRefundID"},"payment_intent_id":{"readOnly":true,"$ref":"#/components/schemas/PaymentIntentID","description":"Payment this refund reverses."},"external_id":{"type":["string","null"],"readOnly":true,"description":"Identifier of this refund at the payment provider. Null until the provider has registered the refund.","example":"rfd_hkpdbybkch3ovunwjy1_1raukh"},"amount":{"type":"string","minLength":1,"readOnly":true,"description":"Refund amount as a decimal string.","example":"25.00"},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code, inherited from the payment.","example":"USD"},"status":{"type":"string","minLength":1,"readOnly":true,"description":"Lifecycle status of the refund: `pending` (submitted to the provider, awaiting confirmation), `confirmed` (the provider accepted the refund; the wallet clawback, if requested, has been applied), or `failed` (the provider rejected the refund; no money moved).\n","enum":["pending","confirmed","failed"],"example":"confirmed"},"reason":{"type":"string","minLength":1,"maxLength":128,"readOnly":true,"description":"Staff-entered reason recorded with the refund."},"wallet_clawback":{"type":"boolean","readOnly":true,"description":"Whether the organization's wallet was debited by the refunded amount. False means the organization keeps the credit."},"wallet_transaction_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WalletTransactionID"},{"type":"null"}],"description":"Wallet transaction recording the clawback debit. Null when no clawback was requested, or before the refund is confirmed."},"failure_reason":{"type":["string","null"],"readOnly":true,"description":"Provider-reported reason the refund failed. Null unless the refund failed."},"actor_id":{"type":["string","null"],"readOnly":true,"description":"Staff user who issued the refund."},"urls":{"type":"array","readOnly":true,"description":"Contextual external links for this refund. Refunds registered at the provider include a \"View on Airwallex\" link; others have none.\n","items":{"$ref":"#/components/schemas/ResourceLink"}},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"confirmed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the provider confirmed the refund. Null until confirmed."}}},"AdminBillingPaymentRefundList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingPaymentRefund"}}}},"AdminBillingPaymentRefundCreate":{"type":"object","additionalProperties":false,"required":["reason"],"properties":{"amount":{"type":"string","minLength":1,"description":"Amount to refund as a decimal string. Omit to refund the full unrefunded remainder of the payment.","example":"25.00"},"reason":{"type":"string","minLength":1,"maxLength":128,"description":"Why this refund is being issued. Recorded on the refund and sent to the payment provider.","example":"Duplicate charge"},"wallet_clawback":{"type":"boolean","default":true,"description":"Whether to debit the organization's wallet by the refunded amount. Leave true to keep the wallet in step with the card. Set false for a goodwill refund where the organization keeps the credit.\n"}}},"AdminPaymentDisputeStatus":{"type":"string","minLength":1,"enum":["REQUIRES_RESPONSE","CHALLENGED","ACCEPTED","REVERSED","WON","LOST","PENDING_CLOSURE","EXPIRED","PENDING_DECISION"],"description":"Dispute status. `REQUIRES_RESPONSE` needs an accept or challenge. `CHALLENGED` means evidence was submitted. `ACCEPTED` means the dispute was accepted and the payment refunded. `REVERSED` means the issuer reversed the dispute. `WON` and `LOST` are final outcomes. `PENDING_CLOSURE` and `PENDING_DECISION` are under review. `EXPIRED` means the response window lapsed.\n"},"AdminPaymentDisputeStage":{"type":"string","minLength":1,"enum":["RFI","PRE_CHARGEBACK","CHARGEBACK","PRE_ARBITRATION","ARBITRATION"],"description":"Dispute lifecycle stage. `RFI` is a request for information from the issuer. `PRE_CHARGEBACK` is an inquiry before a formal chargeback. `CHARGEBACK` means a formal chargeback is in progress. `PRE_ARBITRATION` means the issuer contests after evidence was submitted. `ARBITRATION` means the card scheme decides the outcome.\n"},"AdminPaymentDisputeEvidenceDocumentKind":{"type":"string","minLength":1,"description":"Where a dispute evidence document came from. `ISSUER` is the issuing bank's own attachment. `GENERATED` is a file the provider rendered from Bird's text evidence. `PRIMARY`, `ORDER`, `CUSTOMER` and `OTHER` are the categories Bird chooses when it files evidence itself.\n","enum":["ISSUER","PRIMARY","ORDER","CUSTOMER","OTHER","GENERATED"],"example":"PRIMARY"},"AdminPaymentDisputeEvidenceDocument":{"type":"object","description":"One document attached to a dispute, by the issuing bank or by Bird. This is the read side; AdminPaymentDisputeDocument is the bundle a challenge submits.\n","additionalProperties":false,"required":["file_id","kind","unavailable"],"properties":{"file_id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier used to retrieve the document."},"kind":{"readOnly":true,"$ref":"#/components/schemas/AdminPaymentDisputeEvidenceDocumentKind"},"description":{"type":["string","null"],"readOnly":true,"description":"Note filed with the document, or its source field for evidence filed before typed documents existed."},"filename":{"type":["string","null"],"readOnly":true,"description":"Name of the stored file. Taken from the file service when it resolves the document, and otherwise read from the identifier, which carries the name the document was filed under.\n"},"content_type":{"type":["string","null"],"readOnly":true,"description":"Media type reported by the file service.","example":"application/pdf"},"size":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Stored size in bytes."},"unavailable":{"type":"boolean","readOnly":true,"description":"True when the payment provider's file service does not resolve this document, so a request for it fails. The provider does not distinguish a file it has dropped from one it declines to resolve, and it answers a dispute's issuer documents that way, so both read as unavailable. A lookup that fails outright leaves this false, because the outcome is unknown rather than negative. The document stays listed because the dispute records that it was filed.\n"}}},"AdminPaymentDisputeChallengeReason":{"type":"string","minLength":1,"description":"Why Bird is challenging the dispute.","enum":["CUSTOMER_WITHDRAWN","CUSTOMER_REFUNDED","PRODUCT_RECEIVED","PURCHASE_HISTORY","NOT_ENTITLED","SEPARATE_PRODUCT","AUTHENTIC_PRODUCT","OTHER_REASONS"]},"AdminPaymentDisputeCustomerInfo":{"type":"object","description":"What Bird knows about the cardholder, used to rebut a chargeback.","additionalProperties":false,"properties":{"name":{"type":"string","maxLength":255,"description":"Cardholder name on file."},"email":{"type":"string","maxLength":255,"description":"Cardholder email on file."},"phone_number":{"type":"string","maxLength":50,"description":"Cardholder phone number on file."},"ip":{"type":"string","maxLength":45,"description":"IP address recorded at the time of purchase."},"device_id":{"type":"string","maxLength":255,"description":"Device identifier recorded at the time of purchase."},"billing_address":{"type":"string","maxLength":500,"description":"Billing address on file."}}},"AdminPaymentDisputeChallengeRecord":{"type":"object","description":"One challenge already filed on a dispute, as the provider reports it back.","additionalProperties":false,"required":["stage","documents"],"properties":{"stage":{"readOnly":true,"example":"CHARGEBACK","$ref":"#/components/schemas/AdminPaymentDisputeStage"},"method":{"type":["string","null"],"readOnly":true,"description":"How the challenge was filed. `STANDARD` is the provider's normal flow and the only one Bird files through. `AI_DISPUTE_AUTOMATION` is the provider assembling the response from structured evidence, which Bird does not use. Passed through from the provider.\n","example":"STANDARD"},"challenged_at":{"type":"string","format":"date-time","readOnly":true,"description":"When the challenge was filed. Absent when the provider reports no time."},"challenged_by":{"type":["string","null"],"readOnly":true,"description":"Who filed the challenge, as the provider records it."},"reason":{"readOnly":true,"description":"Why the dispute was challenged. Absent when the provider does not report one.","oneOf":[{"$ref":"#/components/schemas/AdminPaymentDisputeChallengeReason"},{"type":"null"}]},"product_description":{"type":["string","null"],"readOnly":true,"description":"What the cardholder was told they bought."},"refund_refusal_reason":{"type":["string","null"],"readOnly":true,"description":"Why a refund was refused."},"customer_info":{"readOnly":true,"description":"Cardholder details asserted in this challenge. Absent when none were sent.","$ref":"#/components/schemas/AdminPaymentDisputeCustomerInfo"},"documents":{"type":"array","readOnly":true,"description":"Evidence filed with this challenge.","items":{"$ref":"#/components/schemas/AdminPaymentDisputeEvidenceDocument"}}}},"AdminPaymentDisputeRefundReason":{"type":"string","minLength":1,"description":"Why a refund is being offered when accepting a dispute. The payment provider accepts only these values, so a free-text reason cannot be forwarded.\n","enum":["REQUESTED_BY_CUSTOMER","DUPLICATE","FRAUDULENT","ABANDONED","OTHERS"]},"AdminPaymentDisputeRefund":{"type":"object","description":"A refund offered to the cardholder when accepting a dispute.","additionalProperties":false,"required":["amount"],"properties":{"amount":{"type":"string","minLength":1,"pattern":"^\\d+(\\.\\d{1,2})?$","description":"Refund amount in the dispute's own currency. Omit the enclosing refund object to accept the dispute in full."},"reason":{"$ref":"#/components/schemas/AdminPaymentDisputeRefundReason"}}},"AdminPaymentDisputeAcceptanceRecord":{"type":"object","description":"One concession already filed on a dispute.","additionalProperties":false,"properties":{"stage":{"readOnly":true,"description":"The stage the dispute was conceded at.","oneOf":[{"$ref":"#/components/schemas/AdminPaymentDisputeStage"},{"type":"null"}]},"accepted_at":{"type":"string","format":"date-time","readOnly":true,"description":"When the dispute was conceded. Absent when the provider reports no time."},"accepted_by":{"type":["string","null"],"readOnly":true,"description":"Who conceded the dispute, as the provider records it."},"reason":{"type":["string","null"],"readOnly":true,"description":"Why the dispute was conceded, as the provider categorises it: one of `AGREEMENT_REACHED_WITH_CUSTOMER`, `CUSTOMER_RELATIONSHIP_MAINTENANCE`, `LOW_VALUE_TRANSACTION`, `VALID_CUSTOMER_DISPUTE`, `NO_ACTION_TAKEN_BY_MERCHANT`, `RDR_AUTO_ACCEPTED`, `COLLABORATION_ACCEPTED_MANUAL`, `COLLABORATION_AUTO_ACCEPTED`, `COLLABORATION_AUTO_ACCEPTED_BY_EXPIRY` or `OTHERS`. Passed through from the provider, which concedes some disputes on Bird's behalf.\n","example":"AGREEMENT_REACHED_WITH_CUSTOMER"},"description":{"type":["string","null"],"readOnly":true,"description":"How the provider labelled the concession."},"refund":{"readOnly":true,"description":"Refund offered with the concession. Absent when none was.","$ref":"#/components/schemas/AdminPaymentDisputeRefund"}}},"AdminPaymentDisputeRefundRef":{"type":"object","description":"A refund on the payment this dispute refers to.","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"Provider refund identifier."},"acquirer_reference_number":{"type":["string","null"],"readOnly":true,"description":"Acquirer reference number of the refund."}}},"AdminPaymentDispute":{"type":"object","additionalProperties":false,"required":["id","stage","status","currency_code","created_at","updated_at"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"Airwallex dispute identifier.","example":"dpt_hkdmr7v9rg1j5g4azy6"},"payment_intent_id":{"type":["string","null"],"readOnly":true,"description":"The disputed payment intent at the provider."},"customer_id":{"type":["string","null"],"readOnly":true,"description":"Airwallex customer identifier of the original payment. Maps to a Bird organization's payment customer."},"customer_name":{"type":["string","null"],"readOnly":true,"description":"Customer name recorded by the provider for the original payment."},"merchant_order_id":{"type":["string","null"],"readOnly":true,"description":"Merchant order identifier recorded on the original payment."},"amount":{"type":["string","null"],"readOnly":true,"description":"Dispute amount as a decimal string, when reported by the provider.","example":"50.00"},"currency_code":{"$ref":"#/components/schemas/CurrencyCode","readOnly":true,"description":"ISO 4217 currency code of the dispute amount.","example":"USD"},"reason_type":{"type":["string","null"],"readOnly":true,"description":"Categorised dispute reason, e.g. FRAUDULENT, DUPLICATE_CHARGE."},"reason_code":{"type":["string","null"],"readOnly":true,"description":"Issuer/scheme original reason code."},"reason_description":{"type":["string","null"],"readOnly":true,"description":"The provider's longer description of the dispute reason."},"issuer_comment":{"type":["string","null"],"readOnly":true,"description":"The issuing bank's own comment on the dispute."},"issuer_documents":{"type":"array","readOnly":true,"description":"Documents the issuing bank attached to the dispute.","items":{"$ref":"#/components/schemas/AdminPaymentDisputeEvidenceDocument"}},"challenges":{"type":"array","readOnly":true,"description":"Challenges already filed, in the order the provider reports them.","items":{"$ref":"#/components/schemas/AdminPaymentDisputeChallengeRecord"}},"acceptances":{"type":"array","readOnly":true,"description":"Concessions already filed.","items":{"$ref":"#/components/schemas/AdminPaymentDisputeAcceptanceRecord"}},"mode":{"type":["string","null"],"readOnly":true,"description":"How the card scheme is handling the dispute, from the chargeback stage onwards. `ALLOCATION` means the scheme assigns liability from the data it already holds, and `COLLABORATION` means the two banks exchange evidence. Absent before the chargeback stage. Passed through from the provider, so treat an unrecognized value as a newer scheme process rather than an error.\n","example":"ALLOCATION"},"payment_method_type":{"type":["string","null"],"readOnly":true,"description":"Payment method type of the original payment. Bird charges cards, and the provider reports either the family (`CARD`) or the scheme (`VISA`). Passed through from the provider.\n","example":"CARD"},"transaction_type":{"type":["string","null"],"readOnly":true,"description":"What the cardholder disputed. `PAYMENT` is the original charge and `REFUND` is a refund of it. Passed through from the provider.\n","example":"PAYMENT"},"card_brand":{"type":["string","null"],"readOnly":true,"description":"Card brand of the original payment, reported by the provider in lower case (`visa`, `mastercard`, `maestro`, `union pay`, `american express`, `jcb`, `diners club international`, `discover`). Absent unless the payment method was a card. Passed through from the provider, so treat an unrecognized value as a brand it has since added.\n","example":"visa"},"acquirer_reference_number":{"type":["string","null"],"readOnly":true,"description":"Acquirer reference number of the original payment."},"payment_attempt_id":{"type":["string","null"],"readOnly":true,"description":"The disputed payment attempt at the provider."},"refunds":{"type":"array","readOnly":true,"description":"Refunds on the payment this dispute refers to.","items":{"$ref":"#/components/schemas/AdminPaymentDisputeRefundRef"}},"stage":{"readOnly":true,"example":"CHARGEBACK","$ref":"#/components/schemas/AdminPaymentDisputeStage"},"status":{"readOnly":true,"example":"REQUIRES_RESPONSE","$ref":"#/components/schemas/AdminPaymentDisputeStatus"},"due_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Deadline to respond, when the current stage awaits a response."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"urls":{"type":"array","readOnly":true,"description":"Contextual external links (e.g. \"View on Airwallex\").","items":{"$ref":"#/components/schemas/ResourceLink"}}}},"AdminPaymentDisputeList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminPaymentDispute"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminLegitimacyFactor":{"type":"object","additionalProperties":false,"description":"One factor's signed contribution to the composed legitimacy score.","required":["key","points"],"properties":{"key":{"type":"string","minLength":1,"readOnly":true,"description":"The factor's stable identifier (for example, bounce_rate or org_age)."},"points":{"type":"integer","readOnly":true,"description":"How many points this factor added (positive) or subtracted (negative)."},"value":{"type":"number","format":"double","readOnly":true,"description":"The factor's underlying measured input — for example a 30-day bounce rate of 0.0082, an organization age in days, a Sift risk score, or a count of recent reviewer rejections. Omitted for factors that have no numeric input, such as the paid-plan bonus or the banned floor."}}},"AdminLegitimacyOverride":{"type":"object","additionalProperties":false,"description":"An admin pin of an organization's tier, in force until cleared or expired.","required":["tier","actor","reason","created_at"],"properties":{"tier":{"type":"string","minLength":1,"readOnly":true,"enum":["restricted","probation","standard","trusted"],"description":"The pinned tier."},"actor":{"type":"string","minLength":1,"readOnly":true,"description":"The staff identity that set the pin."},"reason":{"type":"string","minLength":1,"readOnly":true,"description":"Why the pin was applied."},"expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the pin lapses, or null for an indefinite pin."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the pin was set."}}},"AdminLegitimacyScoreEvent":{"type":"object","additionalProperties":false,"description":"One entry in an organization's legitimacy score history.","required":["kind","delta","score","tier","actor","reason","created_at"],"properties":{"kind":{"type":"string","minLength":1,"readOnly":true,"description":"What caused this change (for example, recompute, reviewer_reject, admin_spam_label, override_set)."},"delta":{"type":"integer","readOnly":true,"description":"The point change this event represents."},"score":{"type":"integer","readOnly":true,"description":"The score recorded at the time of the event."},"tier":{"type":"string","minLength":1,"readOnly":true,"description":"The tier recorded at the time of the event."},"actor":{"type":"string","minLength":1,"readOnly":true,"description":"Who or what triggered the change (a staff identity, or the system for scheduled recomputes)."},"reason":{"type":"string","minLength":1,"readOnly":true,"description":"A short explanation of the change."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the event was recorded."}}},"AdminLegitimacyScore":{"type":"object","additionalProperties":false,"description":"An organization's internal sender legitimacy standing, for abuse review. The score is not visible to customers.","required":["score","tier","effective_tier","hourly_peak","computed_at","factors","history"],"properties":{"score":{"type":"integer","readOnly":true,"description":"The computed legitimacy score, 0–100 (higher is more legitimate)."},"tier":{"type":"string","minLength":1,"readOnly":true,"enum":["restricted","probation","standard","trusted"],"description":"The tier the computed score maps to."},"effective_tier":{"type":"string","minLength":1,"readOnly":true,"enum":["restricted","probation","standard","trusted"],"description":"The tier in force, after any active admin override."},"hourly_peak":{"type":"integer","readOnly":true,"description":"The trailing hourly send peak used as the surge baseline."},"computed_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the score was last recomputed."},"factors":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminLegitimacyFactor"},"description":"The per-factor breakdown that composed the score."},"override":{"$ref":"#/components/schemas/AdminLegitimacyOverride"},"history":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminLegitimacyScoreEvent"},"description":"Recent score history, newest first."}}},"AdminLegitimacyTierOverrideUpdate":{"type":"object","additionalProperties":false,"required":["tier","reason"],"properties":{"tier":{"type":"string","minLength":1,"enum":["restricted","probation","standard","trusted"],"description":"The tier to pin the organization to, overriding the computed tier."},"reason":{"type":"string","minLength":1,"description":"Why the tier is being pinned. Recorded in the score history."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the pin should lapse. Omit for an indefinite pin."}}},"AdminLegitimacyOrganization":{"type":"object","additionalProperties":false,"required":["organization_id","tier","score"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"tier":{"type":"string","minLength":1,"readOnly":true,"enum":["restricted","probation","standard","trusted"],"description":"The organization's effective sender-legitimacy tier."},"score":{"type":"integer","readOnly":true,"description":"The organization's sender-legitimacy score, from 0 to 100."}}},"AdminLegitimacyOrganizationList":{"type":"object","additionalProperties":false,"required":["organizations"],"properties":{"organizations":{"type":"array","readOnly":true,"description":"The organizations matching the tier, each with its legitimacy tier and score.","items":{"$ref":"#/components/schemas/AdminLegitimacyOrganization"}}}},"AdminTrustProviderKind":{"type":"string","minLength":1,"enum":["persona","bird_native","sift"],"description":"The system that establishes whether a trust requirement is met."},"AdminTrustGate":{"type":"object","additionalProperties":false,"required":["slug","name","provider_kind","status","source"],"properties":{"slug":{"type":"string","minLength":1,"readOnly":true,"description":"Stable identifier for the trust requirement.","example":"identity_verified"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable name of the trust requirement.","example":"Identity verified"},"provider_kind":{"allOf":[{"$ref":"#/components/schemas/AdminTrustProviderKind"}],"readOnly":true},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["unmet","pending","satisfied","revoked"],"description":"The requirement's current state for this organization, after any admin override is applied."},"source":{"type":"string","minLength":1,"readOnly":true,"enum":["provider","override_grant","override_deny"],"description":"Why the status is what it is — derived from the provider, or forced by an admin grant or deny override."}}},"AdminTrustGateList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The organization's trust requirements, each with its resolved status.","items":{"$ref":"#/components/schemas/AdminTrustGate"}}}},"AdminTrustGateOverrideUpdate":{"type":"object","additionalProperties":false,"required":["decision","reason"],"properties":{"decision":{"type":"string","minLength":1,"enum":["grant","deny"],"description":"Force the requirement satisfied (grant) or unmet (deny), regardless of the provider-derived status."},"reason":{"type":"string","minLength":1,"description":"Why this override is being applied. Recorded in the trust audit history."}}},"AdminTrustVerification":{"type":"object","additionalProperties":false,"required":["cursor","id","persona_inquiry_id","organization_id","gate_slug","user_id","reference_id","persona_template_id","persona_account_id","template_version_id","completed_at","decisioned_at","terminal_status","created_at"],"properties":{"cursor":{"type":"string","minLength":1,"description":"Opaque pagination boundary for this exact row, encoding (created_at, id). Reusable across regions; pass it unchanged as `starting_after` or `ending_before`.\n"},"id":{"type":"string","minLength":1,"readOnly":true,"description":"Unique identifier of the verification record."},"persona_inquiry_id":{"type":"string","minLength":1,"readOnly":true,"description":"The Persona inquiry this record is the evidence for. Use it to retrieve the verified facts from Persona, which stays the system of record for them.","example":"inq_4kL9mNpQrStUvWxYz2345678"},"organization_id":{"allOf":[{"$ref":"#/components/schemas/OrganizationID"}],"readOnly":true,"description":"The organization the verification was completed for."},"gate_slug":{"type":"string","minLength":1,"readOnly":true,"description":"The trust requirement the verification was launched against, identified by its stable slug.","example":"identity_verified"},"user_id":{"readOnly":true,"description":"The person who consented by completing the flow. Null on records opened before the user id joined the reference-id contract, which cannot be attributed to a Bird user.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"reference_id":{"type":"string","minLength":1,"readOnly":true,"description":"The reference string sent to Persona, stored verbatim rather than recomputed, so the record shows what the vendor was actually given."},"persona_template_id":{"type":"string","minLength":1,"readOnly":true,"description":"The Persona inquiry template the verification ran against.","example":"itmpl_4kL9mNpQrStUvWxYz23"},"persona_account_id":{"type":["string","null"],"readOnly":true,"description":"The Persona account the inquiry resolved to. Null until Persona assigns one, which happens no earlier than the decision."},"template_version_id":{"type":["string","null"],"readOnly":true,"description":"Which version of the template's wording the person saw. The wording itself is not archived, so this is a pointer into Persona rather than standalone evidence. Null until the first outcome webhook lands.","example":"itmplv_4kL9mNpQrStUvWxYz23"},"completed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the person finished the flow, taken from the inquiry's own timestamp. This is the consent moment. Null while the verification is still in flight. Distinct from `decisioned_at`, because finishing the flow is not Persona adjudicating it."},"decisioned_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When Persona adjudicated the inquiry. Null until the approve or decline webhook lands. Records the first adjudication, so a Persona reviewer re-deciding an inquiry does not move it."},"terminal_status":{"type":["string","null"],"readOnly":true,"description":"The terminal status Persona decided on, in Persona's own vocabulary rather than a Bird status. Null while the inquiry is still in flight. The values seen so far are `approved`, `declined`, `expired`, `failed`, `needs_review`, and `marked_for_review`; treat the set as open, since it is the provider's to extend.","example":"approved"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the verification was launched."}}},"AdminTrustVerificationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of verification records, newest first.","items":{"$ref":"#/components/schemas/AdminTrustVerification"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminTrustUnavailablePolicy":{"type":"string","minLength":1,"enum":["pending_review","fail_open"],"description":"The intended handling for a requirement when its provider status can't be confirmed — hold it for review, or treat it as met. Stored on the requirement; not yet applied."},"AdminTrustGateDefinition":{"type":"object","additionalProperties":false,"required":["slug","name","provider_kind","unavailable_policy","freshness_ttl_seconds","default_enabled","hidden"],"properties":{"slug":{"type":"string","minLength":1,"readOnly":true,"description":"Stable identifier for the trust requirement.","example":"identity_verified"},"name":{"type":"string","minLength":1,"description":"Human-readable name of the trust requirement.","example":"Identity verified"},"provider_kind":{"$ref":"#/components/schemas/AdminTrustProviderKind"},"unavailable_policy":{"$ref":"#/components/schemas/AdminTrustUnavailablePolicy"},"freshness_ttl_seconds":{"type":["integer","null"],"minimum":1,"description":"The intended lifetime, in seconds, of a provider status before it should be re-confirmed. Stored on the requirement; not yet applied. Null means no intended expiry.","example":2592000},"default_enabled":{"type":"boolean","description":"Whether this trust requirement applies to organizations by default, absent an organization-specific override."},"hidden":{"type":"boolean","description":"Whether to keep this requirement off an organization's verification overview while it is unsatisfied. It still applies, is still enforced, and the organization can still complete it from a link straight to it; it reappears on the overview once satisfied. Use it for a requirement that belongs to one specific flow rather than to the organization's standing verification list. To stop enforcing a requirement altogether, turn off default_enabled instead.\n"},"created_at":{"type":"string","format":"date-time","readOnly":true,"description":"When the trust requirement was added to the catalog."},"updated_at":{"type":"string","format":"date-time","readOnly":true,"description":"When the trust requirement was last changed."}}},"AdminTrustGateDefinitionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Every trust requirement in the catalog.","items":{"$ref":"#/components/schemas/AdminTrustGateDefinition"}}}},"AdminTrustGateDefinitionCreate":{"type":"object","additionalProperties":false,"required":["slug","name","provider_kind","unavailable_policy","default_enabled","hidden"],"properties":{"slug":{"type":"string","minLength":1,"description":"Stable identifier for the trust requirement. Must be unique across the catalog.","example":"identity_verified"},"name":{"type":"string","minLength":1,"description":"Human-readable name of the trust requirement.","example":"Identity verified"},"provider_kind":{"$ref":"#/components/schemas/AdminTrustProviderKind"},"unavailable_policy":{"$ref":"#/components/schemas/AdminTrustUnavailablePolicy"},"freshness_ttl_seconds":{"type":["integer","null"],"minimum":1,"description":"The intended lifetime, in seconds, of a provider status before it should be re-confirmed. Stored on the requirement; not yet applied. Omit or null for no intended expiry.","example":2592000},"default_enabled":{"type":"boolean","description":"Whether this trust requirement applies to organizations by default, absent an organization-specific override."},"hidden":{"type":"boolean","description":"Whether to keep this requirement off an organization's verification overview while it is unsatisfied. It still applies, is still enforced, and the organization can still complete it from a link straight to it; it reappears on the overview once satisfied. Use it for a requirement that belongs to one specific flow rather than to the organization's standing verification list. To stop enforcing a requirement altogether, turn off default_enabled instead.\n"}}},"AdminTrustGateDefinitionUpdate":{"type":"object","additionalProperties":false,"required":["name","provider_kind","unavailable_policy","default_enabled","hidden"],"properties":{"name":{"type":"string","minLength":1,"description":"Human-readable name of the trust requirement.","example":"Identity verified"},"provider_kind":{"$ref":"#/components/schemas/AdminTrustProviderKind"},"unavailable_policy":{"$ref":"#/components/schemas/AdminTrustUnavailablePolicy"},"freshness_ttl_seconds":{"type":["integer","null"],"minimum":1,"description":"The intended lifetime, in seconds, of a provider status before it should be re-confirmed. Stored on the requirement; not yet applied. Omit or null for no intended expiry.","example":2592000},"default_enabled":{"type":"boolean","description":"Whether this trust requirement applies to organizations by default, absent an organization-specific override."},"hidden":{"type":"boolean","description":"Whether to keep this requirement off an organization's verification overview while it is unsatisfied. It still applies, is still enforced, and the organization can still complete it from a link straight to it; it reappears on the overview once satisfied. Use it for a requirement that belongs to one specific flow rather than to the organization's standing verification list. To stop enforcing a requirement altogether, turn off default_enabled instead.\n"}}},"AdminTrustGateDefinitionReorder":{"type":"object","additionalProperties":false,"description":"The complete ordered list of gate slugs. Positions are rewritten as 1..n in one pass, so a list that omits any gate in the catalog is rejected.\n","required":["slugs"],"properties":{"slugs":{"type":"array","minItems":1,"description":"Every gate slug in the catalog, in the order to present them.","items":{"type":"string","minLength":1}}}},"AdminTrustActionPolicy":{"type":"object","additionalProperties":false,"required":["action","gate_slug"],"properties":{"action":{"type":"string","minLength":1,"description":"The action this requirement is attached to. The action must satisfy every gate attached to it before it is permitted.","example":"email.send"},"gate_slug":{"type":"string","minLength":1,"description":"The trust requirement the action depends on, identified by its stable slug.","example":"identity_verified"}}},"AdminTrustActionPolicyList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Every action→requirement mapping in the catalog.","items":{"$ref":"#/components/schemas/AdminTrustActionPolicy"}}}},"AdminTrustActionPolicyCreate":{"type":"object","additionalProperties":false,"required":["action","gate_slug"],"properties":{"action":{"type":"string","minLength":1,"description":"The action to attach the requirement to.","example":"email.send"},"gate_slug":{"type":"string","minLength":1,"description":"The trust requirement to attach, identified by its stable slug. The requirement must already exist in the catalog.","example":"identity_verified"}}},"AdminTrustActionPolicyReorder":{"type":"object","additionalProperties":false,"description":"The complete ordered list of the gate slugs this action requires. Positions are rewritten as 1..n in one pass, so a list that omits any of the action's gates is rejected. Attaching or detaching a gate is a separate operation.\n","required":["gate_slugs"],"properties":{"gate_slugs":{"type":"array","minItems":1,"description":"Every gate slug the action requires, in the order to work through them.","items":{"type":"string","minLength":1}}}},"AdminTrustAuditEvent":{"type":"object","additionalProperties":false,"required":["id","organization_id","event_type","gate_slug","actor","detail"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"Unique identifier of the audit event."},"organization_id":{"allOf":[{"$ref":"#/components/schemas/OrganizationID"}],"readOnly":true,"description":"The organization the change concerns."},"event_type":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["override.review_refused","override.granted","override.denied","override.cleared"],"description":"Type of trust audit event. The vocabulary includes override decisions and\nreview attempts, identity review actions, and retained gate-definition or\naction-policy events. The listed values are examples, not an exhaustive set.\nFor override events, `override.granted` and `override.denied` set the decision;\n`override.cleared` removes it; `override.review_refused` records a stale\nreviewed command without changing the effective decision.\n"},"gate_slug":{"type":"string","minLength":1,"readOnly":true,"description":"The trust requirement the change concerns, identified by its stable slug."},"actor":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier of the staff user who made the change."},"detail":{"type":"object","additionalProperties":true,"readOnly":true,"description":"Additional structured context about the change. Shape varies by event type."},"created_at":{"type":"string","format":"date-time","readOnly":true,"description":"When the change was recorded."}}},"AdminTrustAuditEventList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of audit events, newest first.","items":{"$ref":"#/components/schemas/AdminTrustAuditEvent"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminTrustInquiryState":{"type":"string","enum":["in_flight","awaiting_decision","awaiting_provider","decided"],"description":"Where an inquiry stands, derived from the record rather than stored. The four values are mutually exclusive and cover every record.\n\n`in_flight` means the person has not finished the flow yet. `awaiting_decision` means they finished and nobody has adjudicated it: Persona reports `completed`, which means submitted and not decided, and no staff verdict has been sent. This is the queue's whole population and its default filter. `awaiting_provider` means a staff verdict was submitted and Persona's confirming webhook has not landed yet, which is normally a few seconds. `decided` means Persona adjudicated it and `terminal_status` says how.\n"},"AdminTrustInquiryVerdict":{"type":"string","enum":["approve","decline"],"description":"The verdict staff submitted to the verification provider. A submission, not an outcome: the provider stays the only writer of the requirement's standing, and the record shows the decision only once its confirming webhook lands.\n"},"AdminTrustInquiry":{"type":"object","additionalProperties":false,"required":["cursor","id","persona_inquiry_id","organization_id","organization_name","organization_deleted","gate_slug","user_id","persona_template_id","state","created_at","completed_at","decisioned_at","terminal_status","captured_at","capture_error","review_verdict","review_submitted_at","review_submitted_by"],"properties":{"cursor":{"type":"string","minLength":1,"description":"Opaque pagination boundary for this exact row, encoding (created_at, id). Reusable across regions; pass it unchanged as `starting_after` or `ending_before`.\n"},"id":{"type":"string","minLength":1,"readOnly":true,"description":"Unique identifier of the verification record the inquiry was launched from."},"persona_inquiry_id":{"type":"string","minLength":1,"readOnly":true,"description":"The Persona inquiry under review. Also the identifier the detail, document, and resync operations address it by.","example":"inq_4kL9mNpQrStUvWxYz2345678"},"organization_id":{"allOf":[{"$ref":"#/components/schemas/OrganizationID"}],"readOnly":true,"description":"The organization the verification was completed for. Present even when the organization has since been deleted, because the identity decision outlives the account it was made for."},"organization_name":{"type":["string","null"],"readOnly":true,"description":"The organization's name. Null when the organization has been deleted or purged, in which case only its id survives."},"organization_deleted":{"type":"boolean","readOnly":true,"description":"Whether the organization no longer exists. True for a soft-deleted or purged account. The record is still served in that case: it is the evidence for an identity decision, and a closed account is when that evidence is most likely to be asked for.\n"},"gate_slug":{"type":"string","minLength":1,"readOnly":true,"description":"The trust requirement the verification was launched against, identified by its stable slug.","example":"identity_verified"},"user_id":{"readOnly":true,"description":"The person who consented by completing the flow. Null on records opened before the user id joined the reference-id contract, which cannot be attributed to a Bird user.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"persona_template_id":{"type":"string","minLength":1,"readOnly":true,"description":"The Persona inquiry template the verification ran against.","example":"itmpl_4kL9mNpQrStUvWxYz23"},"state":{"allOf":[{"$ref":"#/components/schemas/AdminTrustInquiryState"}],"readOnly":true,"description":"Where the inquiry stands, derived from the timestamps below rather than stored."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the verification was launched."},"completed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the person finished the flow. This is the consent moment. Null while the verification is still in flight. Finishing the flow is not the provider adjudicating it, which is what `decisioned_at` records."},"decisioned_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the provider adjudicated the inquiry. Null until the approve or decline webhook lands, including for the seconds after a staff verdict has been submitted. Records the first adjudication, so a provider reviewer re-deciding an inquiry does not move it."},"terminal_status":{"type":["string","null"],"readOnly":true,"description":"The terminal status the provider decided on, in its own vocabulary rather than a Bird status. Null while the inquiry is still in flight or awaiting adjudication. The values seen so far are `approved`, `declined`, `expired`, `failed`, `needs_review`, and `marked_for_review`; treat the set as open, since it is the provider's to extend.","example":"approved"},"captured_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the reviewable snapshot was last pulled from the provider. Null when no snapshot has ever landed, which is what `capture_error` explains."},"capture_error":{"type":["string","null"],"readOnly":true,"description":"Why the last capture stored nothing, when it stored nothing. Null on a capture that succeeded. A record can carry both this and an earlier snapshot, because a refused capture never discards the payload already held."},"review_verdict":{"readOnly":true,"description":"The verdict staff submitted to the provider. Null when nobody has decided it here. Submitting is not adjudicating, so this can be set while `terminal_status` is still null.","oneOf":[{"$ref":"#/components/schemas/AdminTrustInquiryVerdict"},{"type":"null"}]},"review_submitted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the staff verdict was submitted to the provider. Null when nobody has decided it here. This is what keeps the queue from re-serving an inquiry in the seconds before the confirming webhook lands."},"review_submitted_by":{"type":["string","null"],"readOnly":true,"description":"Identifier of the staff user who submitted the verdict. Null when nobody has decided it here."}}},"AdminTrustInquiryList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"A page of inquiries, newest first.","items":{"$ref":"#/components/schemas/AdminTrustInquiry"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminTrustInquirySummaryStatus":{"type":"string","description":"Pending awaits generation; processing holds an active generation lease; ready has a saved summary. Failed exhausted generation retries; unavailable could not run because configuration or evidence limits prevent generation. Superseded refers to evidence replaced before generation completed.\n","minLength":1,"enum":["pending","processing","ready","failed","unavailable","superseded"]},"AdminTrustInquirySummaryFinding":{"type":"object","additionalProperties":false,"required":["title","detail","evidence_paths"],"properties":{"title":{"type":"string","minLength":1,"maxLength":160},"detail":{"type":"string","minLength":1,"maxLength":1000},"evidence_paths":{"description":"Exact JSON Pointer paths into the sanitized evidence projection supplied to the model. Object keys and array positions match the captured snapshot; sensitive values are masked and file references are omitted.","type":"array","minItems":1,"items":{"type":"string","minLength":1}}}},"AdminTrustInquirySummaryContent":{"type":"object","additionalProperties":false,"required":["summary","findings","gaps"],"properties":{"summary":{"type":"string","minLength":1,"maxLength":2000},"findings":{"type":"array","maxItems":12,"items":{"$ref":"#/components/schemas/AdminTrustInquirySummaryFinding"}},"gaps":{"type":"array","maxItems":10,"items":{"type":"string","minLength":1,"maxLength":500}}}},"AdminTrustInquirySummary":{"type":"object","readOnly":true,"description":"Saved AI explanation and generation state for one captured evidence fingerprint and prompt version.","additionalProperties":false,"required":["status","snapshot_fingerprint","input_version","prompt_version","model","generated_at","content","error"],"properties":{"status":{"$ref":"#/components/schemas/AdminTrustInquirySummaryStatus"},"snapshot_fingerprint":{"description":"SHA-256 fingerprint of the canonical captured snapshot.","type":"string","minLength":64,"maxLength":64,"pattern":"^[0-9a-f]{64}$"},"input_version":{"description":"Version of the evidence projection supplied to the model.","type":"string","minLength":1},"prompt_version":{"description":"Version of the summary instructions and output contract.","type":"string","minLength":1},"model":{"description":"Model used for the generation attempt. Null before an attempt records a model.","type":["string","null"],"minLength":1},"generated_at":{"description":"Time a valid summary was saved. Null until generation succeeds.","type":["string","null"],"format":"date-time"},"content":{"description":"Validated summary content. Null until generation succeeds.","oneOf":[{"$ref":"#/components/schemas/AdminTrustInquirySummaryContent"},{"type":"null"}]},"error":{"description":"Safe explanation of the generation failure or unavailable state. Null when no error is recorded.","type":["string","null"],"minLength":1}}},"AdminTrustInquiryDocumentRef":{"type":"object","additionalProperties":false,"required":["ref","verification_id","verification_type","page"],"properties":{"ref":{"type":"string","minLength":1,"readOnly":true,"description":"The handle to pass to the document endpoint. Opaque: it names a slot on a verification, and only this API mints one. Do not construct or parse it.\n","example":"ver_4kL9mNpQrStUvWxYz23:photo:0"},"verification_id":{"type":"string","minLength":1,"readOnly":true,"description":"The verification the document was submitted for.","example":"ver_4kL9mNpQrStUvWxYz23"},"verification_type":{"type":"string","minLength":1,"readOnly":true,"description":"The kind of verification the document belongs to, in Persona's own vocabulary. Treat the set as open, since it is the provider's to extend.\n","example":"verification/government-id"},"page":{"type":["string","null"],"readOnly":true,"description":"Which face of the document this is, when the provider labelled it. Null when the snapshot carries no label, which is the usual case after sanitization strips the file block the label sat in.\n","example":"front"}}},"AdminTrustInquiryDetail":{"type":"object","additionalProperties":false,"required":["inquiry","snapshot","redacted_at","source_event_id","review_comment","documents","ai_summary"],"properties":{"inquiry":{"allOf":[{"$ref":"#/components/schemas/AdminTrustInquiry"}],"readOnly":true,"description":"The queue row for this inquiry, so the detail header and the list agree by construction."},"snapshot":{"type":["object","null"],"additionalProperties":true,"readOnly":true,"description":"The provider's own inquiry payload, stored as it arrived except that every file reference has been removed. Shape is the provider's to change, so read it defensively. Null when no capture has ever landed, which `capture_error` on the inquiry explains.\n\nNo identity document and no link to one is kept here. Documents are listed separately and fetched live, one request at a time.\n"},"redacted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the provider redacted the inquiry on its own retention schedule. Non-null means the submitted documents are gone at the provider and every entry in `documents` will fail to fetch.\n\nRecorded against the record rather than read out of `snapshot`. A redacted inquiry reads back from the provider as valid but hollow, so that payload is refused rather than stored, and the snapshot captured before redaction survives. Set with a null `snapshot` it says the evidence is permanently gone, which is a different thing from a capture that has not run.\n"},"source_event_id":{"type":["string","null"],"readOnly":true,"description":"The webhook event that triggered the last capture, so a stale snapshot is traceable to the event that should have refreshed it. Null when the row was opened by something other than a capture."},"review_comment":{"type":["string","null"],"readOnly":true,"description":"The note the reviewer sent to the provider with their verdict. Null when nobody has decided it here."},"ai_summary":{"description":"The saved AI explanation for the current captured snapshot. Null until a snapshot has an assessment row.","oneOf":[{"$ref":"#/components/schemas/AdminTrustInquirySummary"},{"type":"null"}],"readOnly":true},"documents":{"type":"array","readOnly":true,"description":"The identity documents this inquiry carried, as handles rather than links. Read from the stored snapshot, so the list is available even when the provider integration is unreachable.\n\nCovers the two slots the provider populates today: a verification's `photo-urls` array and its `center-photo-url` scalar. Left/right government-ID photos and `document/*` file arrays are not extracted yet, so an empty list means no documents were submitted, nothing has captured the inquiry, or it carried only an uncovered slot. Read `captured_at` and `capture_error` to tell the first two apart.\n\nA redacted inquiry still lists them, because the snapshot captured before the redaction records what was submitted. Every handle on such an inquiry will fail to fetch, so read `redacted_at` first and name what is gone rather than requesting a document that cannot arrive.\n","items":{"$ref":"#/components/schemas/AdminTrustInquiryDocumentRef"}}}},"AdminTrustInquiryDecisionRequest":{"type":"object","additionalProperties":false,"required":["verdict"],"properties":{"verdict":{"allOf":[{"$ref":"#/components/schemas/AdminTrustInquiryVerdict"}],"description":"What to ask the verification provider to do with this inquiry."},"comment":{"type":"string","minLength":1,"maxLength":1000,"pattern":"\\S","description":"The reviewer's note, sent to the provider as the audit comment on the decision and kept on the Bird record alongside it.\n\nRequired when the verdict is `decline` and optional when it is `approve`. A decline is the outcome someone will later have to explain, to the customer or to an auditor, and the reason is only recoverable if it was written down at the moment it was known.\n","example":"Government ID expired in 2024 and the address does not match the registration."}},"oneOf":[{"properties":{"verdict":{"const":"approve"}},"required":["verdict"]},{"properties":{"verdict":{"const":"decline"}},"required":["verdict","comment"]}]},"AccountingBookID":{"type":"string","minLength":1,"pattern":"^abk_[0-9a-hjkmnp-tv-z]{26}$","example":"abk_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAccountingEnvironment":{"type":"string","minLength":0,"enum":["","sandbox","production"],"description":"NetSuite environment used by this book; empty while not configured."},"AdminAccountingBook":{"description":"Book","allOf":[{"type":"object","description":"Book","required":["id","business_entity_id","name","currency_code","destination_account","environment","subsidiary_ref","accounting_book_ref","policy_reference","recognition_owner","invoice_posting_owner","functional_currency_code"],"properties":{"id":{"$ref":"#/components/schemas/AccountingBookID"},"business_entity_id":{"$ref":"#/components/schemas/BusinessEntityID"},"name":{"type":"string","minLength":1,"maxLength":200,"description":"Accounting book name."},"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"destination_account":{"x-sensitive":true,"type":"string","minLength":0,"maxLength":500,"description":"NetSuite account identifier."},"environment":{"$ref":"#/components/schemas/AdminAccountingEnvironment"},"subsidiary_ref":{"x-sensitive":true,"type":"string","minLength":0,"maxLength":500,"description":"NetSuite subsidiary internal reference."},"accounting_book_ref":{"x-sensitive":true,"type":"string","minLength":0,"maxLength":2000,"description":"NetSuite accounting book reference; empty for the primary book."},"policy_reference":{"type":"string","minLength":0,"maxLength":500,"description":"Approved accounting and tax policy reference."},"recognition_owner":{"type":"string","minLength":1,"maxLength":2000,"description":"Recognition owner: netsuite."},"invoice_posting_owner":{"type":"string","minLength":1,"maxLength":2000,"description":"Invoice posting owner: existing_feed."},"functional_currency_code":{"$ref":"#/components/schemas/CurrencyCode","readOnly":true,"description":"Functional currency captured from the legal entity when the book was created."}}},{"$ref":"#/components/schemas/Timestamps"}],"unevaluatedProperties":false},"AdminAccountingBookList":{"description":"A page of accounting book records.","allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingBook"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}],"unevaluatedProperties":false},"AdminAccountingAccountType":{"type":"string","minLength":1,"enum":["asset","liability","equity","income","expense"],"description":"Classification of the ledger account."},"AdminAccountingAccountCreate":{"type":"object","additionalProperties":false,"description":"A local chart account. An empty NetSuite reference allows preparation but blocks approval and export.","required":["code","name","account_type","role","destination_ref"],"properties":{"code":{"type":"string","minLength":1,"maxLength":100,"description":"Local account code."},"name":{"type":"string","minLength":1,"maxLength":200,"description":"Account name."},"account_type":{"$ref":"#/components/schemas/AdminAccountingAccountType"},"role":{"type":"string","minLength":1,"maxLength":100,"description":"Unique accounting role used by the posting policy."},"destination_ref":{"x-sensitive":true,"type":"string","minLength":0,"maxLength":100,"description":"NetSuite internal account reference."}}},"AdminAccountingBookCreate":{"type":"object","additionalProperties":false,"description":"Creates one legal-entity and currency book. NetSuite settings may be empty while sources and local journals are prepared; complete mappings are required for approval and export.","required":["business_entity_id","name","currency_code","destination_account","environment","subsidiary_ref","accounting_book_ref","policy_reference","accounts"],"properties":{"business_entity_id":{"$ref":"#/components/schemas/BusinessEntityID"},"name":{"type":"string","minLength":1,"maxLength":200,"description":"Accounting book name."},"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"destination_account":{"x-sensitive":true,"type":"string","minLength":0,"maxLength":500,"description":"NetSuite account identifier."},"environment":{"$ref":"#/components/schemas/AdminAccountingEnvironment"},"subsidiary_ref":{"x-sensitive":true,"type":"string","minLength":0,"maxLength":500,"description":"NetSuite subsidiary internal reference."},"accounting_book_ref":{"x-sensitive":true,"type":"string","minLength":0,"maxLength":2000,"description":"NetSuite accounting book reference; empty for the primary book."},"policy_reference":{"type":"string","minLength":0,"maxLength":500,"description":"Approved accounting and tax policy reference."},"accounts":{"type":"array","minItems":0,"maxItems":2000,"items":{"$ref":"#/components/schemas/AdminAccountingAccountCreate"}}}},"LedgerAccountID":{"type":"string","minLength":1,"pattern":"^gla_[0-9a-hjkmnp-tv-z]{26}$","example":"gla_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAccountingAccount":{"description":"Account","allOf":[{"type":"object","description":"Account","required":["id","code","name","account_type","role","destination_ref","active"],"properties":{"id":{"$ref":"#/components/schemas/LedgerAccountID"},"code":{"type":"string","minLength":1,"maxLength":100,"description":"Local account code."},"name":{"type":"string","minLength":1,"maxLength":200,"description":"Account name."},"account_type":{"$ref":"#/components/schemas/AdminAccountingAccountType"},"role":{"type":"string","minLength":1,"maxLength":100,"description":"Unique accounting role used by the posting policy."},"destination_ref":{"x-sensitive":true,"type":"string","minLength":0,"maxLength":100,"description":"NetSuite internal account reference."},"active":{"type":"boolean"}}},{"$ref":"#/components/schemas/Timestamps"}],"unevaluatedProperties":false},"AdminAccountingBookDetail":{"type":"object","additionalProperties":false,"description":"Book configuration and its bounded account mapping.","required":["book","accounts"],"properties":{"book":{"$ref":"#/components/schemas/AdminAccountingBook"},"accounts":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingAccount"}}}},"AdminAccountingAccountBalance":{"type":"object","additionalProperties":false,"description":"AccountBalance","required":["account_id","month","debit","credit","posted_debit","posted_credit"],"properties":{"account_id":{"$ref":"#/components/schemas/LedgerAccountID"},"month":{"type":"string","format":"date-time","minLength":1,"description":"First day of the month at UTC midnight."},"debit":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"All prepared journal debits, including posted journals."},"credit":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"All prepared journal credits, including posted journals."},"posted_debit":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Debits with a matched NetSuite result."},"posted_credit":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Credits with a matched NetSuite result."}}},"AdminAccountingWorkbook":{"type":"object","additionalProperties":false,"description":"Account movements by calendar month. Prepared totals and verified NetSuite postings are separate; a missing cell has no journals.","required":["book","accounts","balances"],"properties":{"opening_balances":{"type":"array","description":"Retained journal balances before the requested calendar year, including all earlier years. This arithmetic opening does not certify historical completeness; the opening balance control is separate.","items":{"$ref":"#/components/schemas/AdminAccountingAccountBalance"}},"book":{"$ref":"#/components/schemas/AdminAccountingBook"},"accounts":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingAccount"}},"balances":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingAccountBalance"}}}},"AccountingPeriodID":{"type":"string","minLength":1,"pattern":"^acp_[0-9a-hjkmnp-tv-z]{26}$","example":"acp_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAccountingPeriodState":{"type":"string","minLength":1,"enum":["open","closed"],"description":"Open permits financial work; closed preserves the reconciled snapshot."},"AdminAccountingPeriod":{"description":"Period","allOf":[{"type":"object","description":"Period","required":["id","start","end","state"],"properties":{"id":{"$ref":"#/components/schemas/AccountingPeriodID"},"start":{"type":"string","format":"date-time","minLength":1,"description":"Inclusive UTC start of the calendar month."},"end":{"type":"string","format":"date-time","minLength":1,"description":"Exclusive UTC end of the calendar month."},"state":{"$ref":"#/components/schemas/AdminAccountingPeriodState"},"closed_by":{"$ref":"#/components/schemas/UserID"},"closed_at":{"type":"string","format":"date-time","minLength":1,"description":"When the reconciled month was closed."}}},{"$ref":"#/components/schemas/Timestamps"}],"unevaluatedProperties":false},"AdminAccountingPeriodList":{"description":"A page of accounting period records.","allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingPeriod"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}],"unevaluatedProperties":false},"AdminAccountingPeriodCreate":{"type":"object","additionalProperties":false,"description":"PeriodCreate","required":["month"],"properties":{"month":{"type":"string","format":"date-time","minLength":1,"description":"First day of the calendar month at UTC midnight."}}},"AccountingSourceID":{"type":"string","minLength":1,"pattern":"^acs_[0-9a-hjkmnp-tv-z]{26}$","example":"acs_01krdgeqcxet5s7t44vh8rt9mg"},"AccountingEvidenceID":{"type":"string","minLength":1,"pattern":"^ace_[0-9a-hjkmnp-tv-z]{26}$","example":"ace_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAccountingSourceState":{"type":"string","minLength":1,"enum":["unassigned","assigned","conflict","non_posting"],"description":"Unassigned has no journal; assigned has a journal; conflict records changed financial facts; non_posting records an explicit zero or void source that requires no posting."},"AdminAccountingSourceSummary":{"description":"Accounting source summary. Read the individual record for complete retained detail.","allOf":[{"type":"object","description":"Source","required":["id","period_id","system","type","key","financial_sha256","occurred_at","net","tax","gross","description","reference","state"],"properties":{"id":{"$ref":"#/components/schemas/AccountingSourceID"},"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"system":{"type":"string","minLength":1,"maxLength":100,"description":"Source system."},"type":{"type":"string","minLength":1,"maxLength":100,"description":"Financial event type."},"key":{"type":"string","minLength":1,"maxLength":500,"description":"Permanent identity within the source system and type."},"financial_sha256":{"type":"string","minLength":1,"maxLength":64,"description":"SHA-256 of the financial facts."},"occurred_at":{"type":"string","format":"date-time","minLength":1,"description":"Source event time."},"net":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Net amount, excluding tax."},"tax":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Tax amount."},"gross":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Gross amount, including tax."},"description":{"type":"string","minLength":0,"maxLength":2000,"description":"Source description."},"reference":{"type":"string","minLength":0,"maxLength":2000,"description":"Upstream invoice or transaction reference."},"evidence_id":{"$ref":"#/components/schemas/AccountingEvidenceID"},"state":{"$ref":"#/components/schemas/AdminAccountingSourceState"},"exception":{"type":"string","maxLength":2000,"description":"Unresolved source conflict."}}},{"$ref":"#/components/schemas/Timestamps"}],"unevaluatedProperties":false},"AdminAccountingSourceList":{"description":"A page of accounting source records.","allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingSourceSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}],"unevaluatedProperties":false},"AdminAccountingJournalState":{"type":"string","minLength":1,"enum":["prepared","approved","exported","posted","voided"],"description":"Prepared awaits independent approval; approved has been independently reviewed and follows its source owner's posting route; exported awaits a matched NetSuite result; posted has a verified result; voided remains in the audit trail without an active posting."},"AccountingJournalID":{"type":"string","minLength":1,"pattern":"^aje_[0-9a-hjkmnp-tv-z]{26}$","example":"aje_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAccountingJournalSummary":{"description":"Accounting journal summary. Read the individual record for complete retained detail.","allOf":[{"type":"object","description":"Journal","required":["amount","source_reference","id","period_id","source_id","external_id","posting_kind","posting_date","memo","financial_sha256","state","created_by","exchange_rate","source_evidence_id"],"properties":{"amount":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Sum of journal debits, counted once. Not revenue or a customer balance."},"organization_id":{"$ref":"#/components/schemas/OrganizationID","description":"Original source organization when retained."},"source_reference":{"type":"string","minLength":0,"maxLength":2000,"description":"Original document or transaction reference."},"id":{"$ref":"#/components/schemas/AccountingJournalID"},"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"source_id":{"$ref":"#/components/schemas/AccountingSourceID"},"external_id":{"type":"string","minLength":1,"maxLength":500,"description":"Permanent identity used to match the destination posting."},"posting_kind":{"type":"string","minLength":1,"maxLength":100,"description":"Financial event type."},"posting_date":{"type":"string","format":"date-time","minLength":1,"description":"Posting date."},"memo":{"type":"string","minLength":0,"maxLength":999,"description":"Journal explanation."},"financial_sha256":{"type":"string","minLength":1,"maxLength":64,"description":"SHA-256 of the immutable financial payload."},"state":{"$ref":"#/components/schemas/AdminAccountingJournalState"},"reverses_journal_id":{"$ref":"#/components/schemas/AccountingJournalID"},"created_by":{"$ref":"#/components/schemas/UserID"},"approved_by":{"$ref":"#/components/schemas/UserID"},"approved_at":{"type":"string","format":"date-time","minLength":1,"description":"Independent approval time."},"exchange_rate":{"type":"string","minLength":1,"description":"Historical rate from the book transaction currency to the legal entity functional currency. Exactly 1 when the currencies match."},"source_evidence_id":{"$ref":"#/components/schemas/AccountingEvidenceID","description":"Retained source evidence used by this immutable financial payload."}}},{"$ref":"#/components/schemas/Timestamps"}],"unevaluatedProperties":false},"AdminAccountingJournalList":{"description":"A page of accounting journal records.","allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingJournalSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}],"unevaluatedProperties":false},"AdminAccountingJournalLineCreate":{"type":"object","additionalProperties":false,"description":"JournalLineCreate","required":["account_id","debit","credit","source_line_key","memo"],"properties":{"account_id":{"$ref":"#/components/schemas/LedgerAccountID"},"debit":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Debit amount in the book currency."},"credit":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Credit amount in the book currency."},"tax_code_ref":{"type":"string","maxLength":2000,"description":"NetSuite tax code reference."},"source_line_key":{"type":"string","minLength":1,"maxLength":2000,"description":"Matching source line identity."},"memo":{"type":"string","minLength":0,"maxLength":2000,"description":"Line explanation."}}},"AdminAccountingJournalCreate":{"type":"object","additionalProperties":false,"description":"JournalCreate","required":["source_id","posting_date","memo","lines","exchange_rate"],"properties":{"source_id":{"$ref":"#/components/schemas/AccountingSourceID"},"posting_date":{"type":"string","format":"date-time","minLength":1,"description":"Posting date within the source month."},"memo":{"type":"string","minLength":0,"maxLength":999,"description":"Journal explanation."},"lines":{"type":"array","minItems":2,"maxItems":1000,"items":{"$ref":"#/components/schemas/AdminAccountingJournalLineCreate"}},"exchange_rate":{"type":"string","minLength":1,"description":"Historical rate from the book transaction currency to the legal entity functional currency. Exactly 1 when the currencies match."},"expected_financial_sha256":{"type":"string","minLength":64,"maxLength":64,"description":"Current saved proposal hash. Required when revising a prepared journal; omit when preparing its first version."}}},"AdminAccountingJournalLine":{"type":"object","additionalProperties":false,"description":"JournalLine","required":["number","account_id","debit","credit","source_line_key","memo"],"properties":{"number":{"type":"integer","format":"int32","minimum":1},"account_id":{"$ref":"#/components/schemas/LedgerAccountID"},"debit":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Debit amount in the book currency."},"credit":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Credit amount in the book currency."},"tax_code_ref":{"type":"string","maxLength":2000,"description":"NetSuite tax code reference."},"source_line_key":{"type":"string","minLength":1,"maxLength":2000,"description":"Matching source line identity."},"memo":{"type":"string","minLength":0,"maxLength":2000,"description":"Line explanation."}}},"AdminAccountingJournal":{"description":"Journal","allOf":[{"type":"object","description":"Journal","required":["id","period_id","source_id","external_id","posting_kind","posting_date","memo","financial_sha256","policy_snapshot","state","created_by","lines","exchange_rate","source_evidence_id"],"properties":{"id":{"$ref":"#/components/schemas/AccountingJournalID"},"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"source_id":{"$ref":"#/components/schemas/AccountingSourceID"},"external_id":{"type":"string","minLength":1,"maxLength":500,"description":"Permanent identity used to match the destination posting."},"posting_kind":{"type":"string","minLength":1,"maxLength":100,"description":"Financial event type."},"posting_date":{"type":"string","format":"date-time","minLength":1,"description":"Posting date."},"memo":{"type":"string","minLength":0,"maxLength":999,"description":"Journal explanation."},"financial_sha256":{"type":"string","minLength":1,"maxLength":64,"description":"SHA-256 of the immutable financial payload."},"policy_snapshot":{"type":"string","x-sensitive":true,"minLength":1,"description":"Frozen book and mapping policy used to prepare the journal."},"state":{"$ref":"#/components/schemas/AdminAccountingJournalState"},"reverses_journal_id":{"$ref":"#/components/schemas/AccountingJournalID"},"created_by":{"$ref":"#/components/schemas/UserID"},"approved_by":{"$ref":"#/components/schemas/UserID"},"approved_at":{"type":"string","format":"date-time","minLength":1,"description":"Independent approval time."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingJournalLine"}},"exchange_rate":{"type":"string","minLength":1,"description":"Historical rate from the book transaction currency to the legal entity functional currency. Exactly 1 when the currencies match."},"source_evidence_id":{"$ref":"#/components/schemas/AccountingEvidenceID","description":"Retained source evidence used by this immutable financial payload."}}},{"$ref":"#/components/schemas/Timestamps"}],"unevaluatedProperties":false},"AccountingExportID":{"type":"string","minLength":1,"pattern":"^axb_[0-9a-hjkmnp-tv-z]{26}$","example":"axb_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAccountingExportState":{"type":"string","minLength":1,"enum":["exported","posted"],"description":"Exported awaits destination results; posted has matched results for all claimed journals."},"AdminAccountingExportSummary":{"description":"Accounting export summary. Read the individual record for complete retained detail.","allOf":[{"type":"object","description":"Export","required":["id","period_id","content_sha256","state","created_by"],"properties":{"id":{"$ref":"#/components/schemas/AccountingExportID"},"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"content_sha256":{"type":"string","minLength":1,"maxLength":64,"description":"SHA-256 of the exact export bytes."},"state":{"$ref":"#/components/schemas/AdminAccountingExportState"},"created_by":{"$ref":"#/components/schemas/UserID"}}},{"$ref":"#/components/schemas/Timestamps"}],"unevaluatedProperties":false},"AdminAccountingExportList":{"description":"A page of accounting export records.","allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingExportSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}],"unevaluatedProperties":false},"AdminAccountingExportCreate":{"type":"object","additionalProperties":false,"description":"ExportCreate","required":["period_id","journal_ids"],"properties":{"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"journal_ids":{"type":"array","minItems":1,"maxItems":1000,"uniqueItems":true,"items":{"$ref":"#/components/schemas/AccountingJournalID"}}}},"AdminAccountingExport":{"description":"Export","allOf":[{"type":"object","description":"Export","required":["id","period_id","content_sha256","content","manifest","state","created_by"],"properties":{"id":{"$ref":"#/components/schemas/AccountingExportID"},"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"content_sha256":{"type":"string","minLength":1,"maxLength":64,"description":"SHA-256 of the exact export bytes."},"content":{"x-sensitive":true,"type":"string","minLength":1,"description":"Immutable CSV content. Downloading it again returns the same export."},"manifest":{"x-sensitive":true,"type":"string","minLength":1,"description":"Frozen book, mappings and journal payloads included in this export."},"state":{"$ref":"#/components/schemas/AdminAccountingExportState"},"created_by":{"$ref":"#/components/schemas/UserID"}}},{"$ref":"#/components/schemas/Timestamps"}],"unevaluatedProperties":false},"AccountingExceptionID":{"type":"string","minLength":1,"pattern":"^aex_[0-9a-hjkmnp-tv-z]{26}$","example":"aex_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAccountingExceptionResolutionCreate":{"type":"object","additionalProperties":false,"description":"Records independent evidence that explains an observed accounting exception.","required":["file_name","content","resolution","expected_observed_sha256"],"properties":{"file_name":{"type":"string","minLength":1,"maxLength":200},"content":{"x-sensitive":true,"type":"string","minLength":1,"maxLength":5000000,"description":"Normalized independent resolution CSV."},"resolution":{"type":"string","minLength":1,"maxLength":2000,"description":"Explanation supported by the retained evidence."},"expected_observed_sha256":{"type":"string","minLength":64,"maxLength":64,"description":"Hash of the exception observation being resolved."},"source_evidence_id":{"$ref":"#/components/schemas/AccountingEvidenceID","description":"Same-period evidence proving restored original facts for a changed nonbilling source, or newer processor matching evidence from a corrected report for a provider difference."}}},"AdminAccountingException":{"type":"object","additionalProperties":false,"description":"Exception","required":["id","period_id","evidence_id","kind","description","observed_sha256","created_at"],"properties":{"id":{"$ref":"#/components/schemas/AccountingExceptionID"},"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"source_id":{"$ref":"#/components/schemas/AccountingSourceID"},"journal_id":{"$ref":"#/components/schemas/AccountingJournalID"},"evidence_id":{"$ref":"#/components/schemas/AccountingEvidenceID"},"kind":{"type":"string","minLength":1,"maxLength":100,"description":"Observed discrepancy."},"description":{"type":"string","minLength":1,"maxLength":2000,"description":"Explanation of the discrepancy."},"observed_sha256":{"type":"string","minLength":1,"maxLength":64,"description":"SHA-256 of the discrepant observation."},"resolution":{"type":"string","maxLength":2000,"description":"Recorded resolution."},"resolved_by":{"$ref":"#/components/schemas/UserID"},"resolved_at":{"type":"string","format":"date-time","minLength":1,"description":"Resolution time."},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"Observation time.","readOnly":true},"resolution_evidence_id":{"$ref":"#/components/schemas/AccountingEvidenceID","description":"Immutable independent evidence supporting the resolution."},"resolution_source_evidence_id":{"$ref":"#/components/schemas/AccountingEvidenceID","description":"Retained source statement proving the original accepted financial facts during resolution."}}},"AdminAccountingExceptionList":{"description":"A page of accounting exception records.","allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingException"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}],"unevaluatedProperties":false},"AdminAccountingIssuerCurrencyTax":{"type":"object","additionalProperties":false,"description":"Invoice tax restated in the issuer's fiscal currency using the retained issue-time exchange rate.","required":["tax","exchange_rate","rate_date"],"properties":{"tax":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Original fiscal-currency tax amount retained by Billing."},"exchange_rate":{"type":"string","minLength":1,"maxLength":64,"description":"Exact issue-time rate multiplying invoice-currency tax to obtain issuer-currency tax."},"rate_date":{"type":"string","format":"date","minLength":1,"description":"Effective date of the retained issue-time exchange rate."}}},"InvoiceID":{"type":"string","minLength":1,"pattern":"^inv_[0-9a-hjkmnp-tv-z]{26}$","example":"inv_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAccountingInvoiceApplication":{"type":"object","additionalProperties":false,"description":"Billing-owner evidence of a fully prepaid invoice charge. This is a balance transfer, not additional revenue or tax.","required":["invoice_reference","amount","balance_after"],"properties":{"invoice_reference":{"$ref":"#/components/schemas/InvoiceID"},"amount":{"$ref":"#/components/schemas/Money"},"balance_after":{"$ref":"#/components/schemas/Money"}}},"PaymentReceiptID":{"type":"string","minLength":1,"pattern":"^rcp_[0-9a-hjkmnp-tv-z]{26}$","example":"rcp_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAccountingActivityFacts":{"type":"object","additionalProperties":false,"description":"Original references retained with a regional billing activity observation.","required":["region","kind","organization_id","receipt_references","invoice_references"],"properties":{"invoice_application":{"$ref":"#/components/schemas/AdminAccountingInvoiceApplication"},"region":{"$ref":"#/components/schemas/Region"},"kind":{"type":"string","minLength":1,"maxLength":100,"description":"Billing activity kind."},"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"wallet_transaction_id":{"$ref":"#/components/schemas/WalletTransactionID"},"payment_intent_id":{"$ref":"#/components/schemas/PaymentIntentID"},"provider":{"type":"string","minLength":1,"maxLength":100,"description":"Payment provider when retained by the billing owner."},"provider_reference":{"type":"string","minLength":1,"maxLength":500,"description":"Original provider payment reference when available."},"refund_reference":{"type":"string","minLength":1,"maxLength":500,"description":"Original provider refund reference when available."},"receipt_references":{"type":"array","maxItems":10000,"description":"Retained receipt references used to establish the original legal entity.","items":{"$ref":"#/components/schemas/PaymentReceiptID"}},"invoice_references":{"type":"array","maxItems":10000,"description":"Retained invoice references used to establish the original legal entity.","items":{"$ref":"#/components/schemas/InvoiceID"}},"wallet_amount":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Original wallet movement, which may differ from the external payment amount."},"posting_sequence":{"type":"integer","format":"int64","description":"Original wallet posting sequence when available."}}},"AdminAccountingInvoiceObservationLine":{"type":"object","additionalProperties":false,"description":"Original invoice line evidence. Missing amounts and service dates remain unknown.","required":["key"],"properties":{"key":{"type":"string","minLength":1,"maxLength":10000,"description":"Stable identity within the original invoice document."},"description":{"type":"string","minLength":0,"maxLength":10000,"description":"Original document description."},"product_name":{"type":"string","minLength":0,"maxLength":10000,"description":"Original document product name."},"net_amount":{"type":"string","minLength":0,"maxLength":10000,"description":"Original document net amount."},"tax_amount":{"type":"string","minLength":0,"maxLength":10000,"description":"Original document tax amount."},"gross_amount":{"type":"string","minLength":0,"maxLength":10000,"description":"Original document gross amount."},"tax_rate":{"type":"string","minLength":0,"maxLength":10000,"description":"Original document tax rate."},"period_start":{"type":"string","format":"date-time","description":"Inclusive service period start."},"period_end":{"type":"string","format":"date-time","description":"Exclusive service period end."},"raw_text":{"type":"string","minLength":0,"maxLength":10000,"description":"Original document raw text."}}},"AdminAccountingInvoiceObservation":{"type":"object","additionalProperties":false,"description":"Retained invoice document evidence. These observations require canonical billing verification before approval or export.","required":["version","precision","captured_at","source_url","organization_id","document_type","document_status","billing_month","subtotal_net","tax_amount","total_gross","amount_paid","pdf_sha256","pdf_reference","issuer_name","issuer_vat_id","issuer_registration_number","line_items"],"properties":{"version":{"type":"integer","format":"int64","minimum":1,"maximum":1,"description":"Observation format version."},"precision":{"type":"string","minLength":1,"maxLength":2000,"description":"Document display precision; original billing microunits are not asserted."},"captured_at":{"type":"string","minLength":1,"format":"date-time","description":"Time the source evidence was captured."},"source_url":{"type":"string","minLength":1,"maxLength":2000,"description":"Authenticated source page."},"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"document_type":{"type":"string","minLength":1,"maxLength":2000,"description":"Original debit or credit document type."},"document_status":{"type":"string","minLength":1,"maxLength":2000,"description":"Original document status."},"billing_month":{"type":"string","minLength":1,"format":"date-time","description":"Billing month associated with the document."},"subtotal_net":{"type":"string","minLength":1,"maxLength":2000,"description":"Literal displayed subtotal."},"tax_amount":{"type":"string","minLength":1,"maxLength":2000,"description":"Literal displayed tax."},"total_gross":{"type":"string","minLength":1,"maxLength":2000,"description":"Literal displayed gross total."},"amount_paid":{"type":"string","minLength":1,"maxLength":2000,"description":"Literal invoice paid amount; not a provider settlement."},"pdf_sha256":{"type":"string","minLength":1,"maxLength":2000,"description":"SHA-256 of the original invoice PDF."},"pdf_reference":{"type":"string","minLength":1,"maxLength":2000,"description":"Original invoice document reference."},"issuer_name":{"type":"string","minLength":1,"maxLength":2000,"description":"Document issuer name."},"issuer_vat_id":{"type":"string","minLength":1,"maxLength":2000,"description":"Document issuer VAT identifier."},"issuer_registration_number":{"type":"string","minLength":1,"maxLength":2000,"description":"Document issuer company registration."},"line_items":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingInvoiceObservationLine"}},"tax_in_issuer_currency":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Published tax restatement; no general-ledger exchange rate is inferred."}}},"AdminAccountingSourceLine":{"type":"object","additionalProperties":false,"description":"SourceLine","required":["key","net","tax","gross","tax_rate"],"properties":{"key":{"type":"string","minLength":1,"maxLength":500,"description":"Stable identity of the source line."},"product_id":{"allOf":[{"$ref":"#/components/schemas/ProductID"}],"description":"Billing product identity retained with the original source line, when available."},"net":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Net amount, excluding tax."},"tax":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Tax amount."},"gross":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Gross amount, including tax."},"tax_rate":{"type":"string","minLength":0,"maxLength":2000,"description":"Tax rate recorded by the source."},"period_start":{"type":"string","format":"date-time","minLength":1,"description":"Service period start."},"period_end":{"type":"string","format":"date-time","minLength":1,"description":"Service period end."}}},"AdminAccountingSource":{"description":"Source","allOf":[{"type":"object","description":"Source","required":["id","period_id","system","type","key","financial_sha256","occurred_at","net","tax","gross","description","reference","lines","state"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"id":{"$ref":"#/components/schemas/AccountingSourceID"},"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"system":{"type":"string","minLength":1,"maxLength":100,"description":"Source system."},"type":{"type":"string","minLength":1,"maxLength":100,"description":"Financial event type."},"key":{"type":"string","minLength":1,"maxLength":500,"description":"Permanent identity within the source system and type."},"financial_sha256":{"type":"string","minLength":1,"maxLength":64,"description":"SHA-256 of the financial facts."},"occurred_at":{"type":"string","format":"date-time","minLength":1,"description":"Source event time."},"net":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Net amount, excluding tax."},"tax":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Tax amount."},"issuer_currency_tax":{"allOf":[{"$ref":"#/components/schemas/AdminAccountingIssuerCurrencyTax"}],"description":"Retained Billing tax restatement when available. Absent for native fiscal currency, no tax, or missing issue-time FX; absence does not mean zero tax and must not be filled with a current rate."},"activity":{"allOf":[{"$ref":"#/components/schemas/AdminAccountingActivityFacts"}],"description":"Original billing activity attribution and payment or wallet references when retained."},"observation":{"allOf":[{"$ref":"#/components/schemas/AdminAccountingInvoiceObservation"}],"description":"Invoice document evidence retained for review before canonical billing verification."},"gross":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Gross amount, including tax."},"description":{"type":"string","minLength":0,"maxLength":2000,"description":"Source description."},"reference":{"type":"string","minLength":0,"maxLength":2000,"description":"Upstream invoice or transaction reference."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingSourceLine"}},"evidence_id":{"$ref":"#/components/schemas/AccountingEvidenceID"},"state":{"$ref":"#/components/schemas/AdminAccountingSourceState"},"exception":{"type":"string","maxLength":2000,"description":"Unresolved source conflict."}}},{"$ref":"#/components/schemas/Timestamps"}],"unevaluatedProperties":false},"AdminAccountingEvidence":{"type":"object","additionalProperties":false,"description":"Evidence","required":["id","period_id","source_type","file_name","content_sha256","content","uploaded_by","created_at"],"properties":{"id":{"$ref":"#/components/schemas/AccountingEvidenceID"},"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"source_type":{"type":"string","minLength":1,"maxLength":100,"description":"Type of evidence."},"file_name":{"type":"string","minLength":1,"maxLength":200,"description":"Original file name."},"content_sha256":{"type":"string","minLength":1,"maxLength":64,"description":"SHA-256 of the exact retained content."},"content":{"x-sensitive":true,"type":"string","minLength":1,"maxLength":5000000,"description":"Exact retained source evidence."},"uploaded_by":{"$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"Upload time.","readOnly":true}}},"AdminAccountingReceipt":{"type":"object","additionalProperties":false,"description":"Receipt","required":["journal_id","evidence_id","destination_record_id","financial_sha256","posted_at","created_at"],"properties":{"journal_id":{"$ref":"#/components/schemas/AccountingJournalID"},"evidence_id":{"$ref":"#/components/schemas/AccountingEvidenceID"},"destination_record_id":{"type":"string","minLength":1,"maxLength":500,"description":"Verified NetSuite record identity."},"financial_sha256":{"type":"string","minLength":1,"maxLength":64,"description":"SHA-256 of the matched financial payload."},"posted_at":{"type":"string","format":"date-time","minLength":1,"description":"Destination posting date at UTC midnight, as reported by NetSuite."},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"Verification time.","readOnly":true}}},"AdminAccountingJournalApprovalCreate":{"type":"object","additionalProperties":false,"description":"Approves the exact journal version shown to the reviewer.","required":["expected_financial_sha256"],"properties":{"expected_financial_sha256":{"type":"string","minLength":64,"maxLength":64,"description":"Financial hash of the journal version being approved."}}},"AdminAccountingJournalReversalCreate":{"type":"object","additionalProperties":false,"description":"Prepares the one permanent reversal of the journal identified by the request path.","required":["period_id","posting_date","memo"],"properties":{"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"posting_date":{"type":"string","format":"date-time","minLength":1,"description":"Reversal posting date in an open period."},"memo":{"type":"string","minLength":1,"maxLength":999,"description":"Reason for the reversal."},"external_id":{"type":"string","minLength":1,"maxLength":500,"description":"Actual NetSuite external ID for an invoice, credit-note or recognition reversal owned by the existing feed. Such reversals cannot be exported from this book."}}},"AdminAccountingSourceImportFormat":{"type":"string","minLength":1,"enum":["csv","invoice_observation_json","airwallex_csv"],"default":"csv","description":"Source file format. Invoice document observations support local review only. Airwallex transaction reports retain corroborating payment and settlement evidence without creating duplicate funding journals or asserting bank payout."},"AdminAccountingSourceImportCreate":{"type":"object","additionalProperties":false,"description":"Source evidence import.","required":["period_id","file_name","content"],"properties":{"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"file_name":{"type":"string","minLength":1,"maxLength":200,"description":"Evidence file name."},"content":{"x-sensitive":true,"type":"string","minLength":1,"maxLength":5000000,"description":"UTF-8 source content retained as immutable evidence."},"format":{"$ref":"#/components/schemas/AdminAccountingSourceImportFormat"}}},"AdminAccountingImportResult":{"type":"object","additionalProperties":false,"description":"Import result. Identical observations replay; changed or duplicate financial observations remain visible as exceptions.","required":["evidence_id","added","replayed","conflicts"],"properties":{"evidence_id":{"$ref":"#/components/schemas/AccountingEvidenceID"},"added":{"type":"integer","format":"int64","minimum":0},"replayed":{"type":"integer","format":"int64","minimum":0},"conflicts":{"type":"integer","format":"int64","minimum":0}}},"AdminAccountingImportCreate":{"type":"object","additionalProperties":false,"description":"ImportCreate","required":["period_id","file_name","content"],"properties":{"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"},"file_name":{"type":"string","minLength":1,"maxLength":200,"description":"Evidence file name."},"content":{"x-sensitive":true,"type":"string","minLength":1,"maxLength":5000000,"description":"UTF-8 CSV content retained as immutable evidence."}}},"AdminAccountingHistory":{"type":"object","additionalProperties":false,"description":"Available source coverage observed in one region for an accounting book.","required":["region","first_month","current_month","invoice_count","regional_record_count","global_as_of","regional_as_of"],"properties":{"region":{"$ref":"#/components/schemas/Region","readOnly":true},"first_month":{"type":["string","null"],"format":"date","minLength":1,"readOnly":true,"description":"First UTC month with a matching invoice or candidate regional currency activity, represented by its first day. Null when neither has retained activity."},"current_month":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"First day of the current UTC month, which is still accumulating activity."},"invoice_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Retained invoices for this legal entity and currency. This global count repeats across regions."},"regional_record_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Candidate regional records in the book currency, including records whose historical legal entity cannot be established. This is not a count of imported sources or revenue."},"global_as_of":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time at which global invoice coverage was observed."},"regional_as_of":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time at which regional activity coverage was observed."}}},"AdminAccountingHistoryRunStatus":{"type":"string","minLength":1,"enum":["running","monitoring","failed","canceling","canceled","terminated","timed_out"],"description":"Lifecycle of the logical history request. Running observes months; monitoring waits for the next check. Failed retains its cursor and may be resumed only while current. Canceling means cancellation is saved and no further source activity is admitted; an already-running month may finish. Only repeating cancel is permitted while canceling. Canceled means cancellation is established, including a reserved request with no retained execution or a request canceled after its execution failed. Terminated and timed_out preserve those execution outcomes. Canceled, terminated and timed_out requests cannot resume; start a replacement with a new request identity."},"AdminAccountingHistoryRunMonth":{"type":"object","additionalProperties":false,"required":["month","global_as_of","regional_as_of","added","replayed","conflicts","prepared","pending_sources","diagnostic_count"],"properties":{"month":{"type":"string","minLength":1,"format":"date"},"global_as_of":{"type":"string","minLength":1,"format":"date-time"},"regional_as_of":{"type":"string","minLength":1,"format":"date-time"},"added":{"type":"integer","format":"int64"},"replayed":{"type":"integer","format":"int64"},"conflicts":{"type":"integer","format":"int64"},"prepared":{"type":"integer","format":"int64"},"pending_sources":{"type":"integer","format":"int64"},"diagnostic_count":{"type":"integer","format":"int64"}}},"AdminAccountingHistoryRun":{"type":"object","additionalProperties":false,"required":["request_id","region","actor_user_id","first_month","horizon","cycle_horizon","include_invoices","cursor","status","last_error","started_at","updated_at","next_check_at","months"],"properties":{"request_id":{"type":"string","minLength":1,"format":"uuid"},"region":{"$ref":"#/components/schemas/Region"},"actor_user_id":{"$ref":"#/components/schemas/UserID"},"first_month":{"type":"string","minLength":1,"format":"date"},"horizon":{"type":"string","minLength":1,"format":"date","description":"Initial scheduling boundary, not a consistent snapshot of independent source owners."},"cycle_horizon":{"type":"string","minLength":1,"format":"date"},"include_invoices":{"type":"boolean"},"cursor":{"type":"string","minLength":1,"format":"date"},"status":{"$ref":"#/components/schemas/AdminAccountingHistoryRunStatus"},"last_error":{"type":"string","minLength":0},"started_at":{"type":"string","minLength":1,"format":"date-time"},"updated_at":{"type":"string","minLength":1,"format":"date-time"},"next_check_at":{"type":["string","null"],"minLength":1,"format":"date-time"},"months":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingHistoryRunMonth"}}}},"AdminAccountingSourceSyncMonthCreate":{"type":"object","additionalProperties":false,"description":"One bounded monthly observation of existing accounting sources.","required":["month","include_invoices"],"properties":{"month":{"type":"string","format":"date","minLength":1,"description":"First day of the UTC month to import, no later than the current month."},"include_invoices":{"type":"boolean","description":"Include global invoices and credit notes. Use true for one region and false for the other regions when importing the same book's history."}}},"AdminAccountingHistoryStartAction":{"type":"string","minLength":1,"enum":["start"]},"AdminAccountingHistoryStart":{"type":"object","additionalProperties":false,"description":"Start durable regional history synchronization. Retrying the same request identity preserves its original coverage; different coverage is rejected.","required":["history_action","request_id","month","horizon","include_invoices"],"properties":{"history_action":{"$ref":"#/components/schemas/AdminAccountingHistoryStartAction"},"request_id":{"type":"string","minLength":1,"format":"uuid","description":"Stable caller-chosen identity retained across retries of this regional start."},"month":{"type":"string","minLength":1,"format":"date","description":"First UTC calendar month of the immutable initial source range, from 2000 onward."},"horizon":{"type":"string","minLength":1,"format":"date","description":"Last UTC calendar month of the initial catchup, no earlier than month and no later than the current month. Continuing checks also observe newer months."},"include_invoices":{"type":"boolean","description":"Include global invoices and credit notes. Use true for one region and false for the other regions when synchronizing the same book's history."}}},"AdminAccountingHistoryControlAction":{"type":"string","minLength":1,"enum":["resume","cancel"],"description":"Resume only a current failed request without cancellation intent. Cancel stops future source activity; an already-running month may finish before cancellation completes. Repeating cancel retries the same cancellation request."},"AdminAccountingHistoryControl":{"type":"object","additionalProperties":false,"description":"Control an accepted regional history request without changing its source coverage.","required":["history_action","request_id"],"properties":{"history_action":{"$ref":"#/components/schemas/AdminAccountingHistoryControlAction"},"request_id":{"type":"string","minLength":1,"format":"uuid","description":"Accepted regional history request identity returned by its start or status response."}}},"AdminAccountingSourceSyncCreate":{"oneOf":[{"$ref":"#/components/schemas/AdminAccountingSourceSyncMonthCreate"},{"$ref":"#/components/schemas/AdminAccountingHistoryStart"},{"$ref":"#/components/schemas/AdminAccountingHistoryControl"}],"description":"Observe one month, start durable regional history synchronization, or control an accepted history request. Source coverage is immutable after a durable start. Resume and cancel use only the accepted request identity and action."},"AdminAccountingSourceSyncResult":{"type":"object","additionalProperties":false,"description":"Retained source and journal preparation results for one month and region.","required":["region","period_id","month","next_month","added","replayed","conflicts","prepared","existing_journals","pending_sources","unattributed_records","diagnostic_count","diagnostics","global_as_of","regional_as_of"],"properties":{"region":{"$ref":"#/components/schemas/Region","readOnly":true},"period_id":{"$ref":"#/components/schemas/AccountingPeriodID","readOnly":true},"month":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"First day of the observed UTC month."},"next_month":{"type":["string","null"],"format":"date","minLength":1,"readOnly":true,"description":"First day of the next month to observe, or null after observing the current month."},"added":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"New source observations retained by this request."},"replayed":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Existing identical source observations encountered by this request."},"conflicts":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Conflicting source observations encountered by this request."},"prepared":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"New journals prepared automatically from supported sources."},"existing_journals":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Retained sources in this book and period whose journal is already present. This period count can repeat across regional requests."},"pending_sources":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Retained sources in this book and period that still need journal preparation or exception resolution. This period count can repeat across regional requests."},"unattributed_records":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Candidate regional records that could not be assigned to this legal entity from retained evidence."},"diagnostic_count":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Total preparation and coverage diagnostics for this request, including those beyond the returned sample."},"diagnostics":{"type":"array","maxItems":100,"readOnly":true,"description":"At most 100 preparation and coverage diagnostics. Empty does not establish reconciliation.","items":{"type":"string","minLength":1}},"global_as_of":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time at which the global source-attribution snapshot was observed. Imported invoice evidence retains its own observation time."},"regional_as_of":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time at which regional activity was observed."}}},"AdminAccountingInvoiceSyncCreate":{"type":"object","additionalProperties":false,"description":"InvoiceSyncCreate","required":["period_id"],"properties":{"period_id":{"$ref":"#/components/schemas/AccountingPeriodID"}}},"AdminAccountingAllocationCreate":{"type":"object","additionalProperties":false,"description":"Exact settlement-to-bank allocation. Total allocations cannot exceed either source amount.","required":["period_id","left_source_id","right_source_id","amount"],"properties":{"period_id":{"allOf":[{"$ref":"#/components/schemas/AccountingPeriodID"}],"description":"Period containing the provider settlement."},"left_source_id":{"allOf":[{"$ref":"#/components/schemas/AccountingSourceID"}],"description":"Provider settlement source in the selected period."},"right_source_id":{"allOf":[{"$ref":"#/components/schemas/AccountingSourceID"}],"description":"Bank-entry source in the same book and currency with a matching sign. Its period may differ."},"amount":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Positive amount matched between the two sources, in the book currency."}}},"AdminAccountingProcessorEvidence":{"type":"object","additionalProperties":false,"description":"Exact provider payment ID, currency and amount corroboration of the current source snapshot. Stages overlap; settlement is not bank payout or recognized revenue.","required":["report_count","source_count","confirmed_count","confirmed","settled_count","settled","pending_count","difference_count","unverified_count","evidence_ids"],"properties":{"report_count":{"type":"integer","format":"int64","minimum":0},"source_count":{"type":"integer","format":"int64","minimum":0},"confirmed_count":{"type":"integer","format":"int64","minimum":0},"confirmed":{"$ref":"#/components/schemas/Money"},"settled_count":{"type":"integer","format":"int64","minimum":0},"settled":{"$ref":"#/components/schemas/Money"},"pending_count":{"type":"integer","format":"int64","minimum":0},"difference_count":{"type":"integer","format":"int64","minimum":0},"unverified_count":{"type":"integer","format":"int64","minimum":0},"evidence_ids":{"type":"array","items":{"$ref":"#/components/schemas/AccountingEvidenceID"}}}},"AdminAccountingControlKind":{"type":"string","minLength":1,"enum":["source_coverage","invoice_coverage","wallet_coverage","provider_coverage","settlement_coverage","bank_coverage","tax","recognition","opening_balances","netsuite"],"description":"Reconciliation control supported by immutable evidence and the current period snapshot."},"AdminAccountingControl":{"type":"object","additionalProperties":false,"description":"Control","required":["kind","evidence_id","expected_count","actual_count","expected","actual","financial_sha256","verified_by","verified_at"],"properties":{"kind":{"$ref":"#/components/schemas/AdminAccountingControlKind"},"evidence_id":{"$ref":"#/components/schemas/AccountingEvidenceID"},"expected_count":{"type":"integer","format":"int64","minimum":0},"actual_count":{"type":"integer","format":"int64","minimum":0},"expected":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Expected total from the supplied Finance-reviewed report."},"actual":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Actual total derived from retained records for this control."},"financial_sha256":{"type":"string","minLength":1,"maxLength":64,"description":"Period snapshot verified by this control."},"verified_by":{"$ref":"#/components/schemas/UserID"},"verified_at":{"type":"string","format":"date-time","minLength":1,"description":"Control verification time."}}},"AdminAccountingSourceTotal":{"type":"object","additionalProperties":false,"description":"Retained record inventory for one close control. NetSuite counts accepted posted journals; other controls count source records.","required":["kind","count","amount"],"properties":{"kind":{"$ref":"#/components/schemas/AdminAccountingControlKind"},"count":{"type":"integer","format":"int64","minimum":0},"amount":{"$ref":"#/components/schemas/Money","description":"Control amount in the book currency. Tax uses tax amounts and NetSuite uses accepted posted journal debits. Other controls use signed source gross amounts, with void invoices counted as zero."}}},"AdminAccountingExceptionGroup":{"type":"object","additionalProperties":false,"description":"A complete period group of unresolved exceptions. Source amounts are affected evidence, not measured financial differences; groups may overlap.","required":["kind","count","source_count","unlinked_count","source_amount"],"properties":{"kind":{"type":"string","minLength":1,"maxLength":100},"count":{"type":"integer","format":"int64","minimum":0},"source_count":{"type":"integer","format":"int64","minimum":0,"description":"Distinct linked sources within this group."},"unlinked_count":{"type":"integer","format":"int64","minimum":0,"description":"Exceptions without a retained source in this period; excluded from source_amount."},"source_amount":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Sum of absolute gross amounts of distinct linked sources, counted once within this group."}}},"AdminAccountingCloseStatus":{"type":"object","additionalProperties":false,"description":"CloseStatus","required":["calculated_at","document_source_count","source_totals","exception_groups","period","source_count","journal_count","posted_count","unassigned_count","exception_count","snapshot_hash","controls","unmatched_settlement","unmatched_bank","invoice_snapshot_current","issues"],"properties":{"verified_controls":{"type":"array","items":{"type":"string"},"description":"Controls whose source membership, counts, amounts, snapshot and applicable fresh owner checks pass. Missing means verification is unavailable."},"net_billings":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Retained issued invoice net amounts less credit notes, excluding voids and tax, independent of journal preparation and revenue recognition. Source completeness and original precision remain separate controls."},"processor_evidence":{"$ref":"#/components/schemas/AdminAccountingProcessorEvidence"},"period":{"$ref":"#/components/schemas/AdminAccountingPeriod"},"source_count":{"type":"integer","format":"int64","minimum":0},"journal_count":{"type":"integer","format":"int64","minimum":0},"posted_count":{"type":"integer","format":"int64","minimum":0},"unassigned_count":{"type":"integer","format":"int64","minimum":0},"exception_count":{"type":"integer","format":"int64","minimum":0},"snapshot_hash":{"type":"string","minLength":1,"maxLength":64,"description":"Hash of the current financial snapshot."},"controls":{"type":"array","items":{"$ref":"#/components/schemas/AdminAccountingControl"}},"unmatched_settlement":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Unallocated settlement amount."},"unmatched_bank":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Unallocated bank amount."},"invoice_snapshot_current":{"type":"boolean","description":"Whether retained invoice evidence matches the current billing owner snapshot."},"issues":{"type":"array","items":{"type":"string"},"description":"Current blockers, including source changes observed after close."},"calculated_at":{"type":"string","minLength":1,"format":"date-time","description":"Time this complete period snapshot was evaluated."},"document_source_count":{"type":"integer","format":"int64","minimum":0,"description":"Retained document observations requiring canonical owner verification."},"source_totals":{"type":"array","description":"Complete period control inventory. Amounts across different accounting phases must not be added together as revenue.","items":{"$ref":"#/components/schemas/AdminAccountingSourceTotal"}},"exception_groups":{"type":"array","description":"All unresolved period exceptions grouped by kind, independent of list pagination.","items":{"$ref":"#/components/schemas/AdminAccountingExceptionGroup"}}}},"AdminAccountingControlCreate":{"type":"object","additionalProperties":false,"description":"ControlCreate","required":["kind","file_name","content","expected_count","expected","snapshot_hash"],"properties":{"kind":{"$ref":"#/components/schemas/AdminAccountingControlKind"},"file_name":{"type":"string","minLength":1,"maxLength":200,"description":"Control evidence file name."},"content":{"x-sensitive":true,"type":"string","minLength":1,"maxLength":5000000,"description":"Retained evidence supporting this control."},"expected_count":{"type":"integer","format":"int64","minimum":0},"expected":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Expected source total."},"snapshot_hash":{"type":"string","minLength":1,"maxLength":64,"description":"Current period snapshot hash."}}},"AdminAccountingCloseCreate":{"type":"object","additionalProperties":false,"description":"CloseCreate","required":["expected_snapshot_hash"],"properties":{"expected_snapshot_hash":{"type":"string","minLength":1,"maxLength":64,"description":"Snapshot shown to the approving user."}}},"AdminBusinessEntityList":{"type":"object","additionalProperties":false,"description":"The set of Bird legal entities available to invoice organizations.","required":["business_entities"],"properties":{"business_entities":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminBusinessEntity"}}}},"AdminEmailTemplateContentReveal":{"type":"object","additionalProperties":false,"description":"A template's published content for one language, revealed for staff review. Carries the rendered subject and bodies only, not the visual builder document: that is an authoring artifact, not what the workspace's recipients receive.\n","required":["version_id","language","subject","html_body","text_body"],"properties":{"version_id":{"$ref":"#/components/schemas/EmailTemplateVersionID","readOnly":true,"description":"The published version this content was read from."},"language":{"$ref":"#/components/schemas/LanguageTag","readOnly":true,"description":"The language this content belongs to, in its canonical form."},"subject":{"type":["string","null"],"readOnly":true,"description":"The email subject line for this language, or null if the version has none."},"html_body":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"The HTML body for this language, or null if the version has none."},"text_body":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"The plain-text body for this language, or null if the version has none."}}},"AdminWhatsAppStatsSeriesPeriod":{"type":"object","additionalProperties":false,"description":"The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to. The series is bucketed by day, hour, or month.\n","required":["from","to","grain"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window. A calendar day (YYYY-MM-DD) on the day and month grains, an RFC 3339 instant on the hour grain.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window. A calendar day (YYYY-MM-DD) on the day and month grains, an RFC 3339 instant on the hour grain.","example":"2026-05-25"},"grain":{"$ref":"#/components/schemas/AdminStatsGrain","readOnly":true},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The instant the statistics in this response are current to: events recorded up to roughly this time are reflected, while more recent events may not be yet. Statistics are served from a rolling aggregation that refreshes every few seconds, so a response is near-real-time but not live; use this field to label data freshness rather than assuming the numbers are to-the-second. Null when the freshness boundary is not being reported.\n","example":"2026-05-25T14:03:10Z"}}},"AdminWhatsAppStatsTotals":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Aggregate totals across the whole window. Each lifecycle count is the sum of per-bucket distinct counts, so a message active in two buckets contributes one to each; this matches common provider reporting and is not a window-distinct count. Rates and latency percentiles are computed across the whole window. Cost is not reported: a region spans customers billed in different currencies, and WhatsApp has no currency-agnostic volume unit. Per-message cost is on the message read.\n","required":["delivery","engagement","latency"],"properties":{"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryStats"}]},"engagement":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppLatencyStats"}]}}},"AdminWhatsAppStatsResponse":{"type":"object","additionalProperties":false,"description":"Platform-wide WhatsApp sending, volume, and deliverability statistics over time, within a single region. `series` is one row per bucket in chronological order (counts and per-bucket latency percentiles); `totals` aggregates the whole window, including derived rates and a whole-window latency merge.\n","required":["region","period","totals","series"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/AdminWhatsAppStatsSeriesPeriod","description":"The window and bucket grain the response covers (echoed from the request), plus the freshness boundary the data is current to."},"totals":{"$ref":"#/components/schemas/AdminWhatsAppStatsTotals"},"series":{"type":"array","readOnly":true,"description":"One row per bucket (day, hour, or month, per the grain) in chronological order. Buckets with no activity are included with zero counts.","items":{"$ref":"#/components/schemas/WhatsAppStatsPoint"}}}},"WhatsAppStatsSortMetric":{"type":"string","default":"delivered","description":"Metric to rank breakdown rows by, applied descending. Defaults to `delivered`.\n","enum":["accepted","sent","delivered","failed","rejected","read"]},"WhatsAppStatsBreakdownPeriod":{"type":"object","additionalProperties":false,"description":"The date range the server actually computed against. Echoed back so clients can render the period without tracking it themselves and so cached responses can be keyed by what was queried.\n","required":["from","to"],"properties":{"from":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"Inclusive start date the response covers (YYYY-MM-DD).","example":"2026-05-01"},"to":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"Inclusive end date the response covers (YYYY-MM-DD).","example":"2026-05-25"},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable.\n","example":"2026-05-25T14:03:10Z"}}},"AdminWhatsAppTemplateCategoryStatsPoint":{"type":"object","additionalProperties":false,"description":"Delivery and engagement counts for a single WhatsApp template category over the requested period.\n","required":["template_category","delivery","engagement"],"properties":{"template_category":{"readOnly":true,"description":"The template category this row's counts belong to.","allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateCategory"}]},"delivery":{"readOnly":true,"description":"Lifecycle counts for messages using a template in this category over the period.","allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryCounts"}]},"engagement":{"readOnly":true,"description":"Engagement counts for messages using a template in this category over the period.","allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementCounts"}]}}},"AdminWhatsAppStatsByTemplateCategoryResponse":{"type":"object","additionalProperties":false,"description":"Per-template-category breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `delivered`) descending and capped at the requested `limit` (default 50, max 200).\n","required":["region","period","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/WhatsAppStatsBreakdownPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Template-category breakdown rows, ranked by the `sort` metric (default `delivered`) descending. Empty when no eligible activity occurred in the period.","items":{"$ref":"#/components/schemas/AdminWhatsAppTemplateCategoryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct template categories with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":3}}},"AdminWhatsAppCountryStatsPoint":{"type":"object","additionalProperties":false,"description":"Delivery and engagement counts for a single destination country over the requested period.\n","required":["country","delivery","engagement"],"properties":{"country":{"readOnly":true,"description":"The destination country this row's counts belong to, as an ISO 3166-1 alpha-2 code. `ZZ` collects recipients whose country could not be resolved.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"delivery":{"readOnly":true,"description":"Lifecycle counts for messages sent to this country over the period.","allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryCounts"}]},"engagement":{"readOnly":true,"description":"Engagement counts for messages sent to this country over the period.","allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementCounts"}]}}},"AdminWhatsAppStatsByCountryResponse":{"type":"object","additionalProperties":false,"description":"Per-country breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `delivered`) descending and capped at the requested `limit` (default 50, max 200).\n","required":["region","period","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/WhatsAppStatsBreakdownPeriod","readOnly":true,"description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Country breakdown rows, ranked by the `sort` metric (default `delivered`) descending. Empty when no eligible activity occurred in the period; rows sum to the platform or organization total less group-send volume, and less any pre-cutover phone-addressed sends still inside the window.\n","items":{"$ref":"#/components/schemas/AdminWhatsAppCountryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct countries with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":24}}},"AdminWhatsAppErrorCodeStatsPoint":{"type":"object","additionalProperties":false,"description":"Delivery and engagement counts for messages that failed with a single Bird-normalized error code over the requested period. Every row in this breakdown is a failure outcome, so `delivery.failed` accounts for the row; the other lifecycle and engagement counts are included so the row's numbers reconcile with the platform and per-organization stats endpoints.\n","required":["error_code","delivery","engagement"],"properties":{"error_code":{"readOnly":true,"description":"The failure reason this row's counts belong to.","allOf":[{"$ref":"#/components/schemas/WhatsAppErrorCode"}]},"delivery":{"readOnly":true,"description":"Lifecycle counts for messages that failed with this error code over the period.","allOf":[{"$ref":"#/components/schemas/WhatsAppDeliveryCounts"}]},"engagement":{"readOnly":true,"description":"Engagement counts for messages that failed with this error code over the period.","allOf":[{"$ref":"#/components/schemas/WhatsAppEngagementCounts"}]}}},"AdminWhatsAppStatsByErrorCodeResponse":{"type":"object","additionalProperties":false,"description":"Per-error-code failure breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `failed`) descending and capped at the requested `limit` (default 50, max 200).\n","required":["region","period","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/WhatsAppStatsBreakdownPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Error-code breakdown rows, ranked by the `sort` metric (default `failed`) descending. Empty when no failures occurred in the period.","items":{"$ref":"#/components/schemas/AdminWhatsAppErrorCodeStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct error codes with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":5}}},"AdminOrganizationEmailCompetitiveUsage":{"type":"object","additionalProperties":false,"description":"One organization's Competitive Insights request, error and latency figures for the range.\nThese counts are exact. The rollup behind the region-wide totals carries no tenant, so a per-organization figure is counted from the requests themselves.\nThere is no organization count here, unlike the region-wide summary: this read is about one, so the figure would either restate the request count or invent a number. The workspace count is what varies within a tenant and is the one worth reporting.\n","required":["total_requests","error_requests","latency_p95_ms","active_workspaces"],"properties":{"total_requests":{"type":"integer","minimum":0,"description":"Requests this organization made to Competitive Insights over the range.","example":940},"error_requests":{"type":"integer","minimum":0,"description":"Requests that failed with a server error. On this product they are mostly the email panel being unreachable, which is reported as a retryable failure.","example":8},"latency_p95_ms":{"type":"integer","minimum":0,"description":"The 95th-percentile duration of this organization's requests, in milliseconds. A customer watching many brands pays a longer read than one watching a few, so this varies by tenant. Reported as 0 when the range holds no requests.","example":2890},"active_workspaces":{"type":"integer","minimum":0,"description":"Workspaces of this organization that made at least one request over the range.","example":2}}},"AdminOrganizationEmailCompetitive":{"type":"object","additionalProperties":false,"description":"One organization's Competitive Insights footprint in its region: the watchlists its workspaces keep, and the traffic it sent the product.\nUnlike the region-wide usage read, these counts are exact. The rollup those totals come from carries no tenant, so a per-organization figure is counted from the requests themselves.\n","required":["range_from","range_to","watchlists","watchlists_available","watchlists_truncated","usage","operations"],"properties":{"range_from":{"type":"string","format":"date-time","minLength":1,"description":"Start of the usage reporting range, inclusive."},"range_to":{"type":"string","format":"date-time","minLength":1,"description":"End of the usage reporting range, exclusive."},"watchlists":{"type":"array","description":"The organization's workspaces that watch at least one competitor brand. A workspace that has removed all of its brands is absent, since brands are deleted rather than archived, so an organization can show usage here with no watchlist beside it.","items":{"$ref":"#/components/schemas/AdminEmailCompetitiveWorkspace"}},"watchlists_available":{"type":"boolean","description":"Whether the watchlists could be read. False means the read failed and the list above is empty for that reason, which is not the same as the organization watching nothing. The usage figures are unaffected and are still worth reading, which is why a failure here does not fail the request.","example":true},"watchlists_truncated":{"type":"boolean","description":"Whether more watchlists exist than this response carries. There is no cursor to fetch the rest with, so this is how a caller learns the list is partial. It can only be true for an organization watching brands from a great many workspaces, since the competitor quota bounds the brands rather than the workspaces.","example":false},"usage":{"$ref":"#/components/schemas/AdminOrganizationEmailCompetitiveUsage"},"operations":{"type":"array","description":"The operations this organization reached, busiest first.","items":{"$ref":"#/components/schemas/AdminEmailCompetitiveUsageOperation"}}}},"AdminRealtimeUsageAppRow":{"allOf":[{"$ref":"#/components/schemas/RealtimeUsageAppRow"},{"type":"object","required":["workspace_id"],"properties":{"workspace_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WorkspaceID"}],"description":"The workspace owning the app. Present here and not on the platform read because an organization's usage spans workspaces.\n"}}}]},"AdminRealtimeUsage":{"type":"object","additionalProperties":false,"description":"Realtime usage for an organization over a time window, aggregated across every workspace it owns. The same shape as the workspace-scoped read, with the owning workspace added to each per-app row. Display-only; not a billing source.\n","required":["summary","series","by_app","workspaces"],"properties":{"summary":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/RealtimeUsageSummary"}]},"series":{"type":"array","readOnly":true,"description":"One point per UTC day in the window, ascending.","items":{"$ref":"#/components/schemas/RealtimeUsageDailyPoint"}},"by_app":{"type":"array","readOnly":true,"description":"Per-app breakdown over the window, ordered by messages descending.","items":{"$ref":"#/components/schemas/AdminRealtimeUsageAppRow"}},"workspaces":{"type":"integer","readOnly":true,"description":"How many of the organization's workspaces the aggregate covers. Zero means the organization has no workspaces in this region, so every metric is zero.\n"},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The instant this usage is current to. Realtime usage comes from a rolling rollup fed asynchronously by the stats pipeline, so a response is near-real-time rather than live; label freshness with this instead of assuming the numbers are to-the-second. Null when the window has no data.\n","example":"2026-07-22T14:03:10Z"}}},"AdminRealtimeApp":{"type":"object","additionalProperties":false,"description":"A Bird Realtime app as staff see it: the customer-facing identity plus the internal handles the platform reads deliberately withhold (the numeric app id and the cluster the app is pinned to).\n","required":["realtime_app_id","app_id","name","workspace_id","region","cluster","status","created_at"],"properties":{"realtime_app_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/RealtimeAppID"}]},"app_id":{"type":"integer","format":"int64","readOnly":true,"description":"The numeric Realtime app id behind the typed one, for edge-side lookups.","example":432557},"name":{"type":"string","minLength":1,"readOnly":true,"example":"chat-production"},"workspace_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WorkspaceID"}]},"region":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Region"}]},"cluster":{"type":"string","minLength":1,"readOnly":true,"description":"The Realtime edge cluster serving the app. Internal placement detail that never appears on a customer-facing read; staff need it to correlate with edge dashboards and deploys.\n","example":"mt1"},"status":{"type":"string","minLength":1,"readOnly":true,"description":"Lifecycle status of the app as recorded by the platform.","example":"active"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminRealtimeAppList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminRealtimeApp"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSMSStatsSeriesPeriod":{"type":"object","additionalProperties":false,"description":"The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to. The series is bucketed by day, hour, or month.\n","required":["from","to","grain"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window. A calendar day (YYYY-MM-DD) on the day and month grains, an RFC 3339 instant rounded to the hour on the hour grain.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window. A calendar day (YYYY-MM-DD) on the day and month grains, an RFC 3339 instant rounded to the hour on the hour grain.","example":"2026-05-31"},"grain":{"$ref":"#/components/schemas/AdminStatsGrain","readOnly":true},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The instant the statistics in this response are current to: events recorded up to roughly this time are reflected, while more recent events may not be yet. Statistics are served from a rolling aggregation that refreshes every few seconds, so a response is near-real-time but not live; use this field to label data freshness rather than assuming the numbers are to-the-second. Null when the freshness boundary is not being reported.\n","example":"2026-05-25T14:03:10Z"}}},"AdminSMSStatsTotals":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Aggregate totals across the whole window. Each lifecycle count is the sum of per-bucket distinct counts, so a message active in two buckets contributes one to each; this matches common provider reporting and is not a window-distinct count. Rates and latency percentiles, by contrast, are computed across the whole window. Money and paid volume are billing's surface, not this one.\n","required":["delivery","latency"],"properties":{"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSDeliveryStats"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/SMSLatencyStats"}]}}},"AdminSMSStatsResponse":{"type":"object","additionalProperties":false,"description":"Platform-wide SMS sending, volume, and deliverability statistics over time, within a single region. `series` is one row per bucket in chronological order (counts only); `totals` aggregates the whole window, including derived rates and latency percentiles.\n","required":["region","period","totals","series"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/AdminSMSStatsSeriesPeriod","description":"The window and bucket grain the response covers (echoed from the request), plus the freshness boundary the data is current to."},"totals":{"$ref":"#/components/schemas/AdminSMSStatsTotals"},"series":{"type":"array","readOnly":true,"description":"One row per bucket (day, hour, or month, per the grain) in chronological order. Buckets with no activity are included with zero counts.","items":{"$ref":"#/components/schemas/SMSStatsPoint"}}}},"AdminSMSCountryStatsPoint":{"type":"object","additionalProperties":false,"description":"SMS lifecycle counts for a single destination country over the requested period.\n","required":["country_code","delivery"],"properties":{"country_code":{"type":"string","minLength":1,"readOnly":true,"description":"The destination country this row aggregates, as an ISO 3166-1 alpha-2 code.","example":"US"},"delivery":{"readOnly":true,"description":"Lifecycle counts for messages sent to this destination country over the period.","allOf":[{"$ref":"#/components/schemas/SMSDeliveryCounts"}]}}},"AdminSMSStatsByCountryResponse":{"type":"object","additionalProperties":false,"description":"Per-country SMS breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200).\n","required":["region","period","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Country breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no messages were sent in the period.","items":{"$ref":"#/components/schemas/AdminSMSCountryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct destination countries with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":42}}},"AdminSMSCarrierStatsPoint":{"type":"object","additionalProperties":false,"description":"SMS lifecycle counts for a single delivery carrier over the requested period.\n","required":["carrier","delivery"],"properties":{"carrier":{"type":"string","minLength":1,"readOnly":true,"description":"The delivery carrier this row aggregates, as resolved for the destination handset.","example":"Verizon"},"delivery":{"readOnly":true,"description":"Lifecycle counts for messages delivered through this carrier over the period.","allOf":[{"$ref":"#/components/schemas/SMSDeliveryCounts"}]}}},"AdminSMSStatsByCarrierResponse":{"type":"object","additionalProperties":false,"description":"Per-carrier SMS breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200).\n","required":["region","period","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Carrier breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no messages were sent in the period.","items":{"$ref":"#/components/schemas/AdminSMSCarrierStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct delivery carriers with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":18}}},"AdminSMSErrorCodeStatsPoint":{"type":"object","additionalProperties":false,"description":"SMS lifecycle counts for messages that failed with a single Bird-normalized error code over the requested period. Every row in this breakdown is a failure outcome, so `delivery.failed` accounts for the row; the other lifecycle counts are included so the row's numbers reconcile with the platform and per-organization stats endpoints.\n","required":["error_code","delivery"],"properties":{"error_code":{"readOnly":true,"description":"The failure reason this row's counts belong to.","allOf":[{"$ref":"#/components/schemas/SMSErrorCode"}]},"delivery":{"readOnly":true,"description":"Lifecycle counts for messages that failed with this error code over the period.","allOf":[{"$ref":"#/components/schemas/SMSDeliveryCounts"}]}}},"AdminSMSStatsByErrorCodeResponse":{"type":"object","additionalProperties":false,"description":"Per-error-code SMS failure breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `failed`) descending and capped at the requested `limit` (default 50, max 200).\n","required":["region","period","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"$ref":"#/components/schemas/SMSStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Error-code breakdown rows, ranked by the `sort` metric (default `failed`) descending. Empty when no failures occurred in the period.","items":{"$ref":"#/components/schemas/AdminSMSErrorCodeStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct error codes with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":6}}},"AdminApiKeyList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSSOConnectionStatus":{"type":"string","minLength":1,"enum":["draft","active","suspended","deleted"],"description":"Lifecycle state of a connection, as staff see it. Carries one state the\ncustomer-facing vocabulary does not: a deleted connection is retained rather\nthan removed, and a member refused by one is a case support is called about.\n\n- `draft`: configured but not yet serving logins.\n- `active`: serves logins.\n- `suspended`: retained but refuses logins.\n- `deleted`: retired by the owner. Existing sessions live to expiry, and the\n  identity links it made are retained.\n"},"AdminSSOJitMode":{"type":"string","minLength":1,"enum":["automatic","provisioned_only","disabled"],"description":"Whether a first sign-in through this connection may provision the member.\n\n- `automatic`: it does. Every connection is created this way, and no endpoint\n  changes it, so this is the only value in use today.\n- `provisioned_only`: it does not. Intended to admit a member the directory\n  pushed, but directory sync is unbuilt, so nothing can satisfy the exception and\n  a first sign-in is refused outright.\n- `disabled`: it does not. Refused whether or not the member already has a Bird\n  account, because the identity link a returning sign-in matches on is written\n  only by the automatic path.\n"},"AdminSSOConnectionSummary":{"type":"object","additionalProperties":false,"description":"One of the organization's identity-provider connections, as staff need to read it when a member cannot sign in. Carries no identity-provider secrets or certificates: the questions this answers are whether a connection is active, what it makes new members, and how many accounts are keyed on the subjects it sends.\n","required":["id","protocol","status","display_name","jit_mode","linked_identity_count","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid","minLength":1,"description":"Connection ID."},"protocol":{"$ref":"#/components/schemas/SSOProtocol"},"status":{"$ref":"#/components/schemas/AdminSSOConnectionStatus"},"display_name":{"type":"string","minLength":1,"description":"Name shown on the organization's sign-in button."},"default_jit_role":{"type":["string","null"],"description":"Role granted to a member provisioned on first sign-in through this connection."},"default_jit_workspace_id":{"oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}],"description":"Workspace the default role is granted in. Null means the role is granted at the organization instead.\n"},"jit_mode":{"$ref":"#/components/schemas/AdminSSOJitMode"},"linked_identity_count":{"type":"integer","description":"Accounts currently keyed on a subject this connection sent. Non-zero is what makes the connection's identity format and provider details immutable.\n"},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"When the connection was created."},"updated_at":{"type":"string","format":"date-time","minLength":1,"description":"When the connection was last changed."}}},"AdminSSOVerifiedDomain":{"type":"object","additionalProperties":false,"description":"A domain the organization has claimed for single sign-on. Only a verified claim is exclusive: while this organization holds one, no other can verify the same domain. Unverified claims are not, so two organizations can each have one pending.\n","required":["id","domain","status","created_at"],"properties":{"id":{"type":"string","format":"uuid","minLength":1,"description":"Domain record ID."},"domain":{"type":"string","minLength":1,"description":"The claimed email domain, normalized."},"status":{"$ref":"#/components/schemas/SSODomainStatus"},"verified_at":{"type":["string","null"],"format":"date-time","description":"When the claim was last promoted to verified. Later DNS rechecks do not move it, and a suspended claim keeps the timestamp of the promotion it lost, so a non-null value here does not mean the domain is verified now: read `status` for that. Cleared only when the verification token is regenerated, which returns the claim to pending. Null while a claim has never been verified.\n"},"created_at":{"type":"string","format":"date-time","minLength":1,"description":"When the domain was claimed."}}},"AdminOrganizationSSO":{"type":"object","additionalProperties":false,"description":"An organization's single sign-on configuration, read for support rather than for the owner: whether sign-in through an identity provider is required, which connections exist and in what state, and which email domains are verified. Sign-in attempts themselves are audit events, so they are read through the organization's audit log rather than here.\n","required":["sso_required","connections","verified_domains"],"properties":{"sso_required":{"type":"boolean","description":"Whether members other than the owner must have signed in through one of the organization's active connections. The owner keeps a password, so this can never lock the organization out entirely.\n"},"connections":{"type":"array","description":"Every connection the organization holds, including drafts, suspended ones, and the retired ones the owner's own list drops. A member refused by a connection that no longer serves logins is the case support is called about.\n","items":{"$ref":"#/components/schemas/AdminSSOConnectionSummary"}},"verified_domains":{"type":"array","description":"Domains the organization has claimed, verified or not. A connection cannot be activated until at least one is verified, and a member's asserted email must sit on a verified one to sign in.\n","items":{"$ref":"#/components/schemas/AdminSSOVerifiedDomain"}}}},"AdminQuotaDefinition":{"type":"object","additionalProperties":false,"description":"Static definition of a quota — its key, display name, unit, and default value.","required":["key","name","description","unit","default","adjustable"],"properties":{"key":{"type":"string","readOnly":true,"minLength":1,"example":"email_domains_max","description":"Stable identifier used when configuring plan quotas or org overrides."},"name":{"type":"string","readOnly":true,"minLength":1,"description":"Human-readable name for display in admin tooling."},"description":{"type":"string","readOnly":true,"minLength":1,"description":"What the quota controls."},"unit":{"type":"string","readOnly":true,"minLength":1,"description":"What the limit counts (e.g. domains, endpoints)."},"default":{"type":"integer","format":"int64","readOnly":true,"description":"System default when no plan quota or override is active."},"adjustable":{"type":"boolean","readOnly":true,"description":"Whether this quota can be configured on a plan or overridden per org."}}},"AdminQuotaDefinitionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminQuotaDefinition"}}}},"QuotaOverrideView":{"type":"object","additionalProperties":false,"description":"The active or expired override applied to a quota. Present on admin responses only.","required":["value","reason","updated_at"],"properties":{"value":{"type":"integer","format":"int64","readOnly":true,"description":"The overridden limit."},"reason":{"type":"string","minLength":1,"readOnly":true,"description":"Why the override was set."},"expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the override expires and the default is restored. `null` means permanent."},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminQuota":{"type":"object","additionalProperties":false,"description":"A quota as seen by an admin — default, effective limit, usage, override, and status.","required":["key","name","description","default","limit","unit","status","adjustable","override"],"properties":{"key":{"type":"string","readOnly":true,"minLength":1,"example":"email_domains_max"},"name":{"type":"string","readOnly":true,"minLength":1},"description":{"type":"string","minLength":1,"readOnly":true},"product_slug":{"type":["string","null"],"readOnly":true,"minLength":1},"default":{"type":"integer","format":"int64","readOnly":true,"description":"The system default limit when no override is active."},"limit":{"type":"integer","format":"int64","readOnly":true,"description":"The effective limit (override value when active, else default)."},"unit":{"type":"string","minLength":1,"readOnly":true,"description":"What the limit counts (e.g. count, domains, emails).","example":"count"},"usage":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Current usage, or null when usage is not tracked for this quota."},"remaining":{"type":["integer","null"],"format":"int64","readOnly":true},"percent_used":{"type":["number","null"],"format":"double","readOnly":true},"status":{"type":"string","minLength":1,"enum":["none","active","expired"],"readOnly":true},"adjustable":{"type":"boolean","readOnly":true},"override":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/QuotaOverrideView"},{"type":"null"}]},"plan_value":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The active plan quota for this key, or null when the organization has no plan or the plan has no quota configured for this key."}}},"AdminQuotaList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminQuota"}}}},"QuotaOverrideUpdate":{"type":"object","additionalProperties":false,"description":"Sets or replaces the override for a quota.","required":["value","reason"],"properties":{"value":{"type":"integer","format":"int64","minimum":0,"maximum":1000000000000000,"description":"The new limit for this quota, in the unit reported by the quota's `unit` field. Quotas in `bytes` accept values up to this schema maximum, quotas in `microunits` up to 1000000000000, and every other unit up to 1000000000."},"reason":{"type":"string","minLength":1,"maxLength":1000,"description":"Why the override is being set."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the override should expire back to the default. `null` means permanent."},"force":{"type":"boolean","default":false,"description":"Set `true` to allow a value below the organization's current usage."}}},"AdminRateLimitPolicyDefinition":{"type":"object","readOnly":true,"additionalProperties":false,"description":"A customer rate-limit policy available for plan and organization configuration.","required":["key","name","description","product","unit","default_rate","window_seconds"],"properties":{"key":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"description":{"type":"string","minLength":1},"product":{"type":"string","minLength":1},"unit":{"type":"string","minLength":1},"default_rate":{"type":"integer","minimum":1},"window_seconds":{"type":"integer","minimum":1}}},"AdminRateLimitPolicyDefinitionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminRateLimitPolicyDefinition"}}}},"AdminOverrideStatus":{"type":"string","minLength":1,"enum":["none","active","expired"],"description":"Whether an organization override is absent, active, or expired."},"RateLimitOverrideView":{"type":"object","additionalProperties":false,"description":"An active or expired organization override for a rate-limit policy. Present on admin responses only.","required":["value","reason","updated_at"],"properties":{"value":{"type":"integer","readOnly":true,"description":"The overridden rate in the policy's unit per window."},"reason":{"type":"string","minLength":1,"readOnly":true,"description":"Why the override was set."},"expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the override expires and the plan or catalog rate takes effect. `null` means permanent."},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminRateLimitPolicy":{"type":"object","readOnly":true,"additionalProperties":false,"description":"A customer rate-limit policy resolved for an organization.","required":["key","name","description","product","unit","default_rate","limit","window_seconds","status","override"],"properties":{"key":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"description":{"type":"string","minLength":1},"product":{"type":"string","minLength":1},"unit":{"type":"string","minLength":1},"default_rate":{"type":"integer","minimum":1},"limit":{"type":"integer","minimum":1},"plan_value":{"type":["integer","null"],"minimum":1},"window_seconds":{"type":"integer","minimum":1},"status":{"$ref":"#/components/schemas/AdminOverrideStatus"},"override":{"oneOf":[{"$ref":"#/components/schemas/RateLimitOverrideView"},{"type":"null"}]}}},"AdminRateLimitPolicyList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminRateLimitPolicy"}}}},"RateLimitOverrideUpdate":{"type":"object","additionalProperties":false,"description":"Sets or replaces an organization's rate override for a rate-limit policy.","required":["value","reason"],"properties":{"value":{"type":"integer","minimum":1,"maximum":10000000,"description":"The new rate in the policy's unit per window. It takes precedence over the plan or catalog rate while the override is active."},"reason":{"type":"string","minLength":1,"maxLength":1000,"description":"Why the override is being set."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the override expires and the plan or catalog rate takes effect. `null` means permanent."}}},"AdminVerificationCustomerConfig":{"description":"Admin view of a customer's verification configuration, including the workspace it belongs to.","allOf":[{"$ref":"#/components/schemas/VerificationConfiguration"},{"type":"object","required":["workspace_id"],"properties":{"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace within the organization that owns this configuration."}}}]},"AdminVerificationCustomerConfigList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of verification configuration objects.","items":{"$ref":"#/components/schemas/AdminVerificationCustomerConfig"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminVerificationCustomerConfigCreateRequest":{"type":"object","additionalProperties":false,"required":["workspace_id","name"],"description":"Fields for a new verification configuration created on a customer's behalf by an admin.","properties":{"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"name":{"type":"string","minLength":1,"description":"Human-readable label for the configuration, for example \"signup\" or \"login\"."},"settings":{"$ref":"#/components/schemas/VerificationConfigurationSettings"},"senders":{"$ref":"#/components/schemas/VerificationSenders"}}},"AdminIPPoolList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/IPPoolSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSharedIPPoolSelection":{"type":"string","minLength":1,"enum":["automatic","bird_trusted","bird_std_1","bird_probation","bird_restricted"],"description":"The shared SparkPost pool selection.\n\n- `automatic`: Follow the organization's sender-legitimacy tier.\n- `bird_trusted`: Force the trusted shared route.\n- `bird_std_1`: Force the standard shared route.\n- `bird_probation`: Force the probation shared route.\n- `bird_restricted`: Force the restricted shared route.\n\n`automatic` chooses one of the four named routes from sender legitimacy;\na forced value chooses it directly. SparkPost receives the chosen route\nwith the account region's `us_` or `eu_` prefix.\n"},"AdminSharedIPPoolRouting":{"type":"object","additionalProperties":false,"required":["selection"],"properties":{"selection":{"$ref":"#/components/schemas/AdminSharedIPPoolSelection"}}},"AdminSharedIPPoolRoutingUpdate":{"type":"object","additionalProperties":false,"required":["selection","reason"],"properties":{"selection":{"$ref":"#/components/schemas/AdminSharedIPPoolSelection"},"reason":{"type":"string","minLength":1,"maxLength":1000,"pattern":"\\S","description":"Why the shared pool selection is being changed. Recorded in the admin audit log."}}},"AdminDedicatedIPList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DedicatedIP"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminDedicatedIPCreate":{"type":"object","additionalProperties":false,"required":["ip_pool_id"],"properties":{"quantity":{"type":"integer","minimum":1,"default":1,"description":"Number of dedicated IPs to provision."},"ip_pool_id":{"$ref":"#/components/schemas/IPPoolID","description":"The IP pool to allocate the IPs into."}}},"SupportCase":{"type":"object","additionalProperties":false,"required":["id","title","status","created_at","updated_at"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"Support case ID.","example":"th_01abc"},"title":{"type":"string","minLength":1,"readOnly":true,"description":"Case subject or title.","example":"Sending domain verification stuck"},"status":{"type":"string","minLength":1,"enum":["OPEN","SNOOZED","DONE"],"readOnly":true,"description":"Case status.\n\n- `OPEN`: active.\n- `SNOOZED`: paused.\n- `DONE`: resolved.\n","example":"OPEN"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the case was created.","example":"2026-06-01T10:00:00Z"},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the case was last updated.","example":"2026-06-02T15:30:00Z"},"customer_email":{"type":["string","null"],"format":"email","readOnly":true,"description":"Email address of the customer who opened the case.","example":"alice@acme.com"},"customer_name":{"type":["string","null"],"readOnly":true,"description":"Display name of the customer.","example":"Alice Anderson"},"assignee_name":{"type":["string","null"],"readOnly":true,"description":"Display name of the assigned agent or bot. Null when unassigned.","example":"Bob (Support)"}}},"SupportCaseList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/SupportCase"},"description":"Support cases for the organization, ordered newest first."}}},"SupportChatMetrics":{"type":"object","additionalProperties":false,"required":["open_count","period_days"],"properties":{"open_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of currently open support threads.","example":12},"frt_median_sec":{"type":["number","null"],"readOnly":true,"description":"Median first-response time in seconds over the period. Null when no data.","example":180},"resolution_median_sec":{"type":["number","null"],"readOnly":true,"description":"Median resolution time in seconds over the period. Null when no data.","example":3600},"csat_percent":{"type":["number","null"],"minimum":0,"maximum":100,"readOnly":true,"description":"CSAT score as a percentage (0–100) over the period. Null when no data.","example":92.5},"sla_frt_compliance_percent":{"type":["number","null"],"minimum":0,"maximum":100,"readOnly":true,"description":"Percentage of threads where the first response met the configured SLA target over the period. Null when no SLA is configured or no data.\n","example":87.5},"customer_waiting_median_sec":{"type":["number","null"],"readOnly":true,"description":"Median time in seconds that customers spent actively waiting for a reply over the period, excluding time the thread was snoozed. Null when no data.\n","example":240},"period_days":{"type":"integer","minimum":1,"readOnly":true,"description":"Number of days the metrics cover.","example":30}}},"AdminUser":{"type":"object","required":["user_id","email","roles","created_at","updated_at"],"additionalProperties":false,"properties":{"user_id":{"readOnly":true,"$ref":"#/components/schemas/UserID"},"email":{"type":"string","minLength":1,"readOnly":true,"format":"email"},"roles":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/PlatformRoleEnum.gen"}},"deactivated_at":{"type":["string","null"],"format":"date-time","readOnly":true},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminUserList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminUser"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminUserCreate":{"type":"object","required":["email","roles"],"additionalProperties":false,"properties":{"email":{"type":"string","format":"email","minLength":1},"roles":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/PlatformRoleEnum.gen"}}}},"AdminUserUpdate":{"type":"object","required":["roles"],"additionalProperties":false,"properties":{"roles":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/PlatformRoleEnum.gen"}}}},"AdminReviewSource":{"type":"string","minLength":1,"enum":["sift","admin"],"description":"What flagged a user for manual trust and safety review. `sift`: a provider decision did. `admin`: a staff member did. Closed rather than extensible because the set is fixed until somebody adds a source deliberately, and a new one arrives with its own release note. Spelled `sift` rather than `automated` (as `AdminBanEvidenceActorKind` spells the same actor) because this value is stored verbatim and the wire must not rename it."},"AdminPlatformUserOrg":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One organization a platform user belongs to, with their role in it, for the users list.","required":["organization_id","organization_name"],"properties":{"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"organization_name":{"type":"string","minLength":1,"readOnly":true,"description":"Name of the organization."},"role":{"type":["string","null"],"readOnly":true,"description":"The user's organization-level role, or null when they hold only workspace-level access."}}},"AdminPlatformUser":{"type":"object","additionalProperties":false,"required":["user_id","email","first_name","last_name","full_name","language","has_password","created_at","updated_at"],"properties":{"user_id":{"readOnly":true,"$ref":"#/components/schemas/UserID"},"email":{"type":"string","minLength":1,"format":"email","readOnly":true},"first_name":{"type":"string","minLength":1,"readOnly":true},"last_name":{"type":"string","minLength":1,"readOnly":true},"full_name":{"type":"string","minLength":1,"readOnly":true},"language":{"type":"string","minLength":1,"readOnly":true},"timezone":{"oneOf":[{"$ref":"#/components/schemas/Timezone"},{"type":"null"}],"readOnly":true},"email_verified_at":{"type":["string","null"],"format":"date-time","readOnly":true},"mfa_enabled_at":{"type":["string","null"],"format":"date-time","readOnly":true},"has_password":{"type":"boolean","readOnly":true,"description":"True when the account has a password set; false for passwordless/social-only accounts."},"password_changed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the account password was last set. Null if no password change has been recorded."},"mfa_factors":{"type":"array","readOnly":true,"description":"The user's enrolled multi-factor authentication methods.","items":{"$ref":"#/components/schemas/MFAFactor"}},"recovery_codes_remaining":{"type":["integer","null"],"readOnly":true,"description":"Unused MFA recovery codes remaining. Null when the user has no MFA factor enrolled."},"external_identities":{"type":"array","readOnly":true,"description":"Social login providers (Google, GitHub) connected to the account.","items":{"$ref":"#/components/schemas/ExternalIdentity"}},"last_login_at":{"type":["string","null"],"format":"date-time","readOnly":true},"banned_at":{"type":["string","null"],"format":"date-time","readOnly":true},"banned_reason":{"type":["string","null"],"readOnly":true,"description":"Why the user is banned: `admin` (banned by an admin), `sift` (banned autonomously by the Sift trust & safety system), or `org_ban` (banned because their organization was banned). Null when the user is not banned."},"last_ban_category":{"oneOf":[{"$ref":"#/components/schemas/AdminBanCategory"},{"type":"null"}],"readOnly":true,"description":"The category of the user's most recent suspension. Retained after reinstatement, so a non-null value does not mean the user is currently suspended."},"last_ban_note":{"type":["string","null"],"readOnly":true,"description":"Staff note recorded with the user's most recent suspension. Retained after reinstatement, so a non-null value does not mean the user is currently suspended."},"last_banned_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the user's most recent suspension was applied. Unlike banned_at, this is retained after reinstatement (not cleared), so a non-null value does not mean the user is currently suspended."},"under_review_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the user was flagged for manual trust and safety review. A review is softer than a suspension: the account keeps working, and a human is expected to look at it. Null when no review is open."},"review_source":{"oneOf":[{"$ref":"#/components/schemas/AdminReviewSource"},{"type":"null"}],"readOnly":true,"description":"What flagged the user for review. Null when no review is open."},"deleted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When set, the user has been deleted and can no longer be reinstated."},"account_abuse_score":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Sift account_abuse risk score (0–1, higher is riskier). Null when Sift has no score for the user or the score could not be fetched."},"payment_abuse_score":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Sift payment_abuse risk score (0–1, higher is riskier). Null when Sift has no score for the user or the score could not be fetched."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"device_otp_exempt_at":{"type":"string","format":"date-time","readOnly":true,"description":"When staff exempted this account from the email code that password sign-in normally sends to an unrecognised device. Absent when the account is not exempt. Only accounts with no multi-factor authentication enrolled can be exempted, and enrolling a factor makes the exemption inert."},"device_otp_exempt_by":{"$ref":"#/components/schemas/UserID","readOnly":true,"description":"The staff member who granted the exemption. Absent when the account is not exempt."},"device_otp_exempt_by_email":{"type":"string","format":"email","readOnly":true,"description":"Email address of the staff member who granted the exemption, resolved on the single-user read. Absent when the account is not exempt, when a response does not resolve it, or when the lookup failed."},"device_otp_exempt_note":{"type":"string","readOnly":true,"description":"Why the exemption was granted. Absent when the account is not exempt."},"organization_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of organizations the user belongs to."},"organizations":{"type":"array","readOnly":true,"description":"The organizations the user belongs to, capped at a few entries; organization_count carries the true total.","items":{"$ref":"#/components/schemas/AdminPlatformUserOrg"}}}},"AdminPlatformUserList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminPlatformUser"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminPlatformUserMembership":{"type":"object","additionalProperties":false,"required":["organization_id","organization_name","joined_at"],"properties":{"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"organization_name":{"type":"string","minLength":1,"readOnly":true},"role":{"type":["string","null"],"readOnly":true},"joined_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminPlatformUserMembershipList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminPlatformUserMembership"}}}},"AdminPlatformUserSessionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The platform user's most recent sessions, capped at 20 entries, ordered by last_seen_at descending.","items":{"$ref":"#/components/schemas/SessionInfo"}}}},"AdminPlatformUserBanRequest":{"type":"object","additionalProperties":false,"properties":{"category":{"$ref":"#/components/schemas/AdminBanCategory"},"note":{"type":"string","maxLength":2000,"description":"Optional free-text detail recorded alongside the category, for staff."}},"required":["category"]},"AdminPlatformUserDeviceOTPExemptionRequest":{"type":"object","additionalProperties":false,"properties":{"note":{"type":"string","minLength":1,"maxLength":2000,"pattern":"\\S","description":"Why this account is exempt, naming the demo or review programme it was created for."}},"required":["note"]},"AdminAuditLogFields":{"type":"object","description":"Admin-audit-only fields layered on top of the shared audit fields.","required":["cursor","sensitive"],"properties":{"cursor":{"type":"string","minLength":1,"readOnly":true,"description":"Opaque keyset token for this exact row, encoding (occurred_at, id). Pass a row's cursor as `starting_after` to page after it, or `ending_before` to page before it. The admin list is merged across regions in the BFF, so the page boundary cursor is taken from the first/last kept row's cursor.\n"},"sensitive":{"type":"boolean","readOnly":true,"description":"True when the action is in the curated high-risk set (org bans, billing adjustments, staff changes, quota/rate overrides). Drives the UI badge and the `sensitive` filter.\n"}}},"AdminAuditLogEntry":{"type":"object","description":"A single platform (admin) audit log entry as returned in list responses (without bodies).","allOf":[{"$ref":"#/components/schemas/LogEntryBase"},{"$ref":"#/components/schemas/AuditLogFields"},{"$ref":"#/components/schemas/AdminAuditLogFields"}]},"AdminAuditLogEntryList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAuditLogEntry"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminAuditLogEntryDetail":{"type":"object","description":"A single platform audit log entry with the captured request and response bodies.","allOf":[{"$ref":"#/components/schemas/LogEntryBase"},{"$ref":"#/components/schemas/AuditLogFields"},{"$ref":"#/components/schemas/AdminAuditLogFields"},{"$ref":"#/components/schemas/LogEntryBodies"}]},"AdminBillingAutoTopupSettings":{"type":"object","readOnly":true,"additionalProperties":false,"description":"The organization's stored automatic top-up configuration. Amounts use the wallet currency. Enabling the setting does not guarantee that a saved card can be charged.","required":["enabled","threshold","amount","consent_id","currency_code"],"properties":{"enabled":{"type":"boolean","description":"Whether automatic top-up is enabled. Defaults to false when no settings have been configured."},"threshold":{"type":["string","null"],"description":"Settled wallet balance below which automatic top-up is triggered, as a decimal string in currency units. Null when no threshold is configured. A disabled setting can retain its threshold."},"amount":{"type":["string","null"],"description":"Amount to add on each automatic top-up, as a decimal string in currency units. Null when no amount is configured. A disabled setting can retain its amount."},"consent_id":{"description":"Configured saved payment method. Null selects the organization's default payment method.","oneOf":[{"$ref":"#/components/schemas/PaymentConsentID"},{"type":"null"}]},"currency_code":{"$ref":"#/components/schemas/CurrencyCode","description":"Currency in which the threshold and amount are expressed."}}},"AdminBillingWallet":{"type":"object","readOnly":true,"additionalProperties":false,"required":["id","organization_id","currency_code","balance","settled_balance","reserved","unused_reserved","credit_limit","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/WalletID"},"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"currency_code":{"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code.","example":"USD"},"balance":{"type":"string","minLength":1,"description":"Spendable snapshot as a decimal string in the wallet's currency, including confirmed unused reserved funds. The remaining balance fields explain this same snapshot. Concurrent activity and active holds can still delay funding a send or purchase.","example":"118.00"},"settled_balance":{"type":"string","minLength":1,"description":"Ledger balance as a decimal string: money that has settled into wallet transactions. This is what `balance_after` on the wallet's latest transaction ties to. It sits above `balance` while usage is in flight, because usage is charged against an open block and settles in batches.","example":"125.00"},"reserved":{"type":"string","minLength":1,"description":"Funds allocated to open usage blocks and active holds, as a decimal string. Includes both used and unused funds. Subtract this from `settled_balance`, then add `unused_reserved` to reconcile the spendable `balance`.","example":"20.00"},"unused_reserved":{"type":"string","minLength":1,"description":"Unused reserved funds included in the spendable balance, as a decimal string. Taken from the same snapshot as `balance` and capped at `reserved`. Zero when no unused funds can be confirmed, including when live accounting is unavailable; zero does not prove all reserved funds have been spent.","example":"13.00"},"credit_limit":{"type":"string","minLength":1,"description":"Credit limit as a decimal string. \"0.00\" for prepaid wallets.","example":"0.00"},"created_at":{"type":"string","minLength":1,"format":"date-time"},"updated_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminBillingWalletList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingWallet"}}}},"AdminBillingWalletUpdate":{"type":"object","additionalProperties":false,"required":["credit_limit"],"properties":{"credit_limit":{"$ref":"#/components/schemas/Money","description":"New credit limit. Cannot be reduced below the current balance deficit."}}},"AdminBillingWalletTransaction":{"type":"object","additionalProperties":false,"required":["id","type","amount","currency_code","balance_after","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WalletTransactionID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization that owns the wallet this transaction belongs to."},"type":{"type":"string","minLength":1,"readOnly":true,"description":"Transaction type. `top_up` (a manual or automatic top-up credit), `charge` (a usage or subscription debit), `refund` (a refunded payment; this debits the wallet when the refund claws back the credit the original top-up added), or `adjustment` (a manual correction, positive or negative).\n","enum":["top_up","charge","refund","adjustment"],"example":"top_up"},"amount":{"type":"string","minLength":1,"readOnly":true,"description":"Gross amount as a decimal string. Positive for top_up, negative for charge, and negative for a refund that claws the credit back. Refunds and adjustments may be either sign (never zero).\n","example":"50.00"},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code of the wallet this transaction belongs to.","example":"USD"},"net_amount":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Net amount as a decimal string (amount minus tax). Present for charge and adjustment transactions; null for top_up and refund.\n"},"tax_amount":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Tax amount as a decimal string. Present for charge and adjustment transactions; null for top_up and refund.\n"},"balance_after":{"type":"string","minLength":1,"readOnly":true,"description":"Wallet balance after this transaction as a decimal string.","example":"125.00"},"description":{"type":["string","null"],"readOnly":true,"description":"Human-readable description of the transaction.","example":"Manual top-up"},"billing_period":{"type":["string","null"],"format":"date","readOnly":true,"description":"First day of the billing month this transaction belongs to.","example":"2026-05-01"},"actor_id":{"type":["string","null"],"readOnly":true,"description":"Staff user ID who performed the action. Null for automated transactions."},"reason":{"type":["string","null"],"readOnly":true,"description":"Audit reason for manual adjustments and refunds."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"urls":{"type":"array","readOnly":true,"description":"Contextual external links for this transaction. Top-up transactions linked to a payment intent include a \"View on Airwallex\" link; other transactions have none.\n","items":{"$ref":"#/components/schemas/ResourceLink"}}}},"AdminBillingWalletTransactionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingWalletTransaction"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminBillingWalletAdjustment":{"type":"object","additionalProperties":false,"required":["type","amount","reason"],"properties":{"type":{"type":"string","minLength":1,"enum":["credit","debit"],"description":"'credit' adds funds, 'debit' removes funds."},"amount":{"$ref":"#/components/schemas/Money","description":"Adjustment amount (must be positive)."},"reason":{"type":"string","minLength":1,"maxLength":500,"description":"Audit reason for the adjustment (stored in transaction history)."}}},"AdminBillingWalletCurrencyConversion":{"type":"object","additionalProperties":false,"required":["currency_code"],"properties":{"currency_code":{"$ref":"#/components/schemas/CurrencyCode","description":"Target billing currency. Must be a supported billing currency. A request naming the wallet's current currency succeeds and moves no money — this is the retry path that finishes a re-stamp an earlier partial attempt left behind. The balance and credit limit are converted at the current published rate; when a non-zero balance is converted, the rate is recorded in the description of the adjustment transaction(s) this creates — one closing the old balance, and, unless it rounds to zero in the target currency, one booking the converted balance in the new currency.\n","example":"EUR"}}},"SubscriptionID":{"type":"string","minLength":1,"pattern":"^sub_[0-9a-hjkmnp-tv-z]{26}$","example":"sub_01krdgeqcxet5s7t44vh8rt9mg"},"ChannelSlug":{"type":"string","minLength":1,"x-extensible-enum":["email","sms","whatsapp","amb","voice","push","verify","lookup","realtime","telegram","numbers"],"description":"A product channel slug. Open enum: new channels may be added over time; treat any unrecognized value as a future channel rather than an error.\n"},"BillingProductSummary":{"type":"object","additionalProperties":false,"required":["id","slug","name","unit_name","pricing_model"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"slug":{"type":"string","minLength":1,"example":"email"},"name":{"type":"string","minLength":1,"example":"Email"},"external_description":{"readOnly":true,"type":["string","null"],"description":"A customer-facing description of the product. Null when none is set."},"unit_name":{"type":"string","minLength":1,"description":"The unit the product's usage is measured in, such as `email` for emails sent.","example":"email"},"channel":{"readOnly":true,"$ref":"#/components/schemas/ChannelSlug","description":"The channel this product's spend is attributed to, derived from its slug. Absent for products that belong to no channel, such as plan tiers.\n"},"pricing_model":{"type":"string","minLength":1,"enum":["plan","metered","rate_card","unit_subscription","custom","counter"],"description":"How the product is priced.\n\n- `plan`: it comes with the organization's subscribed plan, with usage\n  tracked against that plan's included allowance.\n- `metered`: it is billed per unit beyond any included allowance.\n- `rate_card`: the per-unit price varies by attributes of the usage, such\n  as destination, instead of being flat.\n- `unit_subscription`: it is a standalone add-on billed as its own\n  recurring subscription, independent of the plan.\n- `custom`: a pricing arrangement that does not fit the other models.\n- `counter`: usage is recorded without enforcing a spending cap or blocking\n  requests.\n","example":"metered"}}},"AdminBillingSubscription":{"type":"object","additionalProperties":false,"required":["id","organization_id","product","quantity","status","cancel_at_period_end","billing_anchor","billing_cycle_type","current_period_start","current_period_end","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SubscriptionID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"product":{"$ref":"#/components/schemas/BillingProductSummary"},"workspace_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}]},"unit_price":{"type":["object","null"],"readOnly":true,"description":"Resolved price per unit.","allOf":[{"$ref":"#/components/schemas/Money"}]},"quantity":{"type":"integer","readOnly":true,"example":1},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code.","example":"USD"},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["scheduled","incomplete","trialing","active","past_due","paused","non_renewing","canceled"],"example":"active"},"billing_anchor":{"type":"string","minLength":1,"format":"date","readOnly":true,"example":"2026-01-15"},"billing_cycle_type":{"type":"string","minLength":1,"readOnly":true,"enum":["anchor","calendar_month"],"example":"calendar_month"},"billing_interval":{"type":["string","null"],"readOnly":true,"enum":["month","year"],"example":"month"},"current_period_start":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"current_period_end":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"cancel_at_period_end":{"type":"boolean","readOnly":true},"canceled_at":{"type":["string","null"],"format":"date-time","readOnly":true},"description":{"type":["string","null"],"readOnly":true},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingSubscriptionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingSubscription"}}}},"AdminBillingSubscriptionCreate":{"type":"object","additionalProperties":false,"required":["product_id","billing_cycle_type"],"properties":{"product_id":{"description":"ID of the product to subscribe the organization to. Plan products are not accepted here: assign an organization's plan with the plan-assign operation, which charges the change and applies the upgrade and downgrade rules.","$ref":"#/components/schemas/ProductID"},"unit_price":{"type":["object","null"],"description":"Override price per unit. If null, uses the product's default price.","allOf":[{"$ref":"#/components/schemas/Money"}]},"billing_interval":{"type":"string","minLength":1,"enum":["month","year"],"description":"Billing cadence for the subscription. Omit it to take the product's own, which is what a `unit_subscription` product requires: its cadence is configured on the product and its rate card is priced per that interval, so sending an interval that disagrees returns 422. Defaults to `month` for every other pricing model."},"billing_cycle_type":{"type":"string","minLength":1,"enum":["anchor","calendar_month"]},"quantity":{"type":"integer","minimum":1,"default":1}}},"AdminBillingSubscriptionUpdate":{"type":"object","additionalProperties":false,"properties":{"unit_price":{"type":["object","null"],"description":"Override price per unit. If null, uses the product's default price.","allOf":[{"$ref":"#/components/schemas/Money"}]},"billing_interval":{"type":"string","enum":["month","year"],"description":"Billing cadence for the subscription. Send `unit_price` for the new period in the same request; sending a changed interval on its own returns 422, because the stored price would otherwise keep describing the old period. Changing a unit subscription's cadence returns 422 whatever else you send, since it comes from the product's billing configuration. Sending the interval the subscription already has is accepted and changes nothing."},"billing_cycle_type":{"type":"string","enum":["anchor","calendar_month"]}}},"AdminBillingSubscriptionCancel":{"type":"object","additionalProperties":false,"required":["mode"],"properties":{"mode":{"type":"string","minLength":1,"enum":["immediate","at_period_end"],"x-enum-varnames":["AdminBillingSubscriptionCancelModeImmediate","AdminBillingSubscriptionCancelModeAtPeriodEnd"],"description":"'immediate' cancels now. 'at_period_end' sets cancel_at_period_end and status to non_renewing."}}},"BillingUsageDailyBucket":{"type":"object","additionalProperties":false,"required":["bucket","quantity"],"properties":{"bucket":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"Calendar date of this usage bucket (UTC).","example":"2026-07-01"},"quantity":{"type":"integer","format":"int64","readOnly":true,"description":"For metered products, units consumed on this date. For pay-as-you-go products, transaction count on this date.\n","example":4821},"net_amount":{"readOnly":true,"description":"Net spend on this date. Present only for rate-card products.","allOf":[{"$ref":"#/components/schemas/Money"}]}}},"BillingUsageProjection":{"type":"object","additionalProperties":false,"required":["projected_quantity"],"properties":{"projected_quantity":{"type":"integer","format":"int64","readOnly":true,"description":"Linear-pace estimate of total units by period end, based on usage to date.","example":98400},"projected_net_amount":{"readOnly":true,"description":"Linear-pace estimate of total spend by period end. Included for pay-as-you-go products only; absent for metered products where spend is not per-unit.\n","allOf":[{"$ref":"#/components/schemas/Money"}]}}},"BillingUsageDomainBucket":{"type":"object","additionalProperties":false,"description":"Volume breakdown for one sending domain in the period. Spend is not included because email is a metered product with a flat included allowance rather than per-domain charges.\n","required":["domain","sent","share_pct"],"properties":{"domain":{"type":"string","minLength":1,"readOnly":true,"description":"Sending domain.","example":"mail.example.com"},"sent":{"type":"integer","format":"int64","readOnly":true,"description":"Emails sent via this domain in the period.","example":32000},"share_pct":{"type":"number","format":"double","readOnly":true,"description":"Percentage share of total email volume for the period (0–100). Shares across all returned domains sum to 100, subject to floating-point rounding.\n","example":51.6}}},"BillingUsageMetered":{"type":"object","additionalProperties":false,"required":["quantity","included","overage","utilization_pct"],"properties":{"quantity":{"type":"integer","format":"int64","readOnly":true,"description":"Units consumed in the current billing period.","example":1284300},"included":{"type":"integer","format":"int64","readOnly":true,"description":"Units included in the plan for the current billing period.","example":2000000},"overage":{"type":"integer","format":"int64","readOnly":true,"description":"Units consumed beyond the included allowance. Zero when within the included limit.","example":0},"utilization_pct":{"type":"number","format":"double","readOnly":true,"description":"Percentage of the included units consumed. May exceed 100 when overage is non-zero.","example":64.2},"daily_breakdown":{"type":"array","readOnly":true,"description":"Per-day usage for the period, ordered chronologically. Included when the daily breakdown is requested.","items":{"$ref":"#/components/schemas/BillingUsageDailyBucket"}},"projection":{"readOnly":true,"description":"Linear-pace projection to period end. Present only for the default billing-period view (no custom start/end window); absent when a custom analytics range is supplied because the window is historical and a projection is not meaningful.\n","allOf":[{"$ref":"#/components/schemas/BillingUsageProjection"}]},"domain_breakdown":{"type":"array","readOnly":true,"description":"Top sending-domain breakdown for the period, ranked by volume. Included for email products when the domain breakdown is requested.","items":{"$ref":"#/components/schemas/BillingUsageDomainBucket"}}}},"BillingUsageCountryBucket":{"type":"object","additionalProperties":false,"required":["country_code","transaction_count"],"properties":{"country_code":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"transaction_count":{"type":"integer","format":"int64","readOnly":true,"description":"Number of transactions (messages) sent to this country in the period.","example":4120},"net_amount":{"readOnly":true,"description":"Net spend for messages sent to this country.","allOf":[{"$ref":"#/components/schemas/Money"}]}}},"BillingUsageTransactional":{"type":"object","additionalProperties":false,"required":["transaction_count","net_amount"],"properties":{"transaction_count":{"type":"integer","format":"int64","readOnly":true,"description":"Number of transactions in the current billing period.","example":18450},"net_amount":{"readOnly":true,"description":"Net amount charged for transactions in the current billing period.","$ref":"#/components/schemas/Money"},"daily_breakdown":{"type":"array","readOnly":true,"description":"Per-day usage for the period, ordered chronologically. Included when the daily breakdown is requested.","items":{"$ref":"#/components/schemas/BillingUsageDailyBucket"}},"projection":{"readOnly":true,"description":"Linear-pace projection to period end. Present only for the default billing-period view (no custom start/end window); absent when a custom analytics range is supplied.\n","allOf":[{"$ref":"#/components/schemas/BillingUsageProjection"}]},"country_breakdown":{"type":"array","readOnly":true,"description":"Per-country breakdown for the period, ranked by volume. Included when the country breakdown is requested.","items":{"$ref":"#/components/schemas/BillingUsageCountryBucket"}}}},"BillingUsageItem":{"type":"object","additionalProperties":false,"required":["product","pricing_model","subscription_id","period_start","period_end","data_complete"],"properties":{"product":{"readOnly":true,"$ref":"#/components/schemas/BillingProductSummary"},"pricing_model":{"type":"string","minLength":1,"readOnly":true,"description":"Pricing model for this product. `metered`: usage counted against an included allowance; the `metered` field is populated. `rate_card`: pay-as-you-go transactions at a rate card price; the `transactional` field is populated.\n","enum":["metered","rate_card"],"example":"metered"},"subscription_id":{"readOnly":true,"description":"ID of the active subscription for this product. `null` for pay-as-you-go products with no subscription.","oneOf":[{"$ref":"#/components/schemas/SubscriptionID"},{"type":"null"}]},"period_start":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Start of the measurement window for this item (inclusive). For subscribed products this is the current subscription period start; for pay-as-you-go products it is the first day of the current calendar month.\n","example":"2026-06-01T00:00:00Z"},"period_end":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"End of the measurement window for this item (exclusive). For subscribed products this is the current subscription period end; for pay-as-you-go products it is the first day of the next calendar month.\n","example":"2026-07-01T00:00:00Z"},"metered":{"readOnly":true,"$ref":"#/components/schemas/BillingUsageMetered","description":"Usage details for metered products. Present when `pricing_model` is metered."},"transactional":{"readOnly":true,"$ref":"#/components/schemas/BillingUsageTransactional","description":"Spend details for rate-card products. Present when `pricing_model` is `rate_card`."},"data_complete":{"type":"boolean","readOnly":true,"description":"Whether usage data for this item is complete. When `false`, real-time usage data was temporarily unavailable; quantities and amounts may be zero rather than the actual values for the period. The item is still included so the product and subscription context remains visible.\n","example":true}}},"BillingUsageResponse":{"type":"object","additionalProperties":false,"required":["period_start","period_end","items"],"properties":{"period_start":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Start of the current calendar month (inclusive). Each item also carries its own period reflecting the actual measurement window.","example":"2026-06-01T00:00:00Z"},"period_end":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Start of the next calendar month (exclusive). Each item also carries its own period reflecting the actual measurement window.","example":"2026-07-01T00:00:00Z"},"items":{"type":"array","readOnly":true,"description":"Usage summary per active product.","items":{"$ref":"#/components/schemas/BillingUsageItem"}}}},"BillingProductDimensions":{"type":"object","additionalProperties":false,"description":"Dimension field names for `rate_card` products.","properties":{"fields":{"type":"array","description":"Names of the dimension keys this product prices on.","items":{"type":"string"}}}},"BillingProductMatchingSpec":{"type":"object","additionalProperties":false,"description":"Fallback tiers for pricing a `rate_card` product when a request does not match on every dimension.","properties":{"patterns":{"type":"array","description":"Each entry lists the dimension field names to match on for one tier, ordered from most specific to least. Price lookup tries each tier in turn until one matches.","items":{"type":"array","items":{"type":"string"}}}}},"BillingTaxCategory":{"type":"string","enum":["telecom","messaging_saas","subscription_saas"],"example":"subscription_saas","description":"Tax category for VAT/sales-tax classification."},"BillingProductUseIncrement":{"type":"boolean","description":"Whether this product is billed by session duration. When `true`, its prices use an increment (initial and continuation seconds) to determine how each session's duration is rounded when calculating the charge.\n","example":false},"AdminBillingProduct":{"type":"object","additionalProperties":false,"required":["id","slug","name","unit_name","pricing_model","status","use_increment","billing_unit_size","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"slug":{"type":"string","minLength":1,"pattern":"^[a-z][a-z0-9]*(_[a-z0-9]+)*$","example":"email_sends"},"name":{"type":"string","minLength":1,"example":"Email Sends"},"internal_description":{"type":["string","null"],"description":"Internal notes visible only to Bird staff."},"external_description":{"type":["string","null"],"description":"Customer-facing description of this product."},"label":{"type":["string","null"],"description":"Internal grouping label used to organize products in the admin overview (e.g. SMS, E-mail, Numbers)."},"unit_name":{"type":"string","minLength":1,"example":"email"},"channel":{"readOnly":true,"$ref":"#/components/schemas/ChannelSlug","description":"The channel this product's spend is attributed to, derived from its slug. Absent for products that belong to no channel, such as plan tiers.\n"},"pricing_model":{"type":"string","minLength":1,"enum":["plan","metered","rate_card","unit_subscription","custom","counter"],"example":"metered"},"status":{"type":"string","minLength":1,"enum":["draft","active","deprecated"],"example":"active"},"dimensions":{"$ref":"#/components/schemas/BillingProductDimensions"},"matching_spec":{"$ref":"#/components/schemas/BillingProductMatchingSpec"},"matching_spec_mismatch":{"type":"string","minLength":1,"enum":["fail","continue"],"example":"fail"},"tax_category":{"$ref":"#/components/schemas/BillingTaxCategory"},"use_increment":{"$ref":"#/components/schemas/BillingProductUseIncrement"},"billing_unit_size":{"type":"integer","format":"int64","minimum":1,"description":"Number of units that form one chargeable block for metered overage. Overage is ceiling-rounded to the nearest multiple of this value before applying the unit price. A value of 1 (the default) charges per individual unit.","example":1000},"rate_card_id":{"readOnly":true,"description":"ID of the active rate card currently set as default for this product.","oneOf":[{"$ref":"#/components/schemas/RateCardID"},{"type":"null"}]},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingProductList":{"allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingProduct"}}}}]},"BillingProductSlug":{"type":"string","minLength":1,"pattern":"^[a-z][a-z0-9]*(_[a-z0-9]+)*$","description":"URL-safe identifier for a billing product. Lowercase letters and digits, separated by underscores. Must start with a letter.","example":"email_sends"},"AdminBillingMeterCreate":{"type":"object","additionalProperties":false,"required":["name","aggregation"],"properties":{"name":{"type":"string","minLength":1,"example":"Email Sends Meter"},"aggregation":{"type":"string","minLength":1,"enum":["sum","max"],"example":"sum"}}},"AdminBillingUnitSubscriptionConfigInput":{"type":"object","additionalProperties":false,"properties":{"billing_interval":{"type":"string","enum":["month","year"],"description":"Unit the rate-card price is quoted in. When omitted, resets to `month`.","example":"month"},"billing_interval_count":{"type":"integer","minimum":1,"maximum":36,"description":"Number of intervals one charge covers. When omitted, resets to `1`.","example":6}}},"AdminBillingProductCreate":{"type":"object","additionalProperties":false,"required":["slug","name","unit_name","pricing_model"],"properties":{"slug":{"$ref":"#/components/schemas/BillingProductSlug"},"name":{"type":"string","minLength":1,"example":"Email Sends"},"internal_description":{"type":"string","description":"Internal notes visible only to Bird staff."},"external_description":{"type":"string","description":"Customer-facing description of this product."},"label":{"type":"string","description":"Internal grouping label used to organize products in the admin overview (e.g. SMS, E-mail, Numbers)."},"unit_name":{"type":"string","minLength":1,"example":"email"},"pricing_model":{"type":"string","minLength":1,"enum":["plan","metered","rate_card","unit_subscription","custom","counter"],"example":"metered"},"dimensions":{"$ref":"#/components/schemas/BillingProductDimensions"},"matching_spec":{"$ref":"#/components/schemas/BillingProductMatchingSpec"},"matching_spec_mismatch":{"type":"string","minLength":1,"enum":["fail","continue"],"default":"fail"},"tax_category":{"allOf":[{"$ref":"#/components/schemas/BillingTaxCategory"}],"default":"subscription_saas"},"use_increment":{"allOf":[{"$ref":"#/components/schemas/BillingProductUseIncrement"}],"default":false},"billing_unit_size":{"type":"integer","format":"int64","minimum":1,"description":"Number of units that form one chargeable block for metered overage. Overage is ceiling-rounded to the nearest multiple of this value before applying the unit price. Defaults to 1 (charge per individual unit).","example":1000},"meter":{"$ref":"#/components/schemas/AdminBillingMeterCreate"},"unit_subscription_config":{"description":"Billing cadence for a `unit_subscription` product, created with the product in one transaction. Omit it to bill monthly, which is also what omitting each of its own fields falls back to.","allOf":[{"$ref":"#/components/schemas/AdminBillingUnitSubscriptionConfigInput"}]}}},"AdminBillingProductUpdate":{"type":"object","additionalProperties":false,"properties":{"slug":{"$ref":"#/components/schemas/BillingProductSlug"},"name":{"type":"string","minLength":1},"internal_description":{"type":"string","description":"Internal notes visible only to Bird staff."},"external_description":{"type":"string","description":"Customer-facing description of this product."},"label":{"type":["string","null"],"description":"Internal grouping label used to organize products in the admin overview (e.g. SMS, E-mail, Numbers). Set to null to remove the label."},"unit_name":{"type":"string","minLength":1},"dimensions":{"$ref":"#/components/schemas/BillingProductDimensions"},"matching_spec":{"$ref":"#/components/schemas/BillingProductMatchingSpec"},"matching_spec_mismatch":{"type":"string","minLength":1,"enum":["fail","continue"]},"tax_category":{"$ref":"#/components/schemas/BillingTaxCategory"},"use_increment":{"$ref":"#/components/schemas/BillingProductUseIncrement"},"billing_unit_size":{"type":"integer","format":"int64","minimum":1,"description":"Number of units that form one chargeable block for metered overage. Overage is ceiling-rounded to the nearest multiple of this value before applying the unit price.","example":1000},"rate_card_id":{"description":"Set the default rate card for this product. Must be an active rate card belonging to this product.","oneOf":[{"$ref":"#/components/schemas/RateCardID"},{"type":"null"}]},"status":{"type":"string","enum":["active","deprecated"],"description":"Valid transitions: draft→active, active→deprecated, deprecated→active. Setting status back to draft is not supported.\n"}}},"MeterID":{"type":"string","minLength":1,"pattern":"^mtr_[0-9a-hjkmnp-tv-z]{26}$","example":"mtr_01krdgeqcxet5s7t44vh8rt9mg"},"AdminBillingMeter":{"type":"object","additionalProperties":false,"required":["id","product_id","name","aggregation","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/MeterID"},"product_id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"name":{"type":"string","minLength":1,"example":"Email Sends Meter"},"aggregation":{"type":"string","minLength":1,"enum":["sum","max"],"example":"sum"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingSubscriptionPage":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingSubscription"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminRevenueMetricKind":{"type":"string","minLength":1,"description":"Financial measure and its attribution basis.\n- `mrr_monthly`: Current monthly-billed paid-plan MRR, excluding removed organizations.\n- `mrr_annual`: Current annual-billed paid-plan value divided by twelve, excluding removed organizations.\n- `mrr_non_renewing`: Current MRR flagged to stop at period end; a scheduled replacement may follow.\n- `mrr_past_due`: Current paid-plan MRR in past-due status; not proof of collectability.\n- `mrr_unpriced`: Count of paid-plan subscriptions with no resolved stored price; amount is zero.\n- `mrr_accounts`: Distinct organizations contributing priced paid-plan MRR in this currency; amount is zero. An organization with multiple eligible plans is counted once.\n- `payg_history`: Trailing 30-day non-recurring wallet charges using the existing charge-date removal policy; not earned or collected revenue.\n- `mrr_history`: Charge-period plan MRR on the given UTC day, net of tax. Upgrade deltas may restate earlier days.\n- `mrr_wallet_settled`: Charge-period MRR marked settled against the wallet, including approved credit; not cash.\n- `scheduled_mrr`: Normalized MRR of plans scheduled to start on the given day, not yet active.\n- `scheduled_expansion`: MRR increase from unambiguous same-currency scheduled plan handovers relative to the current plan.\n- `scheduled_contraction`: MRR decrease from unambiguous same-currency scheduled plan handovers relative to the current plan.\n- `upgrade_charges`: Net plan-upgrade fees posted on the given day; not an effective-date MRR movement.\n- `churned_mrr`: Normalized MRR of subscriptions cancelled on the given day; based on their stored price.\n- `invoiced_net`: Net debit invoices issued on the given day, excluding void invoices.\n- `invoiced_tax`: Customer tax on debit invoices issued on the given day, excluding void invoices.\n- `invoiced_gross`: Gross debit invoices issued on the given day, excluding void invoices.\n- `invoice_not_due`: Unpaid gross debit invoices with a due date today or later.\n- `invoice_overdue_1_30`: Unpaid gross debit invoices one to thirty days past due.\n- `invoice_overdue_31_60`: Unpaid gross debit invoices thirty-one to sixty days past due.\n- `invoice_overdue_61_90`: Unpaid gross debit invoices sixty-one to ninety days past due.\n- `invoice_overdue_90_plus`: Unpaid gross debit invoices more than ninety days past due.\n- `invoice_due_unknown`: Unpaid gross debit invoices with no due date.\n- `charges_net`: All net charges posted to customer wallets on the given day.\n- `charges_tax`: Tax on charges posted to customer wallets on the given day.\n- `charges_gross`: All gross charges posted to customer wallets on the given day, shown positive.\n- `plan_charges_net`: Net wallet charges identified as initial recurring fees, renewals or plan upgrades.\n- `non_plan_charges_net`: Net wallet charges excluding recurring-fee and plan-upgrade keys; includes usage, overage, one-offs and commitment gaps.\n- `charges_missing_tax`: Gross charge amount whose net or tax breakdown is missing; count is affected transactions.\n- `provider_funding`: Wallet credits linked by stable identity, amount and currency to successful provider payment intents, by wallet-credit day.\n- `invoice_funding`: Wallet credits for staff-recorded invoice payments; not independently bank-verified.\n- `unmatched_funding`: Other top-up credits without a matching successful provider intent or staff-recorded invoice payment.\n- `wallet_funding_organizations`: Distinct organizations with top-ups in the selected or prior comparison period, per region and currency; amount is zero and day is the period start. This is a whole-period count, not a daily series. Organizations funding multiple currencies appear in each currency.\n- `wallet_adjustments`: Signed wallet adjustments; these are not necessarily cash.\n- `wallet_refunds`: Signed wallet refund ledger effects; do not add them to provider refunds as a second cash movement.\n- `wallet_prepaid`: Sum of positive posted wallet balances, without netting other customers' debt.\n- `wallet_debt`: Absolute sum of negative posted wallet balances; may overlap unpaid invoices.\n- `wallet_beyond_credit`: Posted wallet debt above approved credit limits, excluding reservations.\n- `wallet_credit_limit`: Total approved wallet credit limits; not cash or revenue.\n- `wallet_reserved`: Current reserved wallet amounts; not an additional posted expense.\n- `provider_refunds`: Confirmed provider refunds by confirmation day, separate from wallet clawbacks.\n- `pending_refunds`: Gross refund requests currently pending; not confirmed cash outflows.\n- `payment_created`: Provider payment intents created on the given day, including all current outcomes.\n- `payment_succeeded`: Created-cohort payment intents currently succeeded; this is not a capture-date series.\n- `payment_failed`: Created-cohort payment intents currently failed, declined or blocked.\n- `payment_canceled`: Created-cohort payment intents currently cancelled.\n- `payment_open`: Created-cohort payment intents whose outcome remains nonterminal.\n- `payment_uncredited`: Succeeded intents created in the reporting history without an identity, amount and currency matched wallet credit.\n","enum":["mrr_monthly","mrr_annual","mrr_non_renewing","mrr_past_due","mrr_unpriced","mrr_accounts","payg_history","mrr_history","mrr_wallet_settled","scheduled_mrr","scheduled_expansion","scheduled_contraction","upgrade_charges","churned_mrr","invoiced_net","invoiced_tax","invoiced_gross","invoice_not_due","invoice_overdue_1_30","invoice_overdue_31_60","invoice_overdue_61_90","invoice_overdue_90_plus","invoice_due_unknown","charges_net","charges_tax","charges_gross","plan_charges_net","non_plan_charges_net","charges_missing_tax","provider_funding","invoice_funding","unmatched_funding","wallet_funding_organizations","wallet_adjustments","wallet_refunds","wallet_prepaid","wallet_debt","wallet_beyond_credit","wallet_credit_limit","wallet_reserved","provider_refunds","pending_refunds","payment_created","payment_succeeded","payment_failed","payment_canceled","payment_open","payment_uncredited"]},"AdminRevenueMetric":{"type":"object","additionalProperties":false,"readOnly":true,"description":"A native-currency financial measure, optionally converted to the reporting currency. An absent day indicates a current snapshot.","required":["kind","amount","count"],"properties":{"kind":{"$ref":"#/components/schemas/AdminRevenueMetricKind"},"day":{"type":"string","format":"date","description":"UTC attribution day; absent for current snapshots."},"amount":{"$ref":"#/components/schemas/Money"},"reporting_amount":{"$ref":"#/components/schemas/Money","description":"The amount converted using the report's current FX policy; absent when the rate is unavailable."},"count":{"type":"integer","format":"int64","minimum":0,"description":"Number of underlying records when counted. Daily MRR and trailing-charge history use zero because record counts are not supplied. Distinct customers are not implied."}}},"AdminRevenueAccount":{"type":"object","additionalProperties":false,"readOnly":true,"description":"An organization on a bounded financial leaderboard, with amounts in its wallet currency. Balances are current snapshots.","required":["organization_id","charges","funding","balance","credit_limit"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"charges":{"$ref":"#/components/schemas/Money","description":"Net wallet charges in the selected period."},"funding":{"$ref":"#/components/schemas/Money","description":"All wallet top-ups in the selected period, including staff-recorded invoice payments."},"balance":{"$ref":"#/components/schemas/Money"},"credit_limit":{"$ref":"#/components/schemas/Money"}}},"AdminRevenueCollectionKind":{"type":"string","minLength":1,"description":"Collection policy that created the lifecycle.","enum":["auto_topup","overage_collection"]},"AdminRevenueCollectionStatus":{"type":"string","minLength":1,"description":"Current lifecycle state. An exhausted lifecycle has no remaining automatic retries.","enum":["in_progress","succeeded","exhausted"]},"AdminRevenueCollectionOutcome":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Current outcome of collection lifecycles created on the given day. Counts are independent of currency and do not count individual retries as new lifecycles.","required":["day","kind","status","count"],"properties":{"day":{"type":"string","minLength":1,"format":"date"},"kind":{"$ref":"#/components/schemas/AdminRevenueCollectionKind"},"status":{"$ref":"#/components/schemas/AdminRevenueCollectionStatus"},"count":{"type":"integer","format":"int64","minimum":0}}},"AdminRevenueReport":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Financial reporting over complete UTC days. Period flows and current snapshots remain separate; no amount certifies bank receipt or recognized revenue.","required":["from","to","comparison_from","history_from","as_of","metrics","reporting_currency","missing_currencies","top_spend_accounts","debt_accounts","collection_outcomes"],"properties":{"from":{"type":"string","minLength":1,"format":"date-time","description":"Selected period start, inclusive."},"to":{"type":"string","minLength":1,"format":"date-time","description":"Selected period end, exclusive."},"comparison_from":{"type":"string","minLength":1,"format":"date-time","description":"Start of the immediately preceding equal-length comparison period."},"history_from":{"type":"string","minLength":1,"format":"date-time","description":"Start of returned daily history, including the trailing-window lookback."},"as_of":{"type":"string","minLength":1,"format":"date-time","description":"Query time for current snapshots and payment outcome observation."},"reporting_currency":{"$ref":"#/components/schemas/CurrencyCode","description":"Currency used for reporting_amount; USD for this report."},"missing_currencies":{"type":"array","items":{"$ref":"#/components/schemas/CurrencyCode"},"description":"Native currencies that could not be converted; the combined reporting-currency total is incomplete."},"metrics":{"type":"array","items":{"$ref":"#/components/schemas/AdminRevenueMetric"}},"top_spend_accounts":{"type":"array","items":{"$ref":"#/components/schemas/AdminRevenueAccount"},"description":"Up to ten accounts per native currency with the largest net charges in the responding region. Empty for global and collection reports."},"debt_accounts":{"type":"array","items":{"$ref":"#/components/schemas/AdminRevenueAccount"},"description":"Up to ten negative-wallet accounts per native currency in the responding region. Empty for global and collection reports. This is a leaderboard, not a complete receivables list."},"collection_outcomes":{"type":"array","items":{"$ref":"#/components/schemas/AdminRevenueCollectionOutcome"},"description":"Currency-independent collection lifecycle counts, separate from payment-intent attempts."}}},"AdminBillingMrrByCurrency":{"type":"object","additionalProperties":false,"description":"Monthly recurring revenue for a single currency.","required":["mrr","at_risk","subscription_count","at_risk_subscription_count"],"properties":{"mrr":{"readOnly":true,"description":"Monthly recurring revenue in this currency. Annual subscriptions are normalized to a monthly figure.","allOf":[{"$ref":"#/components/schemas/Money"}]},"at_risk":{"readOnly":true,"description":"The portion of this currency's recurring revenue set to cancel at the end of the current period. Still billed this period but lost at renewal, so net forward revenue is mrr minus at_risk.","allOf":[{"$ref":"#/components/schemas/Money"}]},"subscription_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Subscriptions contributing to this currency's recurring revenue.","example":64},"at_risk_subscription_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Subscriptions in this currency set to cancel at the end of the current period.","example":4}}},"AdminBillingSubscriptionStatusCounts":{"type":"object","additionalProperties":false,"description":"Count of active paid subscriptions by status.","required":["incomplete","trialing","active","past_due","paused","non_renewing"],"properties":{"incomplete":{"type":"integer","minimum":0,"readOnly":true},"trialing":{"type":"integer","minimum":0,"readOnly":true},"active":{"type":"integer","minimum":0,"readOnly":true},"past_due":{"type":"integer","minimum":0,"readOnly":true},"paused":{"type":"integer","minimum":0,"readOnly":true},"non_renewing":{"type":"integer","minimum":0,"readOnly":true}}},"AdminBillingPlansSummary":{"type":"object","additionalProperties":false,"description":"Aggregate figures for active paid plan subscriptions across all organizations, narrowed by the same filters as the plan list.","required":["mrr_by_currency","status_counts","total_subscriptions","total_organizations","unpriced_count"],"properties":{"report":{"readOnly":true,"description":"Platform plan snapshots, historical MRR and scheduled movements. Present only when reporting dates are supplied. Invoices, wallets and payments are reported by their own billing operations.","allOf":[{"$ref":"#/components/schemas/AdminRevenueReport"}]},"mrr_by_currency":{"type":"array","readOnly":true,"description":"Monthly recurring revenue per currency. Annual subscriptions are normalized to a monthly figure. Subscriptions without a resolved price are excluded.","items":{"$ref":"#/components/schemas/AdminBillingMrrByCurrency"}},"status_counts":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminBillingSubscriptionStatusCounts"}]},"total_subscriptions":{"type":"integer","minimum":0,"readOnly":true,"description":"Active paid plan subscriptions matching the filters.","example":128},"total_organizations":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct organizations with an active paid plan matching the filters.","example":113},"unpriced_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Subscriptions excluded from recurring-revenue figures because they carry no resolved price.","example":0}}},"WalletTransactionType":{"type":"string","minLength":1,"description":"Wallet transaction type.\n\n- `top_up`: a manual or automatic top-up credit.\n- `charge`: a usage or subscription debit.\n- `refund`: a refunded payment. It debits the wallet: the credit from the\n  original top-up is taken back when the money returns to your card.\n- `adjustment`: a positive or negative manual correction.\n","enum":["top_up","charge","refund","adjustment"],"example":"top_up"},"AdminBillingWalletTransactionPage":{"allOf":[{"type":"object","required":["data"],"properties":{"report":{"readOnly":true,"description":"Regional wallet funding provenance, charges, PAYG history, current balances and account leaders. Present only when reporting dates are supplied; it covers the whole region regardless of the current page.","allOf":[{"$ref":"#/components/schemas/AdminRevenueReport"}]},"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingWalletTransaction"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminProviderFundingState":{"type":"string","minLength":1,"enum":["funded","not_observed","unknown"],"description":"Funded identifies the first confirmed provider top-up. Not observed means this region has no qualifying posting in complete retained history. Unknown means history or payment identity cannot establish the first funding."},"AdminProviderFunding":{"type":"object","readOnly":true,"additionalProperties":false,"required":["wallet_transaction_id","payment_intent_id","wallet_posted_at","amount"],"properties":{"wallet_transaction_id":{"$ref":"#/components/schemas/WalletTransactionID"},"payment_intent_id":{"$ref":"#/components/schemas/PaymentIntentID"},"wallet_posted_at":{"type":"string","minLength":1,"format":"date-time","description":"Original posting timestamp. Posting order determines the first funding even when timestamps are out of order."},"amount":{"$ref":"#/components/schemas/Money","description":"Original provider-funded wallet amount and currency, without conversion to the wallet's current currency."}}},"AdminOrganizationProviderFunding":{"type":"object","readOnly":true,"additionalProperties":false,"required":["organization_id","state","first_funding"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"state":{"$ref":"#/components/schemas/AdminProviderFundingState"},"first_funding":{"oneOf":[{"$ref":"#/components/schemas/AdminProviderFunding"},{"type":"null"}],"description":"Confirmed first provider funding when state is funded; null for not_observed or unknown."}},"not":{"anyOf":[{"properties":{"state":{"const":"funded"},"first_funding":{"type":"null"}}},{"properties":{"state":{"not":{"const":"funded"}},"first_funding":{"type":"object"}}}]}},"AdminFirstProviderFundings":{"type":"object","readOnly":true,"additionalProperties":false,"required":["observed_at","data"],"properties":{"observed_at":{"type":"string","minLength":1,"format":"date-time","description":"Time this regional read was observed, not the provider settlement time."},"data":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/AdminOrganizationProviderFunding"}}}},"AdminBillingSubscriptionStatusBreakdown":{"type":"object","additionalProperties":false,"description":"Count of subscriptions by status, including terminal and scheduled states.","required":["scheduled","incomplete","trialing","active","past_due","paused","non_renewing","canceled"],"properties":{"scheduled":{"type":"integer","minimum":0,"readOnly":true},"incomplete":{"type":"integer","minimum":0,"readOnly":true},"trialing":{"type":"integer","minimum":0,"readOnly":true},"active":{"type":"integer","minimum":0,"readOnly":true},"past_due":{"type":"integer","minimum":0,"readOnly":true},"paused":{"type":"integer","minimum":0,"readOnly":true},"non_renewing":{"type":"integer","minimum":0,"readOnly":true},"canceled":{"type":"integer","minimum":0,"readOnly":true}}},"AdminBillingPricingModelCount":{"type":"object","additionalProperties":false,"description":"Number of subscriptions for a single pricing model.","required":["pricing_model","count"],"properties":{"pricing_model":{"type":"string","minLength":1,"readOnly":true,"enum":["plan","metered","rate_card","unit_subscription","custom","counter"],"example":"metered"},"count":{"type":"integer","minimum":0,"readOnly":true,"example":42}}},"AdminBillingSubscriptionsSummary":{"type":"object","additionalProperties":false,"description":"Aggregate figures for subscriptions across all organizations, narrowed by the same filters as the subscription list. The free plan is excluded.","required":["status_counts","by_pricing_model","total_subscriptions","total_organizations"],"properties":{"status_counts":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminBillingSubscriptionStatusBreakdown"}]},"by_pricing_model":{"type":"array","readOnly":true,"description":"Subscription counts grouped by pricing model.","items":{"$ref":"#/components/schemas/AdminBillingPricingModelCount"}},"total_subscriptions":{"type":"integer","minimum":0,"readOnly":true,"description":"Subscriptions matching the filters.","example":512},"total_organizations":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct organizations with a subscription matching the filters.","example":240}}},"AdminBillingMrrPoint":{"type":"object","additionalProperties":false,"description":"Plan MRR on a single calendar day, summed across every currency into the base currency (USD). Booking counts every recurring charge whose billing period covers the day, including pending/unpaid periods; collected counts only settled (funded) charges.","required":["day","booking","collected"],"properties":{"day":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"The calendar day (UTC) the figures are as of.","example":"2026-07-14"},"booking":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Contracted recurring revenue for periods covering this day."},"collected":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Settled (funded) recurring revenue for periods covering this day."}}},"AdminBillingMrrTimeseries":{"type":"object","additionalProperties":false,"description":"Platform-wide daily plan MRR over the window, converted to a single base currency (USD) via the latest FX rates. One point per calendar day, oldest first.","required":["series"],"properties":{"series":{"type":"array","readOnly":true,"description":"One point per calendar day in the window, oldest first.","items":{"$ref":"#/components/schemas/AdminBillingMrrPoint"}}}},"AdminBillingPaygPoint":{"type":"object","additionalProperties":false,"description":"Pay-as-you-go revenue for the 30 days up to and including a single calendar day, summed across every currency into the base currency (USD). Counts metered usage, period-end overage, one-time purchases, and minimum-commit shortfalls; excludes plan subscription charges, wallet top-ups, refunds, and credit adjustments.","required":["day","revenue"],"properties":{"day":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"The calendar day (UTC) the trailing window ends on, inclusive.","example":"2026-07-14"},"revenue":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Non-subscription revenue charged over the 30 days ending on this day, before tax and without later refunds or credits subtracted. Final for every point but the last, whose window ends on a day still in progress and so keeps rising until that day closes."}}},"AdminBillingPaygTimeseries":{"type":"object","additionalProperties":false,"description":"This region's daily pay-as-you-go revenue run-rate over the window, converted to a single base currency (USD) via the latest FX rates. One point per calendar day, oldest first.","required":["series"],"properties":{"series":{"type":"array","readOnly":true,"description":"One point per calendar day in the window, oldest first.","items":{"$ref":"#/components/schemas/AdminBillingPaygPoint"}}}},"BillingInvoiceStatus":{"type":"string","minLength":1,"enum":["open","paid","partial","void"],"description":"Payment status of the invoice.\n\n- `open`: issued and awaiting full payment.\n- `paid`: fully settled.\n- `partial`: partially paid; a balance remains.\n- `void`: cancelled before or after issue.\n"},"BillingInvoiceType":{"type":"string","minLength":1,"enum":["debit","credit"],"description":"Accounting classification of the invoice. The `debit` value is a standard invoice for an amount you owe us, such as charges, monthly recurring costs, or overage. The `credit` value is a credit note for an amount we owe you, such as a correction or goodwill credit.\n"},"AdminBillingInvoice":{"type":"object","additionalProperties":false,"description":"An admin view of a billing invoice, includes organization_id for cross-org listing.","required":["id","organization_id","invoice_number","status","type","currency_code","billing_month","subtotal_net","tax_amount","total_gross","amount_paid","issued_at","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/InvoiceID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"invoice_number":{"type":"string","minLength":1,"readOnly":true},"status":{"readOnly":true,"$ref":"#/components/schemas/BillingInvoiceStatus"},"type":{"readOnly":true,"$ref":"#/components/schemas/BillingInvoiceType"},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode"},"billing_month":{"type":"string","minLength":1,"format":"date","readOnly":true},"subtotal_net":{"type":"string","minLength":1,"readOnly":true,"description":"Decimal amount as a string, in major currency units."},"tax_amount":{"type":"string","minLength":1,"readOnly":true},"total_gross":{"type":"string","minLength":1,"readOnly":true},"amount_paid":{"type":"string","minLength":1,"readOnly":true},"due_date":{"type":["string","null"],"format":"date-time","readOnly":true},"issued_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"paid_at":{"type":["string","null"],"format":"date-time","readOnly":true},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"tax_in_issuer_currency":{"readOnly":true,"description":"Total tax restated in the currency of the entity that issued this invoice, when that differs from `currency_code`. Converted at the exchange rate in force on the issue date. Null when no restatement applies.","oneOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]}}},"AdminBillingInvoiceList":{"allOf":[{"type":"object","required":["data"],"properties":{"report":{"readOnly":true,"description":"Platform invoiced net amounts, tax, gross amounts and current aging. Present only when reporting dates are supplied; it covers all matching report dates regardless of the current page.","allOf":[{"$ref":"#/components/schemas/AdminRevenueReport"}]},"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingInvoice"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"InvoiceLineItemID":{"type":"string","minLength":1,"pattern":"^ivl_[0-9a-hjkmnp-tv-z]{26}$","example":"ivl_01krdgeqcxet5s7t44vh8rt9mg"},"BillingInvoiceLineItem":{"type":"object","additionalProperties":false,"description":"A single line on a billing invoice.","required":["id","product_name","description","net_amount","tax_amount","gross_amount","currency_code","sort_order"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/InvoiceLineItemID"},"workspace_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}],"description":"The workspace this line concerns. Null for an org-level charge, such as the plan's recurring fee."},"product_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/ProductID"},{"type":"null"}],"description":"The product this line concerns, or null when the line is not tied to one."},"product_name":{"type":"string","minLength":1,"readOnly":true},"description":{"type":"string","minLength":1,"readOnly":true},"period_start":{"description":"First day of the line's service period (UTC). Absent for lines with no period (for example, adjustments).","type":"string","format":"date","readOnly":true},"period_end":{"description":"Exclusive end of the line's service period: the next period's first day (UTC); the billed last day is the day before. Absent for lines with no period.","type":"string","format":"date","readOnly":true},"quantity":{"description":"Billed quantity for usage lines (for example, number of messages). Absent for flat or subscription lines.","type":"integer","format":"int64","readOnly":true},"unit_name":{"description":"Unit the quantity is counted in (for example, `message`, `minute`). Absent when the line has no unit.","type":"string","readOnly":true},"net_amount":{"type":"string","minLength":1,"readOnly":true,"description":"This line's amount before tax, as a decimal string in `currency_code` units."},"tax_rate":{"type":["string","null"],"readOnly":true,"description":"Tax rate applied to this line, as a decimal fraction (for example, `0.21` for 21%). Null when no tax applies."},"tax_amount":{"type":"string","minLength":1,"readOnly":true,"description":"Tax charged on this line, as a decimal string in `currency_code` units."},"gross_amount":{"type":"string","minLength":1,"readOnly":true,"description":"This line's total (`net_amount` plus `tax_amount`), as a decimal string in `currency_code` units."},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode"},"sort_order":{"type":"integer","readOnly":true,"description":"Position of this line among the invoice's line items, for display order."}}},"AdminBillingInvoiceDetail":{"type":"object","description":"Admin view of an invoice with line items and amendable fields.","allOf":[{"$ref":"#/components/schemas/AdminBillingInvoice"},{"type":"object","required":["billing_address","line_items"],"properties":{"po_number":{"type":["string","null"],"readOnly":true},"billing_address":{"description":"The bill-to block shown on the invoice — the customer's name and billing address, one item per line — captured when the invoice was issued.","type":"string","minLength":1,"readOnly":true},"line_items":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/BillingInvoiceLineItem"}}}}]},"AdminBillingInvoiceUpdate":{"type":"object","additionalProperties":false,"description":"Amendable fields on an invoice.","properties":{"po_number":{"type":["string","null"],"description":"Purchase order reference. Pass null to clear."},"billing_address":{"type":"string","minLength":1,"description":"Replacement bill-to block for the invoice — the customer's name and billing address, one item per line."}}},"AdminBillingInvoicePayment":{"type":"object","additionalProperties":false,"description":"Request body for recording an off-platform payment on an invoice.","required":["amount"],"properties":{"amount":{"type":"string","minLength":1,"description":"Payment amount as a decimal string in the invoice currency (e.g. \"50.00\")."}}},"AdminBillingPaymentReceipt":{"type":"object","additionalProperties":false,"description":"An admin view of a wallet top-up payment receipt; includes organization_id for cross-org listing.","required":["id","organization_id","wallet_transaction_id","amount","currency_code","auto_topup","issued_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PaymentReceiptID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"wallet_transaction_id":{"readOnly":true,"$ref":"#/components/schemas/WalletTransactionID","description":"ID of the wallet transaction that funded this top-up."},"amount":{"type":"string","minLength":1,"readOnly":true,"description":"Decimal amount as a string, in major currency units."},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode"},"payment_method_type":{"type":["string","null"],"readOnly":true,"description":"Payment method type (e.g. \"card\", \"bank_transfer\", \"ideal\")."},"payment_method_summary":{"type":["string","null"],"readOnly":true,"description":"Human-readable payment method summary (e.g. \"Visa ending in 4242\")."},"provider_payment_id":{"type":["string","null"],"readOnly":true,"description":"External provider reference for reconciliation (e.g. Airwallex payment ID)."},"auto_topup":{"type":"boolean","readOnly":true,"description":"Whether this receipt was generated from an automatic top-up."},"issued_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingPaymentReceiptList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingPaymentReceipt"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"ProformaDocumentID":{"type":"string","minLength":1,"pattern":"^pfo_[0-9a-hjkmnp-tv-z]{26}$","example":"pfo_01krdgeqcxet5s7t44vh8rt9mg"},"AdminBillingProformaDocument":{"type":"object","additionalProperties":false,"description":"An admin view of a pro forma document; includes organization_id for cross-org listing.","required":["id","organization_id","document_number","description","amount","issued_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ProformaDocumentID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"document_number":{"type":"string","minLength":1,"readOnly":true,"description":"Sequential reference to quote as the bank-transfer payment reference (e.g. \"PRO-2600001\")."},"description":{"type":"string","minLength":1,"readOnly":true,"description":"What the prepayment is for."},"amount":{"readOnly":true,"description":"Amount requested by this document.","allOf":[{"$ref":"#/components/schemas/Money"}]},"issued_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingProformaDocumentList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingProformaDocument"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminPaymentDisputeAccept":{"type":"object","description":"Concede a dispute, optionally with a partial refund.","additionalProperties":false,"properties":{"refund":{"oneOf":[{"$ref":"#/components/schemas/AdminPaymentDisputeRefund"},{"type":"null"}],"description":"Partial refund to offer. Omit or null to accept in full. Accepting a dispute at the RFI stage triggers a full refund regardless."}}},"AdminPaymentDisputeDocumentType":{"type":"string","minLength":1,"description":"How an evidence bundle is categorised in the challenge submission.","enum":["PRIMARY","ORDER","CUSTOMER","OTHER"]},"AdminPaymentDisputeDocument":{"type":"object","description":"One bundle of evidence files submitted with a challenge.","additionalProperties":false,"required":["type","file_ids"],"properties":{"type":{"$ref":"#/components/schemas/AdminPaymentDisputeDocumentType"},"description":{"type":"string","maxLength":1000,"description":"What these files show and why they support the challenge."},"file_ids":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"Identifiers returned when the evidence files were uploaded."}}},"AdminPaymentDisputeChallenge":{"type":"object","description":"A challenge submission. Only disputes awaiting a response at the RFI or chargeback stage can be challenged, and at least one evidence document is required.\n","additionalProperties":false,"required":["reason","documents"],"properties":{"reason":{"$ref":"#/components/schemas/AdminPaymentDisputeChallengeReason"},"product_description":{"type":"string","maxLength":1000,"description":"What the cardholder purchased. Applies at the chargeback stage."},"refund_refusal_reason":{"type":"string","maxLength":1000,"description":"Why a refund is being refused. Applies at the chargeback stage."},"customer_info":{"$ref":"#/components/schemas/AdminPaymentDisputeCustomerInfo"},"documents":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AdminPaymentDisputeDocument"},"description":"Evidence backing the challenge."}}},"AdminPaymentDisputeEvidenceFileCreate":{"type":"object","description":"An evidence file to attach to a dispute challenge.","additionalProperties":false,"required":["filename","content_type","data"],"properties":{"filename":{"type":"string","minLength":1,"maxLength":50,"description":"Name of the file, at most 50 characters."},"content_type":{"type":"string","minLength":1,"enum":["application/pdf","image/jpeg","image/png","image/tiff"],"description":"Media type of the file."},"data":{"type":"string","format":"byte","minLength":1,"maxLength":13981016,"x-sensitive":true,"description":"File contents, base64-encoded. The 13981016-character limit is the encoded size of a 10 MB file, so anything larger is rejected before it is decoded.\n"}}},"AdminPaymentDisputeEvidenceFile":{"type":"object","description":"A stored evidence file, referenced by identifier when challenging.","additionalProperties":false,"required":["file_id","filename","size"],"properties":{"file_id":{"type":"string","minLength":1,"readOnly":true,"description":"Identifier to list under a challenge document."},"filename":{"type":"string","minLength":1,"readOnly":true,"description":"Name of the stored file."},"size":{"type":"integer","format":"int64","readOnly":true,"description":"Stored size in bytes."}}},"AdminPaymentCustomerResolveRequest":{"type":"object","additionalProperties":false,"required":["external_ids"],"properties":{"external_ids":{"type":"array","minItems":1,"maxItems":200,"items":{"type":"string","minLength":1},"description":"Airwallex customer external ids to resolve to Bird organizations."}}},"AdminPaymentCustomerResolution":{"type":"object","additionalProperties":false,"required":["external_id","organization_id"],"properties":{"external_id":{"type":"string","minLength":1,"readOnly":true,"description":"The Airwallex customer external id, as passed in the request."},"organization_id":{"$ref":"#/components/schemas/OrganizationID","readOnly":true,"description":"The Bird organization the external id resolved to."}}},"AdminPaymentCustomerOrgResolution":{"type":"object","additionalProperties":false,"required":["resolutions"],"properties":{"resolutions":{"type":"array","readOnly":true,"description":"One entry per external id resolved to an org in this region. Unresolved ids are omitted.","items":{"$ref":"#/components/schemas/AdminPaymentCustomerResolution"}}}},"PlanEntitlementID":{"type":"string","minLength":1,"pattern":"^ent_[0-9a-hjkmnp-tv-z]{26}$","example":"ent_01krdgeqcxet5s7t44vh8rt9mg"},"AdminBillingPlanEntitlement":{"type":"object","additionalProperties":false,"required":["id","plan_product_id","entitled_product_id","effective_from","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PlanEntitlementID"},"plan_product_id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"entitled_product_id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"included_units":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"Units included per billing period. Null means unlimited."},"effective_from":{"type":"string","minLength":1,"format":"date","readOnly":true},"effective_to":{"type":["string","null"],"format":"date","readOnly":true,"description":"Null means the entitlement is open-ended."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingPlanEntitlementList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingPlanEntitlement"}}}},"AdminBillingPlanEntitlementCreate":{"type":"object","additionalProperties":false,"required":["entitled_product_id","effective_from"],"properties":{"entitled_product_id":{"description":"Product granted to subscribers of this plan.","$ref":"#/components/schemas/ProductID"},"included_units":{"type":["integer","null"],"format":"int64","description":"Units included per billing period. Null means unlimited."},"effective_from":{"type":"string","minLength":1,"format":"date","example":"2025-01-01"}}},"AdminBillingOrgPlanEntitlement":{"allOf":[{"$ref":"#/components/schemas/AdminBillingPlanEntitlement"},{"type":"object","additionalProperties":false,"required":["organization_id"],"properties":{"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"}}}]},"AdminBillingOrgPlanEntitlementList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingOrgPlanEntitlement"}}}},"AdminBillingOrgPlanEntitlementCreate":{"type":"object","additionalProperties":false,"required":["organization_id","entitled_product_id","effective_from"],"properties":{"organization_id":{"description":"Organization this override applies to.","$ref":"#/components/schemas/OrganizationID"},"entitled_product_id":{"description":"Product being granted.","$ref":"#/components/schemas/ProductID"},"included_units":{"type":["integer","null"],"format":"int64","description":"Units included per billing period. Null means unlimited."},"effective_from":{"type":"string","minLength":1,"format":"date"}}},"PlanQuotaID":{"type":"string","minLength":1,"pattern":"^pq_[0-9a-hjkmnp-tv-z]{26}$","example":"pq_01krdgeqcxet5s7t44vh8rt9mg"},"AdminBillingPlanQuota":{"type":"object","additionalProperties":false,"description":"A per-plan quota limit, effective between effective_from and effective_to.","required":["id","plan_product_id","quota_key","value","effective_from","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PlanQuotaID"},"plan_product_id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"quota_key":{"type":"string","minLength":1,"readOnly":true,"description":"Stable quota key this row applies to.","example":"email_domains_max"},"value":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"The limit value."},"effective_from":{"type":"string","minLength":1,"format":"date","readOnly":true,"description":"Date from which this limit applies, inclusive."},"effective_to":{"type":["string","null"],"format":"date","readOnly":true,"description":"Date until which this limit applies, exclusive. Null means open-ended."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingPlanQuotaList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingPlanQuota"}}}},"AdminBillingPlanQuotaCreate":{"type":"object","additionalProperties":false,"required":["quota_key","value","effective_from"],"properties":{"quota_key":{"type":"string","minLength":1,"description":"The quota key to set. Must be a registered, adjustable quota key.","example":"email_domains_max"},"value":{"type":"integer","format":"int64","minimum":0,"description":"The new limit value for this plan."},"effective_from":{"type":"string","minLength":1,"format":"date","description":"The date from which this limit takes effect. Must be after any existing limit for the same quota key on this plan."}}},"PlanRateLimitPolicyID":{"type":"string","minLength":1,"pattern":"^prlp_[0-9a-hjkmnp-tv-z]{26}$","example":"prlp_01krdgeqcxet5s7t44vh8rt9mg"},"AdminBillingPlanRateLimitPolicy":{"type":"object","readOnly":true,"additionalProperties":false,"description":"A per-plan customer rate-limit policy value with an effective date range.","required":["id","plan_product_id","policy_key","rate","effective_from","created_at"],"properties":{"id":{"$ref":"#/components/schemas/PlanRateLimitPolicyID"},"plan_product_id":{"$ref":"#/components/schemas/ProductID"},"policy_key":{"type":"string","minLength":1},"rate":{"type":"integer","minimum":1,"maximum":10000000},"effective_from":{"type":"string","minLength":1,"format":"date"},"effective_to":{"type":["string","null"],"format":"date"},"created_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminBillingPlanRateLimitPolicyList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingPlanRateLimitPolicy"}}}},"AdminBillingPlanRateLimitPolicyCreate":{"type":"object","additionalProperties":false,"required":["policy_key","rate","effective_from"],"properties":{"policy_key":{"type":"string","minLength":1,"pattern":"^[a-z][a-z0-9_]*$"},"rate":{"type":"integer","minimum":1,"maximum":10000000},"effective_from":{"type":"string","minLength":1,"format":"date"}}},"AdminBillingPlanEntitlementExpire":{"type":"object","additionalProperties":false,"properties":{"effective_to":{"type":"string","format":"date","description":"Date on which the entitlement closes. Defaults to today if omitted."}}},"AdminBillingPlanQuotaExpire":{"type":"object","additionalProperties":false,"required":["effective_to"],"properties":{"effective_to":{"type":"string","minLength":1,"format":"date","description":"The date on which this quota limit stops applying."}}},"AdminBillingPlanRateLimitExpire":{"type":"object","additionalProperties":false,"required":["effective_to"],"properties":{"effective_to":{"type":"string","minLength":1,"format":"date","description":"The date on which this rate limit stops applying."}}},"PlanConfigID":{"type":"string","minLength":1,"pattern":"^pc_[0-9a-hjkmnp-tv-z]{26}$","example":"pc_01krdgeqcxet5s7t44vh8rt9mg"},"AdminBillingPlanConfig":{"type":"object","additionalProperties":false,"required":["id","product_id","tier","weight","default_interval","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PlanConfigID"},"product_id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"tier":{"type":"string","minLength":1,"example":"starter","description":"Plan tier label (e.g. free, starter, professional, enterprise)."},"weight":{"type":"integer","format":"int32","readOnly":true,"description":"Sort key for plan ordering; higher is more premium.","example":200},"default_interval":{"type":"string","minLength":1,"enum":["month","year"],"description":"Default billing cadence for subscriptions to this plan."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingPlanConfigUpdate":{"type":"object","additionalProperties":false,"properties":{"tier":{"type":"string","minLength":1,"description":"Plan tier label (e.g. free, starter, professional, enterprise). Must be provided on every upsert; omitting it returns a 422 `validation_error`."},"weight":{"type":"integer","format":"int32","description":"Sort key for plan ordering; higher is more premium. When omitted, resets to `0`.","example":200},"default_interval":{"type":"string","enum":["month","year"],"description":"Default billing cadence for subscriptions to this plan. When omitted, resets to `month`."}}},"UnitSubscriptionConfigID":{"type":"string","minLength":1,"pattern":"^usc_[0-9a-hjkmnp-tv-z]{26}$","example":"usc_01krdgeqcxet5s7t44vh8rt9mg"},"AdminBillingUnitSubscriptionConfig":{"type":"object","additionalProperties":false,"required":["id","product_id","billing_interval","billing_interval_count","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/UnitSubscriptionConfigID"},"product_id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"billing_interval":{"type":"string","minLength":1,"enum":["month","year"],"description":"Unit the rate-card price is quoted in. A `month` interval means the rate card holds a monthly price; `year` means it holds an annual one, which is how a discounted annual rate is configured rather than dividing it into months.","example":"month"},"billing_interval_count":{"type":"integer","minimum":1,"maximum":36,"description":"Number of intervals one charge covers. With a `month` interval, 6 bills six months of the monthly rate at once; with `year`, 1 bills the annual rate once a year. Subscriptions keep the cadence they were created with, so a change here applies only to new ones.","example":6},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingRateCard":{"type":"object","additionalProperties":false,"required":["id","product_id","name","status","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/RateCardID"},"product_id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"name":{"type":"string","minLength":1,"example":"Standard Email Pricing"},"description":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/AdminBillingRateCardStatus","readOnly":true,"example":"active"},"plan_product_id":{"readOnly":true,"description":"If set, this rate card applies to subscribers of the given plan. Null for catalog and org-specific rate cards.","oneOf":[{"$ref":"#/components/schemas/ProductID"},{"type":"null"}]},"organization_id":{"readOnly":true,"description":"If set, this rate card is specific to that organization. Null for catalog (default) rate cards.","oneOf":[{"$ref":"#/components/schemas/OrganizationID"},{"type":"null"}]},"parent_id":{"readOnly":true,"description":"ID of the catalog rate card this org card overrides.","oneOf":[{"$ref":"#/components/schemas/RateCardID"},{"type":"null"}]},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingRateCardList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingRateCard"}}}},"AdminBillingRateCardCreate":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"example":"Standard Email Pricing"},"description":{"type":"string","example":"Default catalog pricing for email sends."},"plan_product_id":{"description":"If set, creates a plan-specific rate card that applies to subscribers of this plan. Mutually exclusive with organization_id.","$ref":"#/components/schemas/ProductID"},"organization_id":{"description":"If set, creates an org-specific rate card for this organization.","$ref":"#/components/schemas/OrganizationID"},"parent_id":{"description":"Catalog rate card ID this org card overrides. Required when organization_id is set.","$ref":"#/components/schemas/RateCardID"},"duplicate_from_version_id":{"description":"If set, copies all prices from this version into the new card's first draft version.","$ref":"#/components/schemas/RateCardVersionID"}}},"AdminBillingRateCardVersion":{"type":"object","additionalProperties":false,"required":["id","rate_card_id","version","status","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/RateCardVersionID"},"rate_card_id":{"readOnly":true,"$ref":"#/components/schemas/RateCardID"},"version":{"type":"integer","readOnly":true,"example":1},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["draft","active","superseded"],"example":"draft"},"effective_from":{"type":["string","null"],"format":"date","example":"2025-01-01"},"effective_to":{"type":["string","null"],"format":"date","example":"2025-12-31"},"created_by":{"readOnly":true,"description":"ID of the admin user who created this version.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"approved_by":{"readOnly":true,"description":"ID of the admin user who activated this version.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"approved_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this version was activated."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingRateCardVersionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingRateCardVersion"}}}},"AdminBillingRateCardVersionCreate":{"type":"object","additionalProperties":false,"properties":{"effective_from":{"type":"string","format":"date","description":"Date from which this version becomes effective. Leave empty for immediate effect.","example":"2025-01-01"}}},"PriceID":{"type":"string","minLength":1,"pattern":"^pri_[0-9a-hjkmnp-tv-z]{26}$","example":"pri_01krdgeqcxet5s7t44vh8rt9mg"},"BillingPriceIncrement":{"type":"string","pattern":"^[1-9][0-9]{0,2}/[1-9][0-9]{0,2}$","description":"Billing increment as `initial/continue` whole seconds, both 1–999, for example, '60/6' bills a 60-second minimum then in 6-second steps.\n","example":"60/6"},"AdminBillingPrice":{"type":"object","additionalProperties":false,"required":["id","rate_card_version_id","dimensions","unit_price","currency_code","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PriceID"},"rate_card_version_id":{"readOnly":true,"$ref":"#/components/schemas/RateCardVersionID"},"dimensions":{"type":"object","additionalProperties":{"type":"string"},"example":{"channel":"sms","country":"us"}},"unit_price":{"type":"string","minLength":1,"description":"Unit price as a decimal string in the rate card's currency (e.g. \"0.10\").","example":"0.10"},"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"increment":{"description":"Billing increment, or null for non-session prices.","oneOf":[{"$ref":"#/components/schemas/BillingPriceIncrement"},{"type":"null"}]},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingRateCardVersionDetail":{"allOf":[{"$ref":"#/components/schemas/AdminBillingRateCardVersion"},{"type":"object","required":["prices"],"properties":{"prices":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingPrice"}}}}]},"AdminBillingPriceCreate":{"type":"object","additionalProperties":false,"required":["dimensions","unit_price","currency_code"],"properties":{"dimensions":{"type":"object","additionalProperties":{"type":"string"},"example":{"channel":"sms","country":"us"}},"unit_price":{"type":"string","minLength":1,"description":"Unit price as a decimal string in the rate card's currency (e.g. \"0.10\").","example":"0.10"},"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"increment":{"$ref":"#/components/schemas/BillingPriceIncrement"}}},"AdminBillingRateCardImportRequest":{"type":"object","additionalProperties":false,"required":["csv"],"properties":{"csv":{"type":"string","minLength":1,"description":"The CSV content to import. The first column is the currency (ISO 4217), followed by one column per pricing dimension, an increment column for session-based products, and a final unit price column with the decimal rate in that row's currency. The header row must name every dimension of the product exactly.\n","example":"currency,country,unit_price\nUSD,us,0.0075\n"}}},"AdminBillingRateCardImportResult":{"type":"object","additionalProperties":false,"required":["inserted","overwritten","total"],"properties":{"inserted":{"type":"integer","description":"Number of new prices added from the file.","example":3},"overwritten":{"type":"integer","description":"Number of existing prices updated to match the file.","example":12},"total":{"type":"integer","description":"Total number of rows applied from the file.","example":15}}},"AdminBillingPriceUpdate":{"type":"object","additionalProperties":false,"properties":{"unit_price":{"type":"string","minLength":1,"description":"Unit price as a decimal string in the rate card's currency (e.g. \"0.10\")."},"increment":{"description":"Billing increment. Send null to clear.","oneOf":[{"$ref":"#/components/schemas/BillingPriceIncrement"},{"type":"null"}]}}},"AdminBillingCurrencyList":{"type":"object","additionalProperties":false,"required":["currencies"],"properties":{"currencies":{"type":"array","items":{"$ref":"#/components/schemas/CurrencyCode"},"example":["EUR","USD"]}}},"AdminBillingOrgRateCardList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingRateCard"}}}},"MinimumCommitID":{"type":"string","minLength":1,"pattern":"^mmc_[0-9a-hjkmnp-tv-z]{26}$","example":"mmc_01krdgeqcxet5s7t44vh8rt9mg"},"MinimumCommitPeriodType":{"type":"string","minLength":1,"enum":["monthly","annual"],"description":"Billing period of the commitment. The `monthly` period means the committed spend applies to each monthly billing period. We charge any shortfall at the end of each month. The `annual` period means the committed spend applies to a single 12-month window starting at `starts_at`. We charge any shortfall once, at the end of that window.\n"},"AdminBillingMinimumCommit":{"type":"object","additionalProperties":false,"required":["id","organization_id","commitment_net","currency_code","period_type","starts_at","status","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/MinimumCommitID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"commitment_net":{"type":"string","minLength":1,"readOnly":true,"description":"Committed NET spend per period (excluding tax) as a decimal string in major currency units.","example":"25000"},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code. Always equals the organization's wallet currency.","example":"USD"},"period_type":{"readOnly":true,"$ref":"#/components/schemas/MinimumCommitPeriodType"},"starts_at":{"type":"string","minLength":1,"format":"date","readOnly":true},"ends_at":{"type":["string","null"],"format":"date","readOnly":true,"description":"Exclusive end date. Null means the commit is open-ended."},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["active","expired","terminated"],"example":"active"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminBillingMinimumCommitList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminBillingMinimumCommit"}}}},"AdminBillingMinimumCommitCreate":{"type":"object","additionalProperties":false,"required":["commitment_net","currency_code","period_type","starts_at"],"properties":{"commitment_net":{"type":"string","minLength":1,"description":"Committed NET spend per period (excluding tax) as a positive decimal string in major currency units.","example":"25000"},"currency_code":{"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code. Must equal the organization's wallet currency.","example":"USD"},"period_type":{"$ref":"#/components/schemas/MinimumCommitPeriodType"},"starts_at":{"type":"string","minLength":1,"format":"date"},"ends_at":{"type":"string","format":"date","description":"Exclusive end date; when set, it must be after `starts_at` and in the future. Required when `period_type` is `annual`, and must then be exactly 12 months after `starts_at` (Bird charges the annual shortfall once, at the end of that window). Only `monthly` commits may omit it to run open-ended."}}},"AdminBillingPlanAssign":{"type":"object","additionalProperties":false,"required":["plan_product_id","reason"],"properties":{"plan_product_id":{"description":"ID of the plan product to assign to the organization.","$ref":"#/components/schemas/ProductID"},"reason":{"type":"string","minLength":1,"maxLength":500,"description":"Audit reason for the plan assignment (stored in the activity record)."},"commence_on":{"type":"string","minLength":1,"format":"date","description":"The day the new plan starts governing. Omit it to take the date the plan ladder\nimplies: today for a move up, the end of the current period for a move down. Today\ncommences the plan now; a later date schedules it and charges nothing until that day.\nA date before today is rejected with 422. When the organization already holds a plan,\na date past the end of its current billing period is rejected too, since it would\nleave the outgoing plan's renewals unpriced; a first assignment has no period to\nbound and accepts any future day.\n\nNaming a date re-anchors the term to it, which is also what makes the change cost a\nfull new period rather than the difference between the two plans.\n\nIt does not apply when the organization already holds the plan being assigned: that\nis a reprice, which takes effect at the next renewal, so a date is rejected with 422.\n\nA scheduled change is charged on the day it commences and is not checked for\naffordability now. Fund the wallet before that day, or the charge overdraws it.\n"},"unit_price":{"description":"Replaces the plan's rate-card price, for this period and every renewal after it. Omit it to resolve the price from the rate card: the organization's own, falling back to the product's list card. This is the amount for the WHOLE period, so on a yearly plan it is the annual figure. Its currency must match the organization's wallet, and a negative amount is rejected with 422.","allOf":[{"$ref":"#/components/schemas/Money"}]},"description":{"type":"string","minLength":1,"maxLength":500,"description":"A staff note stored on the subscription: the contract this came from, why this price. Not a place to list the entitlements, which are their own records and can be read directly."}}},"WalletPendingOperationID":{"type":"string","minLength":1,"pattern":"^wpo_[0-9a-hjkmnp-tv-z]{26}$","example":"wpo_01krdgeqcxet5s7t44vh8rt9mg"},"AdminBillingPlanAssignResult":{"type":"object","additionalProperties":false,"required":["outcome","subscription"],"properties":{"outcome":{"type":"string","minLength":1,"readOnly":true,"description":"Outcome of the assignment. `created` — the organization had no plan and one was created. `upgraded` — the plan change applied immediately. `scheduled` — a downgrade that takes effect at the end of the current billing period. `pending_topup` — the wallet was short and a top-up against the organization's saved card is processing. `requires_topup` — the wallet was short and no saved card is available, so the change is parked until funds arrive. `repriced` — the organization already held this plan and its recurring price was updated to the negotiated rate, effective at the next renewal. `unchanged` — the organization already held this plan at the resolved price; nothing was modified.\n","enum":["created","upgraded","scheduled","pending_topup","requires_topup","repriced","unchanged"],"example":"repriced"},"subscription":{"$ref":"#/components/schemas/AdminBillingSubscription"},"pending_operation_id":{"readOnly":true,"description":"Identifier of the pending upgrade operation. Present for `pending_topup` and `requires_topup`.","oneOf":[{"$ref":"#/components/schemas/WalletPendingOperationID"},{"type":"null"}]},"shortfall_amount":{"type":["object","null"],"readOnly":true,"description":"Amount still needed to fund the upgrade. Present for `pending_topup` and `requires_topup`.","allOf":[{"$ref":"#/components/schemas/Money"}]}}},"BillingPlanChangePreview":{"type":"object","additionalProperties":false,"description":"Quote for a plan change that shows charges and renewal details before the change is applied.","required":["is_upgrade","currency_code","upgrade_charge","tax_amount","tax_rate","overage_charge","gross_amount","renewal_amount","renewal_date","effective_date"],"properties":{"is_upgrade":{"type":"boolean","readOnly":true,"description":"Which way the change moves on the plan ladder: `true` when the target plan ranks above the current one. It does not say whether a charge applies. Read `gross_amount` for that: a change commencing today can owe a charge whichever way it moves, and can owe nothing when the price difference is zero."},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"Currency used for all amounts in this preview."},"upgrade_charge":{"readOnly":true,"description":"Net plan charge due today, before tax: the difference between the two plan prices when the term keeps its billing anchor, or the full new period when the change re-anchors. `null` when nothing is charged for the plan today.","oneOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"tax_amount":{"readOnly":true,"description":"Tax applied to `upgrade_charge`. `null` when `upgrade_charge` is `null`.","oneOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"tax_rate":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Decimal VAT/GST rate applied (for example, `0.21`). `null` when `upgrade_charge` is `null`.","example":"0.21"},"overage_charge":{"readOnly":true,"description":"Gross overage billed for the period this change closes, when it starts a fresh period rather than keeping the current billing anchor. `null` when the anchor is kept or there is no overage to bill.","oneOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"gross_amount":{"readOnly":true,"description":"Total charged today: `upgrade_charge` + `tax_amount` + `overage_charge`. `null` when nothing is charged today.","oneOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"renewal_amount":{"readOnly":true,"$ref":"#/components/schemas/Money","description":"Gross monthly amount for the new plan at next renewal (net plan price + tax)."},"effective_date":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the new plan starts governing. Now for a change that applies immediately, or the end of the current period for one that is deferred; where the operation accepts a requested commencement day, that day."},"renewal_date":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the period starting at `effective_date` itself renews. A change that keeps the current billing anchor renews at the current period end; one that starts a fresh period renews one full period after `effective_date`."}}},"AdminBillingOpeningInvoiceCreate":{"type":"object","additionalProperties":false,"description":"Request body for issuing an opening invoice against an organization's uninvoiced subscription charges.","required":["invoice_date"],"properties":{"invoice_date":{"type":"string","minLength":1,"format":"date","description":"The invoice date, which also anchors the payment due date. Set it to the day the charges were made, which may be earlier than today; a date in the future is rejected with 422. The calendar month it falls in selects which subscription charges the invoice covers."}}},"CostBookID":{"type":"string","minLength":1,"pattern":"^cbk_[0-9a-hjkmnp-tv-z]{26}$","example":"cbk_01krdgeqcxet5s7t44vh8rt9mg"},"CostBookVersionID":{"type":"string","minLength":1,"pattern":"^cbv_[0-9a-hjkmnp-tv-z]{26}$","example":"cbv_01krdgeqcxet5s7t44vh8rt9mg"},"AdminCostBook":{"type":"object","additionalProperties":false,"required":["id","product_id","name","max_age_days","fresh","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/CostBookID"},"product_id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"name":{"type":"string","minLength":1,"example":"SMS route costs"},"max_age_days":{"type":"integer","description":"Freshness policy in days. Pricing operations that depend on this book warn or fail when its active version is older than this.","example":45},"active_version_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/CostBookVersionID"},{"type":"null"}]},"active_version":{"type":["integer","null"],"readOnly":true,"description":"Version number of the currently active version, or null when no version has been activated yet.","example":3},"activated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the currently active version was activated."},"fresh":{"type":"boolean","readOnly":true,"description":"Whether the active version is within the book's freshness policy. False when no version is active."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminCostBookList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminCostBook"}}}},"AdminCostBookCreate":{"type":"object","additionalProperties":false,"required":["product_id","name"],"properties":{"product_id":{"$ref":"#/components/schemas/ProductID"},"name":{"type":"string","minLength":1,"example":"SMS route costs"},"max_age_days":{"type":"integer","minimum":1,"description":"Freshness policy in days. Defaults to 45 when omitted.","example":45}}},"AdminCostBookVersion":{"type":"object","additionalProperties":false,"required":["id","cost_book_id","version","status","created_by","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/CostBookVersionID"},"cost_book_id":{"readOnly":true,"$ref":"#/components/schemas/CostBookID"},"version":{"type":"integer","readOnly":true,"example":3},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["draft","active","superseded"],"example":"active"},"source_note":{"type":"string","description":"Free-text provenance note supplied at import, such as the rate load the file came from.","example":"DC rate load 2026-06-29"},"created_by":{"readOnly":true,"$ref":"#/components/schemas/UserID"},"approved_by":{"readOnly":true,"description":"ID of the admin user who activated this version.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"activated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this version was activated."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminCostBookDetail":{"type":"object","additionalProperties":false,"required":["book","versions"],"properties":{"book":{"$ref":"#/components/schemas/AdminCostBook"},"versions":{"type":"array","description":"All versions of the book, newest first.","items":{"$ref":"#/components/schemas/AdminCostBookVersion"}}}},"AdminCostBookImportRequest":{"type":"object","additionalProperties":false,"required":["csv"],"properties":{"csv":{"type":"string","minLength":1,"x-sensitive":true,"description":"The CSV content to import. The first column is the currency (ISO 4217), followed by one column per pricing dimension of the product, a unit_cost column with the decimal cost in that row's currency, and a final source column with the row's provenance. Session-based products may add an optional trailing increment column carrying the carrier's billing increment per row ('{initial_s}/{continuation_s}', blank for per-second rows). The header row must name every dimension of the product exactly. Each import creates a new draft version wholesale.\n","example":"currency,country_code,unit_cost,source\nEUR,RO,0.0165,DC direct\n"},"source_note":{"type":"string","description":"Free-text provenance note for the new version, such as the rate load the file came from.","example":"DC rate load 2026-06-29"}}},"AdminCostBookImportResult":{"type":"object","additionalProperties":false,"required":["version","rows"],"properties":{"version":{"$ref":"#/components/schemas/AdminCostBookVersion"},"rows":{"type":"integer","description":"Number of cost rows imported into the new draft version.","example":152}}},"CostPriceID":{"type":"string","minLength":1,"pattern":"^cpr_[0-9a-hjkmnp-tv-z]{26}$","example":"cpr_01krdgeqcxet5s7t44vh8rt9mg"},"AdminCostBookPrice":{"type":"object","additionalProperties":false,"required":["id","currency_code","dimensions","unit_cost"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/CostPriceID"},"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"dimensions":{"type":"object","additionalProperties":{"type":"string"},"example":{"country_code":"RO"}},"unit_cost":{"type":"string","minLength":1,"x-sensitive":true,"description":"Unit cost as a decimal string in the row's currency (e.g. \"0.0165\").","example":"0.0165"},"source":{"type":"string","description":"Carrier or route provenance for the cost row.","example":"DC direct"},"increment":{"description":"The carrier's billing increment for session-product cost rows, or null for non-session products and rows costed per exact second.","oneOf":[{"$ref":"#/components/schemas/BillingPriceIncrement"},{"type":"null"}]},"pct_change":{"type":["number","null"],"readOnly":true,"description":"Percent change of the unit cost against the matching row in the previously activated version. Null when the prior version has no matching row.","example":-3.5}}},"AdminCostBookVersionDetail":{"type":"object","additionalProperties":false,"required":["version","prices"],"properties":{"version":{"$ref":"#/components/schemas/AdminCostBookVersion"},"use_increment":{"type":"boolean","readOnly":true,"description":"Whether the book's product is session-based, i.e. whether cost rows may carry a billing increment."},"prices":{"type":"array","description":"All cost rows of the version, with the percent change against the previously activated version.","items":{"$ref":"#/components/schemas/AdminCostBookPrice"}}}},"MarginFloorDialID":{"type":"string","minLength":1,"pattern":"^mfd_[0-9a-hjkmnp-tv-z]{26}$","example":"mfd_01krdgeqcxet5s7t44vh8rt9mg"},"AdminMarginFloorDial":{"type":"object","additionalProperties":false,"description":"One recorded change to a product's margin floor. Rows are appended, never edited, so the floor a past quote was judged against stays readable.\n","required":["id","floor_bps","model_floor_bps","status","requested_by","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/MarginFloorDialID"},"floor_bps":{"type":"integer","description":"The floor this change sets, in basis points of the offered price.","example":400},"model_floor_bps":{"type":"integer","description":"The cost model's own floor at the moment the change was asked for. What made the change a concession, or not.","example":500},"reason":{"type":"string","description":"Why the floor was moved. Mandatory when the change loosens the model floor, absent otherwise.","example":"Matching a competing bid on EU SMS pricing for the Riverstone renewal"},"status":{"type":"string","minLength":1,"description":"`active` is the current dial; `pending_approval` is waiting on a second signature because it loosens the model floor; `rejected` was declined; `superseded` was replaced by a later change. An `active` dial past its `expires_at` has lapsed and no longer moves the floor.\n","enum":["active","pending_approval","rejected","superseded"],"example":"active"},"requested_by":{"readOnly":true,"$ref":"#/components/schemas/UserID"},"decided_by":{"description":"The approver who cleared or declined the change. Null until decided, and on a change that never needed approval.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"decision_note":{"type":"string","description":"What the approver said.","example":"Approved for this renewal cycle only"},"decided_at":{"type":["string","null"],"format":"date-time","readOnly":true},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the dial stops moving the floor. Always set on a concession, because a floor below the model floor has to lapse; optional on a floor at or above the model floor, which may stand until it is replaced.\n"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminProductMarginFloor":{"type":"object","additionalProperties":false,"description":"One product's whole floor position: what the cost model says, what an operator dialled on top of it, which of the two is in force, and any change still waiting on a decision.\n","required":["product_id","product_name","product_slug","effective_floor_bps"],"properties":{"product_id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"product_name":{"type":"string","minLength":1,"example":"SMS"},"product_slug":{"type":"string","minLength":1,"example":"sms"},"model_floor_bps":{"type":["integer","null"],"description":"The cost model's floor. Null when finance has never set one, in which case every priced line clears the floor.","example":500},"effective_floor_bps":{"type":"integer","description":"The floor quotes are actually judged against: the dial in force when there is one, else the model floor, else 0.","example":400},"active_dial":{"description":"The product's current dial. Still reported once it has lapsed, so the operator can see what expired and when. Check `expires_at` against `effective_floor_bps` to tell a live dial from a lapsed one.\n","oneOf":[{"$ref":"#/components/schemas/AdminMarginFloorDial"},{"type":"null"}]},"pending_dial":{"description":"A requested change waiting on a second signature. Present only for a change that loosens the model floor.","oneOf":[{"$ref":"#/components/schemas/AdminMarginFloorDial"},{"type":"null"}]}}},"AdminMarginFloorDialCounts":{"type":"object","additionalProperties":false,"description":"The pending floor-change counters for the signed-in staff member. Pending and needs-me are separate because they answer different questions: one is the queue, whoever may clear it, the other is this caller's workload. A caller can never decide a change they requested themselves, and deciding needs the deal-approver grant.\n","required":["pending_total","needs_me"],"properties":{"pending_total":{"type":"integer","description":"Floor changes waiting on a second signature, whoever may clear them.","example":2},"needs_me":{"type":"integer","description":"Pending changes this caller can decide right now.","example":1}}},"AdminCostBookMarginFloor":{"type":"object","additionalProperties":false,"required":["product_id","min_margin_bps","updated_by","updated_at"],"properties":{"product_id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"min_margin_bps":{"type":"integer","description":"Minimum margin in basis points of the offered price. Offers below this floor need a second approver.","example":500},"updated_by":{"readOnly":true,"$ref":"#/components/schemas/UserID"},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminCostBookMarginFloorUpdate":{"type":"object","additionalProperties":false,"required":["min_margin_bps"],"properties":{"min_margin_bps":{"type":"integer","minimum":0,"description":"Minimum margin in basis points of the offered price.","example":500}}},"AdminMarginFloorDialRequest":{"type":"object","additionalProperties":false,"description":"Set a product's margin floor. Raising it, or restoring it to the model floor, takes effect immediately. Lowering it below the model floor is a concession: it needs a reason and an expiry date, and routes to the deal approvers instead.\n","required":["floor_bps"],"properties":{"floor_bps":{"type":"integer","minimum":0,"maximum":10000,"description":"The floor to set, in basis points of the offered price.","example":400},"reason":{"type":"string","maxLength":500,"description":"Why the floor moves. Required when the value is below the model floor.","example":"Matching a competing bid on EU SMS pricing for the Riverstone renewal"},"expires_at":{"type":"string","format":"date-time","description":"When the floor reverts to the model floor. Required when the value is below the model floor, so a concession cannot outlive the deal it was granted for; optional above it, where an omitted date means the floor stands until it is replaced. Must be in the future.\n","example":"2026-10-01T00:00:00Z"}}},"AdminMarginFloorDialDecision":{"type":"object","additionalProperties":false,"description":"The second signature on a floor change that loosens the model floor.","required":["approve"],"properties":{"approve":{"type":"boolean","description":"True clears the change and puts it in force; false declines it and leaves the previous floor standing.","example":true},"note":{"type":"string","maxLength":500,"description":"What the approver wants on the record. Required when declining.","example":"Approved for this renewal cycle only"}}},"AdminSalesDocumentKind":{"type":"string","enum":["proposal","mutual_action_plan","outreach"],"minLength":1},"AdminSalesDocumentState":{"type":"string","enum":["draft","in_review","changes_requested","approved","archived"],"minLength":1,"description":"draft: editable authored content. in_review: content is pinned and cannot be edited. changes_requested: returned content may be edited or resubmitted. approved: independently reviewed content; saving creates a new draft. archived: read-only content that cannot receive comments."},"AdminSalesDocumentCategory":{"type":"string","enum":["studio"],"minLength":1,"description":"Sales Studio proposals, mutual action plans and outreach documents."},"SalesDocumentID":{"type":"string","minLength":1,"pattern":"^sdoc_[0-9a-hjkmnp-tv-z]{26}$","example":"sdoc_01krdgeqcxet5s7t44vh8rt9mg"},"SalesDocumentReviewID":{"type":"string","minLength":1,"pattern":"^sdrw_[0-9a-hjkmnp-tv-z]{26}$","example":"sdrw_01krdgeqcxet5s7t44vh8rt9mg"},"ApprovalRequestID":{"type":"string","minLength":1,"pattern":"^apr_[0-9a-hjkmnp-tv-z]{26}$","example":"apr_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSalesDocumentBase":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","properties":{"id":{"$ref":"#/components/schemas/SalesDocumentID"},"deal_id":{"$ref":"#/components/schemas/DealID"},"quote_id":{"$ref":"#/components/schemas/DealQuoteID"},"kind":{"$ref":"#/components/schemas/AdminSalesDocumentKind"},"title":{"type":"string","maxLength":200,"minLength":1},"owner_user_id":{"$ref":"#/components/schemas/UserID"},"state":{"$ref":"#/components/schemas/AdminSalesDocumentState"},"current_revision":{"type":"integer","minimum":1,"maximum":2147483647},"created_by":{"$ref":"#/components/schemas/UserID"},"updated_by":{"$ref":"#/components/schemas/UserID"},"review_revision":{"type":"integer","minimum":1,"maximum":2147483647,"description":"Content revision pinned by the most recent review request. May remain after withdrawal or a decision; absent before review or after editing. It is not an active-review identity."},"review_quote_fingerprint":{"type":"string","maxLength":64,"minLength":0,"description":"SHA-256 fingerprint of quote inputs pinned by the most recent review request. Absent for unquoted documents or when no quote review pin exists. May remain after withdrawal or a decision."},"review_stale":{"type":"boolean","description":"True when the attached quote differs from the retained review_quote_fingerprint, including after a decision or withdrawal. Computed on list and detail; false when no quote review pin exists. Does not indicate an active review by itself."},"current_review_id":{"$ref":"#/components/schemas/SalesDocumentReviewID","description":"ID of the active request_review event. Present only while a current review request exists; cleared by withdrawal, completed review, archive or edits. Partial shared approval votes retain it. Approve and request_changes must supply this exact ID as expected_review_id."},"approval_request_id":{"$ref":"#/components/schemas/ApprovalRequestID","description":"Most recent shared approval request. Retained after decisions, withdrawal and edits. Absent for documents that have only used legacy reviews."},"viewer_has_authored":{"type":"boolean","description":"Whether the authenticated caller authored any immutable revision of this document. Derived from full revision history, not the latest updater or review actor. True callers cannot independently review this document."}},"required":["id","deal_id","kind","title","owner_user_id","state","current_revision","created_by","updated_by","review_stale","viewer_has_authored"]}],"x-sensitive":true},"AdminSalesDocumentSummary":{"readOnly":true,"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AdminSalesDocumentBase"}],"x-sensitive":true},"AdminSalesDocumentList":{"allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSalesDocumentSummary"}}},"required":["data"]}],"unevaluatedProperties":false,"x-sensitive":true},"AdminSalesDocumentBlockKind":{"type":"string","enum":["heading","paragraph","bullets","slide","clause"],"minLength":1},"AdminSalesDocumentBlock":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string","maxLength":100,"minLength":1,"description":"Nonblank identifier, unique within the document. The service limits this value to 100 UTF-8 bytes; maxLength also bounds characters."},"kind":{"$ref":"#/components/schemas/AdminSalesDocumentBlockKind"},"title":{"type":"string","maxLength":200,"minLength":0,"x-go-type-skip-optional-pointer":true,"description":"Optional block title, up to 200 characters. Omission is stored as an empty string. Empty strings are omitted from responses."},"body":{"type":"string","maxLength":20000,"minLength":0,"x-go-type-skip-optional-pointer":true,"description":"Optional authored text, up to 20,000 characters. Omission is stored as an empty string. Empty strings are omitted from responses. Counts toward the document aggregate UTF-8 byte limit."}},"required":["id","kind"],"x-sensitive":true},"AdminSalesDocumentContent":{"type":"object","additionalProperties":false,"properties":{"schema_version":{"type":"integer","minimum":1,"maximum":1},"blocks":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/AdminSalesDocumentBlock"},"description":"At most 100 ordered blocks. Block IDs must be unique within the document."}},"required":["schema_version","blocks"],"description":"Ordered authored blocks using schema version 1. Across all blocks, the combined UTF-8 bytes of title and body must not exceed 1,000,000. This aggregate byte limit is enforced by the service independently of the 2,500,000-byte HTTP body limit.","x-sensitive":true},"AdminSalesDocumentCreate":{"type":"object","additionalProperties":false,"properties":{"deal_id":{"$ref":"#/components/schemas/DealID"},"quote_id":{"$ref":"#/components/schemas/DealQuoteID","description":"Optional existing quote on deal_id. A quote from another deal returns 422. Omit for an unquoted document."},"kind":{"$ref":"#/components/schemas/AdminSalesDocumentKind"},"title":{"type":"string","maxLength":200,"minLength":1},"content":{"$ref":"#/components/schemas/AdminSalesDocumentContent"}},"required":["deal_id","kind","title","content"],"description":"Create a Sales Studio draft. Deal, optional quote, and kind cannot change after creation. Any associated quote must belong to the selected deal.","x-sensitive":true},"AdminSalesDocument":{"readOnly":true,"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AdminSalesDocumentBase"},{"type":"object","properties":{"content":{"$ref":"#/components/schemas/AdminSalesDocumentContent"}},"required":["content"]}],"x-sensitive":true},"AdminSalesDocumentUpdate":{"type":"object","additionalProperties":false,"properties":{"expected_revision":{"type":"integer","minimum":1,"maximum":2147483647},"title":{"type":"string","maxLength":200,"minLength":1},"content":{"$ref":"#/components/schemas/AdminSalesDocumentContent"}},"required":["expected_revision","title","content"],"x-sensitive":true},"SalesDocumentRevisionID":{"type":"string","minLength":1,"pattern":"^sdrv_[0-9a-hjkmnp-tv-z]{26}$","example":"sdrv_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSalesDocumentRevision":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/SalesDocumentRevisionID"},"document_id":{"$ref":"#/components/schemas/SalesDocumentID"},"revision":{"type":"integer","minimum":1,"maximum":2147483647},"title":{"type":"string","maxLength":200,"minLength":1},"content":{"$ref":"#/components/schemas/AdminSalesDocumentContent"},"created_by":{"$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","format":"date-time","minLength":1}},"required":["id","document_id","revision","title","content","created_by","created_at"],"readOnly":true,"x-sensitive":true},"AdminSalesDocumentRevisionList":{"allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSalesDocumentRevision"}}},"required":["data"]}],"unevaluatedProperties":false,"x-sensitive":true},"SalesDocumentCommentID":{"type":"string","minLength":1,"pattern":"^sdcm_[0-9a-hjkmnp-tv-z]{26}$","example":"sdcm_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSalesDocumentComment":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/SalesDocumentCommentID"},"document_id":{"$ref":"#/components/schemas/SalesDocumentID"},"revision":{"type":"integer","minimum":1,"maximum":2147483647},"body":{"type":"string","maxLength":10000,"minLength":1},"author_user_id":{"$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","format":"date-time","minLength":1}},"required":["id","document_id","revision","body","author_user_id","created_at"],"readOnly":true,"x-sensitive":true},"AdminSalesDocumentCommentList":{"allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSalesDocumentComment"}}},"required":["data"]}],"unevaluatedProperties":false,"x-sensitive":true},"AdminSalesDocumentCommentCreate":{"type":"object","additionalProperties":false,"properties":{"revision":{"type":"integer","minimum":1,"maximum":2147483647},"body":{"type":"string","maxLength":10000,"minLength":1}},"required":["revision","body"],"x-sensitive":true},"AdminSalesDocumentAction":{"type":"string","enum":["request_review","withdraw","request_changes","approve","archive"],"minLength":1,"description":"request_review: pin current content and attached quote for review. withdraw: return an active review to draft. request_changes: independently return reviewed content for editing. approve: independently approve the pinned content and unchanged quote snapshot. archive: make a document read-only outside active review. No action grants quote approval, signature, delivery or billing authority."},"AdminSalesDocumentReview":{"type":"object","additionalProperties":false,"properties":{"id":{"$ref":"#/components/schemas/SalesDocumentReviewID"},"document_id":{"$ref":"#/components/schemas/SalesDocumentID"},"revision":{"type":"integer","minimum":1,"maximum":2147483647},"created_at":{"type":"string","format":"date-time","minLength":1},"action":{"$ref":"#/components/schemas/AdminSalesDocumentAction"},"actor_user_id":{"$ref":"#/components/schemas/UserID"},"quote_fingerprint":{"type":"string","maxLength":64,"minLength":0}},"required":["id","document_id","revision","created_at","action","actor_user_id"],"readOnly":true,"x-sensitive":true},"AdminSalesDocumentReviewList":{"allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSalesDocumentReview"}}},"required":["data"]}],"unevaluatedProperties":false,"x-sensitive":true},"AdminSalesDocumentReviewAction":{"type":"string","minLength":1,"enum":["approve","request_changes"],"description":"approve: independently approve the active review. request_changes: independently return the active review for editing. Both actions require expected_review_id."},"AdminSalesDocumentTransition":{"type":"object","additionalProperties":false,"properties":{"expected_revision":{"type":"integer","minimum":1,"maximum":2147483647},"action":{"$ref":"#/components/schemas/AdminSalesDocumentAction"},"note":{"type":"string","maxLength":10000,"description":"Reason for requesting changes. Required and nonblank for request_changes on a document with an approval_request_id; optional for legacy reviews."},"expected_review_id":{"$ref":"#/components/schemas/SalesDocumentReviewID","description":"Required for approve, request_changes or withdraw. Must match current_review_id from the document being reviewed."}},"required":["expected_revision","action"],"if":{"properties":{"action":{"anyOf":[{"$ref":"#/components/schemas/AdminSalesDocumentReviewAction"},{"const":"withdraw"}]}},"required":["action"]},"then":{"required":["expected_review_id"],"properties":{"expected_review_id":{"$ref":"#/components/schemas/SalesDocumentReviewID"}}},"x-sensitive":true},"AdminDeal":{"type":"object","additionalProperties":false,"required":["id","name","status","owner_user_id","currency_code","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/DealID"},"organization_id":{"description":"The organization the deal is for. Null for a prospect that has no organization yet.","oneOf":[{"$ref":"#/components/schemas/OrganizationID"},{"type":"null"}]},"prospect_name":{"type":"string","description":"Display name of the prospect. Empty once the deal is bound to an organization.","example":"Acme Logistics"},"name":{"type":"string","minLength":1,"example":"Acme 2026 renewal"},"status":{"type":"string","minLength":1,"enum":["draft","negotiating","accepted","lost","archived"],"example":"negotiating"},"owner_user_id":{"$ref":"#/components/schemas/UserID"},"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminDealList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminDeal"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminDealCreate":{"type":"object","additionalProperties":false,"required":["name"],"oneOf":[{"required":["organization_id"]},{"required":["prospect_name"]}],"properties":{"name":{"type":"string","minLength":1,"example":"Acme 2026 renewal"},"organization_id":{"description":"The organization the deal is for. Omit for a prospect and set prospect_name instead.","$ref":"#/components/schemas/OrganizationID"},"prospect_name":{"type":"string","minLength":1,"description":"Display name of the prospect. Required when no organization is set.","example":"Acme Logistics"},"owner_user_id":{"description":"The staff member who owns the deal. Defaults to the caller.","$ref":"#/components/schemas/UserID"},"currency_code":{"description":"Deal currency. Defaults to EUR.","$ref":"#/components/schemas/CurrencyCode"}}},"AdminDealMarginBand":{"type":"string","minLength":1,"enum":["negative","thin","healthy"],"description":"Margin band of an offer against its margin floor. `negative` means the offer prices below cost, `thin` that the margin is positive but below the floor, `healthy` that the margin clears the floor.\n","example":"healthy"},"AdminCostBookMarginCheck":{"type":"object","additionalProperties":false,"required":["band","verdict","cost_book_version_id","stale","explanation"],"properties":{"unit_cost":{"type":["string","null"],"minLength":1,"readOnly":true,"x-sensitive":true,"description":"Unit cost as a decimal string. Only present for callers allowed to see raw route costs.","example":"0.0165"},"floor_price":{"type":["string","null"],"minLength":1,"readOnly":true,"x-sensitive":true,"description":"The lowest offer that still meets the product's margin floor, as a decimal string. Cost-derivable (equals the unit cost at a 0 bps floor), so only present for callers allowed to see raw route costs.","example":"0.0174"},"margin_bps":{"type":["integer","null"],"readOnly":true,"x-sensitive":true,"description":"Margin of the checked offer in basis points of the offer. Cost-derivable given the caller-supplied offer, so only present for callers allowed to see raw route costs.","example":2667},"band":{"readOnly":true,"description":"Margin band of the checked offer against the product's floor.","$ref":"#/components/schemas/AdminDealMarginBand"},"verdict":{"type":"string","minLength":1,"readOnly":true,"description":"Whether the offer can be self-approved (`ok`) or needs a second approver (`needs_approval`).","enum":["ok","needs_approval"],"example":"ok"},"cost_book_version_id":{"readOnly":true,"description":"The active cost book version the check was computed against.","$ref":"#/components/schemas/CostBookVersionID"},"stale":{"type":"boolean","readOnly":true,"description":"Whether the cost book's active version is older than its freshness policy."},"explanation":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Human-readable summary of how the verdict was reached. Margin and floor numbers appear only for callers allowed to see raw route costs.","example":"Margin 2667 bps clears the 500 bps floor (cost version v3, fresh)."}}},"AdminDealFamily":{"type":"string","minLength":1,"enum":["usage","session","recurring","one_time","plan"],"description":"Pricing family of a quote section and its products. The family fixes how lines are quantified and billed: `usage` prices per message or event, `session` per minute with a billing increment, `recurring` as a monthly fee per unit, `one_time` as a single fee per occurrence, and `plan` as a platform plan subscription.\n","example":"usage"},"AdminDealQuotableProductDim":{"type":"object","additionalProperties":false,"required":["field","label"],"properties":{"field":{"type":"string","minLength":1,"readOnly":true,"description":"The dimension key, as used in a line's dims.","example":"country_code"},"label":{"type":"string","minLength":1,"readOnly":true,"description":"Display label for the dimension.","example":"Country code"}}},"AdminDealQuotableProduct":{"type":"object","additionalProperties":false,"required":["id","slug","name","family","dims"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"slug":{"type":"string","minLength":1,"readOnly":true,"description":"Stable identifier of the product.","example":"sms_outbound"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the product.","example":"SMS"},"family":{"readOnly":true,"$ref":"#/components/schemas/AdminDealFamily"},"dims":{"type":"array","readOnly":true,"description":"The product's pricing dimensions, in display order.","items":{"$ref":"#/components/schemas/AdminDealQuotableProductDim"}}}},"AdminDealQuotableProductList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The products a deal can quote, ordered by pricing family.","items":{"$ref":"#/components/schemas/AdminDealQuotableProduct"}}}},"AdminDealStaffName":{"type":"object","description":"A staff member's display identity, resolved for rendering on deal surfaces.\n","additionalProperties":false,"required":["user_id","email"],"properties":{"user_id":{"$ref":"#/components/schemas/UserID"},"email":{"type":"string","format":"email","minLength":3,"description":"The staff member's login email, used as their display name."}}},"AdminDealUpdate":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":1,"example":"Acme 2026 renewal"},"owner_user_id":{"description":"Reassign the deal to another staff member.","$ref":"#/components/schemas/UserID"},"organization_id":{"description":"Bind a prospect deal to an organization. One-way; a deal already bound to a different organization is rejected.","$ref":"#/components/schemas/OrganizationID"},"status":{"type":"string","minLength":1,"enum":["draft","negotiating","accepted","lost","archived"],"example":"negotiating"}}},"AdminDealWaPenetration":{"type":"object","additionalProperties":false,"required":["default"],"properties":{"default":{"type":"number","format":"double","minimum":0,"maximum":1,"description":"Assumed share of blended traffic delivered over WhatsApp, 0 to 1, used when a country has no override.","example":0.7},"by_country":{"type":"object","description":"Per-country penetration overrides keyed by ISO 3166-1 alpha-2 country code.","additionalProperties":{"type":"number","format":"double"},"example":{"KE":0.7,"TZ":0.4}}}},"AdminDealRounding":{"type":"object","additionalProperties":false,"required":["mode","precision_micros"],"properties":{"mode":{"type":"string","minLength":1,"description":"How blended costs are rounded into an offer.","enum":["half_up","up","down"],"example":"half_up"},"precision_micros":{"type":"integer","format":"int64","minimum":0,"description":"Rounding step in millionths of a currency unit. 100 rounds to 0.0001.","example":100}}},"AdminDealFx":{"type":"object","additionalProperties":false,"properties":{"rates":{"type":"object","description":"Display-only FX rates keyed by currency pair, such as EURUSD.","additionalProperties":{"type":"number","format":"double"},"example":{"EURUSD":1.146}},"as_of":{"type":"string","format":"date","description":"The date the FX rates were taken, as YYYY-MM-DD.","example":"2026-07-22"}}},"AdminDealAssumptions":{"type":"object","additionalProperties":false,"required":["wa_penetration","meta_rebate_pct","rounding"],"properties":{"wa_penetration":{"$ref":"#/components/schemas/AdminDealWaPenetration"},"meta_rebate_pct":{"type":"number","format":"double","minimum":0,"maximum":100,"description":"Assumed Meta rebate applied to the WhatsApp tariff, as a percentage.","example":15},"rounding":{"$ref":"#/components/schemas/AdminDealRounding"},"fx":{"$ref":"#/components/schemas/AdminDealFx"},"dims_defaults":{"type":"object","description":"Per-product pricing-dimension defaults, keyed by product slug or ID. Quote lines carry only a country, so a product that prices on additional dimensions (for example the WhatsApp message category) resolves costs and list prices with these values filled in for the dimensions a line does not carry.","additionalProperties":{"type":"object","additionalProperties":{"type":"string","minLength":1}},"example":{"whatsapp_outbound":{"category":"utility"}}}}},"AdminDealQuoteCommitPeriodType":{"type":"string","minLength":1,"enum":["monthly","annual"],"description":"How a minimum commit settles. `monthly` floors each period at the greater of the commit and actual spend, so a shortfall is topped up every month; `annual` settles once as a year-end true-up against cumulative spend.\n","example":"monthly"},"AdminDealQuoteCommit":{"type":"object","additionalProperties":false,"required":["amount","period_type","term_months"],"description":"Minimum spend the customer commits to. The only commercial term that provisions: accepting the quote compiles it into a billing minimum commit.\n","properties":{"amount":{"type":"string","minLength":1,"description":"Committed spend per period, as a positive decimal string in the deal's currency. Held to six decimal places; a longer value is rounded half-up, so a value that rounds to zero is rejected.","example":"25000.00"},"period_type":{"$ref":"#/components/schemas/AdminDealQuoteCommitPeriodType"},"term_months":{"type":"integer","minimum":1,"description":"How many months the commitment runs for.","example":12}}},"AdminDealQuoteSupport":{"type":"object","additionalProperties":false,"required":["tier"],"description":"Support commitment, recorded and fulfilled manually. Billing carries no support product, quota or SLA construct, so the tier is the name used in the order form rather than a catalog reference.\n","properties":{"tier":{"type":"string","minLength":1,"maxLength":100,"description":"The agreed support tier, as named in the order form.","example":"premium"},"notes":{"type":"string","maxLength":2000,"description":"Caveats on the tier that the order form spells out.","example":"TAM named at close"}}},"AdminDealQuotePaymentTerms":{"type":"string","minLength":1,"enum":["prepaid","invoiced"],"description":"How the customer settles. Recorded for the proposal pack and the close checklist; neither value changes billing behaviour, and a credit limit is set through the wallet rather than on the quote.\n","example":"invoiced"},"AdminDealQuoteTerms":{"type":"object","additionalProperties":false,"description":"Commercial terms of the quote round, alongside its prices. Every term is optional: a quote may state none. Amounts are decimal strings in the deal's own currency, which the deal fixes.\n\nA read always states each term, so `null` means the quote carries none. On an update the three cases differ: omitting a member leaves it unchanged, sending `null` clears it, and sending a value replaces it.\n","properties":{"commit":{"oneOf":[{"$ref":"#/components/schemas/AdminDealQuoteCommit"},{"type":"null"}]},"support":{"oneOf":[{"$ref":"#/components/schemas/AdminDealQuoteSupport"},{"type":"null"}]},"payment":{"oneOf":[{"$ref":"#/components/schemas/AdminDealQuotePaymentTerms"},{"type":"null"}]},"validity_days":{"type":"integer","minimum":0,"description":"How long the quote stands after it is sent. Zero when unstated.","example":30}}},"AdminDealQuote":{"type":"object","additionalProperties":false,"required":["id","deal_id","round","status","cost_book_version_ids","assumptions","terms","notes","created_by","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/DealQuoteID"},"deal_id":{"readOnly":true,"$ref":"#/components/schemas/DealID"},"round":{"type":"integer","readOnly":true,"description":"Negotiation round number, starting at 1.","example":2},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["draft","pending_approval","approved","sent","accepted","superseded"],"example":"draft"},"approval_request_id":{"readOnly":true,"description":"Most recent approval request for this quote, retained after completion. Omitted for quotes that have not entered shared approval.","$ref":"#/components/schemas/ApprovalRequestID"},"returned_note":{"type":"string","readOnly":true,"maxLength":2000,"description":"Explanation from the approval stage that returned this quote for changes. Omitted unless the quote was sent back."},"cost_book_version_ids":{"type":"object","readOnly":true,"description":"The cost book version pinned per product when the round was opened. Margins stay reproducible against these versions after costs move.","additionalProperties":{"$ref":"#/components/schemas/CostBookVersionID"}},"assumptions":{"$ref":"#/components/schemas/AdminDealAssumptions"},"terms":{"$ref":"#/components/schemas/AdminDealQuoteTerms"},"approved_by":{"readOnly":true,"description":"The staff member who approved the quote.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"approved_at":{"type":["string","null"],"format":"date-time","readOnly":true},"notes":{"type":"string","minLength":0,"example":"Round 2 after volume commitment moved up."},"created_by":{"readOnly":true,"$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminDealQuoteList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"All quote rounds of the deal, newest first.","items":{"$ref":"#/components/schemas/AdminDealQuote"}}}},"AdminDealQuoteCreate":{"type":"object","additionalProperties":false,"properties":{"source_quote_id":{"description":"The quote to copy lines and assumptions forward from. Defaults to the deal's latest round.","$ref":"#/components/schemas/DealQuoteID"}}},"QuoteSectionID":{"type":"string","minLength":1,"pattern":"^dsc_[0-9a-hjkmnp-tv-z]{26}$","example":"dsc_01krdgeqcxet5s7t44vh8rt9mg"},"AdminDealQuoteSection":{"type":"object","additionalProperties":false,"required":["id","family","title","sort_order"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/QuoteSectionID"},"family":{"$ref":"#/components/schemas/AdminDealFamily"},"title":{"type":"string","minLength":1,"description":"Section heading as it appears on the quote and the proposal. Unique within the quote.","example":"SMS / WhatsApp"},"sort_order":{"type":"integer","readOnly":true,"description":"Display position of the section, following the order sections were submitted in.","example":1}}},"DealLineID":{"type":"string","minLength":1,"pattern":"^dln_[0-9a-hjkmnp-tv-z]{26}$","example":"dln_01krdgeqcxet5s7t44vh8rt9mg"},"DealLineGroupID":{"type":"string","minLength":1,"pattern":"^dlg_[0-9a-hjkmnp-tv-z]{26}$","example":"dlg_01krdgeqcxet5s7t44vh8rt9mg"},"AdminDealDims":{"type":"object","additionalProperties":{"type":"string"},"description":"Pricing dimension values for a line, keyed by the product's pricing dimensions. A subset is accepted when the remaining dimensions follow the product's catalog rows.","example":{"country_code":"RO"}},"AdminDealIncrement":{"type":"object","additionalProperties":false,"description":"Billing increment for a per-minute line — how call duration rounds up when billed.","required":["initial_seconds","continuation_seconds"],"properties":{"initial_seconds":{"type":"integer","minimum":1,"maximum":999,"description":"Seconds billed for the first increment of a call.","example":60},"continuation_seconds":{"type":"integer","minimum":1,"maximum":999,"description":"Seconds billed for each increment after the first.","example":6}}},"AdminDealIncludedLanding":{"type":"string","minLength":1,"enum":["fold_in","credit"],"description":"How included units are honored once the deal is live. `fold_in` lowers the contractual unit price so the inclusion is built into the billed rate; `credit` keeps the offered price as the billed rate and grants the inclusion's value as a one-time account credit at go-live.\n","example":"fold_in"},"AdminDealLine":{"type":"object","additionalProperties":false,"required":["id","section_id","dims","included_qty","included_landing","sort_order"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/DealLineID"},"section_id":{"readOnly":true,"$ref":"#/components/schemas/QuoteSectionID"},"group_id":{"readOnly":true,"description":"The composite-offer group the line belongs to. Null for a standalone line.","oneOf":[{"$ref":"#/components/schemas/DealLineGroupID"},{"type":"null"}]},"product_id":{"description":"The priced product. Every line carries either a product or a grant key, never both.","oneOf":[{"$ref":"#/components/schemas/ProductID"},{"type":"null"}]},"product_name":{"type":"string","readOnly":true,"description":"Display name of the priced product.","example":"SMS"},"grant_key":{"type":["string","null"],"description":"For grant lines, the quota or entitlement being granted instead of a priced product.","example":"email_mailboxes_custom_handles_max"},"dims":{"$ref":"#/components/schemas/AdminDealDims"},"dims_display":{"type":"string","readOnly":true,"description":"Human-readable rendering of the line's pricing dimensions, such as the destination country's name.","example":"Romania"},"quantity":{"type":["integer","null"],"format":"int64","description":"Committed monthly quantity in the section's unit — events, minutes, units, or fee occurrences; the granted value on a grant line. Null for a line whose group carries the volume.","example":2500000},"current_price":{"type":["string","null"],"description":"The customer's current unit price as a decimal string. Null when unknown.","example":"0.0310"},"offer":{"type":["string","null"],"description":"Offered unit price as a decimal string. Null when the line's group carries the price.","example":"0.0225"},"offer_increment":{"description":"For per-minute lines, the billing increment as initial and continuation seconds. Null for other lines.","oneOf":[{"$ref":"#/components/schemas/AdminDealIncrement"},{"type":"null"}]},"included_qty":{"type":"integer","format":"int64","description":"Units included at no charge, valued at the offered price when the quote is checked against margin floors.","example":0},"included_landing":{"$ref":"#/components/schemas/AdminDealIncludedLanding"},"comment":{"type":"string","example":"Volume steps up in Q3."},"sort_order":{"type":"integer","readOnly":true,"description":"Display position of the line within its section, following the order lines were submitted in.","example":0}}},"AdminDealLineGroup":{"type":"object","additionalProperties":false,"required":["id","kind","offer","quantity"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/DealLineGroupID"},"kind":{"type":"string","minLength":1,"readOnly":true,"enum":["blend"],"description":"How the group's single price decomposes across its member lines. `blend` splits a country's SMS and WhatsApp traffic by the assumed WhatsApp penetration.","example":"blend"},"offer":{"type":"string","minLength":1,"readOnly":true,"description":"The single offered unit price covering all member lines, as a decimal string.","example":"0.0220"},"quantity":{"type":"integer","format":"int64","readOnly":true,"description":"Total committed monthly volume the group price covers.","example":2500000}}},"AdminDealQuoteContent":{"type":"object","additionalProperties":false,"required":["sections","lines","groups"],"properties":{"sections":{"type":"array","description":"The quote's sections in display order.","items":{"$ref":"#/components/schemas/AdminDealQuoteSection"}},"lines":{"type":"array","description":"The quote's stored lines in display order, section by section.","items":{"$ref":"#/components/schemas/AdminDealLine"}},"groups":{"type":"array","description":"Composite-offer groups referenced by the lines, such as a blended SMS and WhatsApp price.","items":{"$ref":"#/components/schemas/AdminDealLineGroup"}}}},"AdminDealOfferEvaluation":{"type":"object","additionalProperties":false,"required":["offer","below_floor"],"description":"One offered price floor-checked against its own cost basis — the SMS offer against the SMS cost, the blended offer against the penetration-blended cost.","properties":{"offer":{"type":"string","minLength":1,"readOnly":true,"description":"The offered unit price as a decimal string.","example":"0.0220"},"margin":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Per-unit margin of this offer against its cost basis as a decimal string. Null when the basis cost is missing. Cost-derived, so only present for callers allowed to see raw route costs.","example":"0.0078"},"margin_bps":{"type":["integer","null"],"readOnly":true,"x-sensitive":true,"description":"Margin in basis points of this offer. Cost-derived, so only present for callers allowed to see raw route costs.","example":3545},"band":{"readOnly":true,"description":"Margin band of this offer against its floor. Null when the margin cannot be evaluated.","oneOf":[{"$ref":"#/components/schemas/AdminDealMarginBand"},{"type":"null"}]},"below_floor":{"type":"boolean","readOnly":true,"description":"Whether this offer prices below its margin floor."}}},"AdminDealComputedLine":{"type":"object","additionalProperties":false,"required":["monthly_volume","band","below_floor","flags"],"properties":{"line_id":{"readOnly":true,"description":"The stored line this row was computed from. Null for what-if rows that are not persisted.","oneOf":[{"$ref":"#/components/schemas/DealLineID"},{"type":"null"}]},"country_code":{"readOnly":true,"description":"The line's country dimension value. Null for lines without one (grants, country-less products).","example":"RO","oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}]},"monthly_volume":{"type":"integer","format":"int64","readOnly":true,"example":2500000},"offer":{"type":["string","null"],"readOnly":true,"description":"The effective offered unit price as a decimal string — the blended offer when set, otherwise the SMS offer.","example":"0.0220"},"sms_cost":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"SMS unit cost as a decimal string. Only present for callers allowed to see raw route costs.","example":"0.0165"},"wa_cost":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Effective WhatsApp unit cost after the Meta rebate, as a decimal string. Only present for callers allowed to see raw route costs.","example":"0.0089"},"blended_cost":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Penetration-weighted blended unit cost as a decimal string. Only present for callers allowed to see raw route costs.","example":"0.0142"},"list_price":{"type":["string","null"],"readOnly":true,"description":"The product's list price for the country as a decimal string. Null when no list price matches.","example":"0.0350"},"suggested_offer":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"The blended cost rounded per the quote's rounding assumption — the historical sheet's offer column. Cost-derived, so only present for callers allowed to see raw route costs.","example":"0.0220"},"margin":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Per-unit margin (offer minus blended cost) as a decimal string. Null when the margin cannot be evaluated. Cost-derived, so only present for callers allowed to see raw route costs.","example":"0.0078"},"margin_bps":{"type":["integer","null"],"readOnly":true,"x-sensitive":true,"description":"Margin in basis points of the offer. Cost-derived, so only present for callers allowed to see raw route costs.","example":3545},"margin_total":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Monthly margin (per-unit margin times monthly volume) as a decimal string. Cost-derived, so only present for callers allowed to see raw route costs.","example":"19500.00"},"savings":{"type":["string","null"],"readOnly":true,"description":"Per-unit savings against the customer's current price as a decimal string. Null when the current price is unknown.","example":"0.0090"},"savings_total":{"type":["string","null"],"readOnly":true,"description":"Monthly savings (per-unit savings times monthly volume) as a decimal string.","example":"22500.00"},"sms_offer_eval":{"readOnly":true,"description":"The SMS offer's floor evaluation (against the SMS cost). Null when the line carries no SMS offer.","oneOf":[{"$ref":"#/components/schemas/AdminDealOfferEvaluation"},{"type":"null"}]},"blended_offer_eval":{"readOnly":true,"description":"The blended offer's floor evaluation (against the penetration-blended cost, at the stricter of the SMS and WhatsApp floors). Null when the line carries no blended offer.","oneOf":[{"$ref":"#/components/schemas/AdminDealOfferEvaluation"},{"type":"null"}]},"band":{"readOnly":true,"description":"Worst margin band across the line's evaluated offers.","$ref":"#/components/schemas/AdminDealMarginBand"},"below_floor":{"type":"boolean","readOnly":true,"description":"Whether any of the line's offers prices below its margin floor — or cannot be evaluated — and therefore needs a second approver."},"flags":{"type":"array","readOnly":true,"description":"Data gaps on the line, such as a missing cost or list price.","items":{"type":"string","x-extensible-enum":["no_offer","missing_sms_cost","missing_wa_cost","missing_unit_cost","missing_list_price","family_not_priced"]}}}},"AdminDealQuoteTotals":{"type":"object","additionalProperties":false,"required":["monthly_volume"],"properties":{"monthly_volume":{"type":"integer","format":"int64","readOnly":true,"description":"Total committed monthly volume across all lines.","example":6800000},"margin_total":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Total monthly margin across all evaluable lines as a decimal string. Null when no line is evaluable. Cost-derived, so only present for callers allowed to see raw route costs.","example":"41200.00"},"savings_total":{"type":["string","null"],"readOnly":true,"description":"Total monthly savings across lines with a known current price as a decimal string.","example":"58600.00"}}},"AdminDealQuoteDetail":{"type":"object","description":"A quote round with its stored content plus the pricing computed on read.","allOf":[{"$ref":"#/components/schemas/AdminDealQuoteContent"},{"type":"object","required":["quote","computed_lines","totals"],"properties":{"quote":{"$ref":"#/components/schemas/AdminDealQuote"},"computed_lines":{"type":"array","description":"Per-line pricing derived from the pinned cost versions, the active list prices, and the quote's assumptions. Computed on read, never stored.","items":{"$ref":"#/components/schemas/AdminDealComputedLine"}},"totals":{"$ref":"#/components/schemas/AdminDealQuoteTotals"}}}]},"AdminDealQuoteUpdate":{"type":"object","additionalProperties":false,"properties":{"assumptions":{"$ref":"#/components/schemas/AdminDealAssumptions"},"terms":{"$ref":"#/components/schemas/AdminDealQuoteTerms"},"notes":{"type":"string","example":"Round 2 after volume commitment moved up."}}},"AdminDealSectionLineInput":{"type":"object","additionalProperties":false,"required":["quantity"],"oneOf":[{"required":["product_id"]},{"required":["grant_key"]}],"properties":{"product_id":{"description":"The priced product. Provide either a product or a grant key, never both.","$ref":"#/components/schemas/ProductID"},"grant_key":{"type":"string","minLength":1,"description":"For grant lines in a plan section, the quota or entitlement to grant instead of a priced product.","example":"email_mailboxes_custom_handles_max"},"dims":{"$ref":"#/components/schemas/AdminDealDims"},"quantity":{"type":"integer","format":"int64","minimum":0,"description":"Committed monthly quantity in the section's unit — events, minutes, units, or fee occurrences; the granted value on a grant line.","example":2500000},"current_price":{"type":["string","null"],"description":"The customer's current unit price as a decimal string. Null when unknown.","example":"0.0310"},"offer":{"type":["string","null"],"description":"Offered unit price as a decimal string.","example":"0.0225"},"blended_offer":{"type":["string","null"],"description":"For an SMS line, the single offered price covering the country's SMS and WhatsApp traffic. Stored as a blended group with a WhatsApp leg alongside the SMS line.","example":"0.0220"},"offer_increment":{"description":"For per-minute lines, the billing increment as initial and continuation seconds.","$ref":"#/components/schemas/AdminDealIncrement"},"included_qty":{"type":"integer","format":"int64","minimum":0,"description":"Units included at no charge. Valued at the offered price when the quote is checked against margin floors.","example":0},"included_landing":{"$ref":"#/components/schemas/AdminDealIncludedLanding"},"comment":{"type":"string","example":"Volume steps up in Q3."}}},"AdminDealQuoteSectionInput":{"type":"object","additionalProperties":false,"required":["family","title","lines"],"properties":{"family":{"$ref":"#/components/schemas/AdminDealFamily"},"title":{"type":"string","minLength":1,"description":"Section heading as it appears on the quote and the proposal. Unique within the quote, at most one section per family.","example":"SMS / WhatsApp"},"lines":{"type":"array","description":"The section's lines, in display order.","items":{"$ref":"#/components/schemas/AdminDealSectionLineInput"}}}},"AdminDealSectionsReplace":{"type":"object","additionalProperties":false,"required":["sections"],"properties":{"sections":{"type":"array","description":"The full section set, in display order. Replaces every existing section, line, and group of the quote.","items":{"$ref":"#/components/schemas/AdminDealQuoteSectionInput"}}}},"AdminDealQuotePreviewRequest":{"type":"object","additionalProperties":false,"properties":{"assumptions":{"description":"Assumption overrides for this computation only. Defaults to the quote's stored assumptions.","$ref":"#/components/schemas/AdminDealAssumptions"},"sections":{"type":"array","description":"Section overrides for this computation only. Defaults to the quote's stored sections. Nothing is persisted either way.","items":{"$ref":"#/components/schemas/AdminDealQuoteSectionInput"}}}},"AdminDealQuotePreviewResult":{"type":"object","additionalProperties":false,"required":["computed_lines","totals"],"properties":{"computed_lines":{"type":"array","items":{"$ref":"#/components/schemas/AdminDealComputedLine"}},"totals":{"$ref":"#/components/schemas/AdminDealQuoteTotals"}}},"AdminDealQuoteImportRequest":{"type":"object","additionalProperties":false,"oneOf":[{"required":["content"]},{"required":["content_xlsx"]}],"properties":{"content":{"type":"string","minLength":1,"description":"The pasted or uploaded table as text. Tab-separated (a direct paste from a spreadsheet) and comma-separated content are both accepted; the first row must be the column headers. Provide exactly one of `content` or `content_xlsx`."},"content_xlsx":{"type":"string","minLength":1,"description":"An uploaded workbook (`.xlsx`), base64-encoded. Workbooks previously exported from a quote are matched back to their source round via the embedded metadata. Provide exactly one of `content` or `content_xlsx`."},"confirm":{"type":"boolean","description":"False (the default) parses the table and returns the interpreted rows and column mapping without changing anything — review this echo first. True applies the parsed lines by opening the next negotiation round for the deal.\n"}}},"AdminDealImportColumn":{"type":"object","additionalProperties":false,"required":["header","field","confidence"],"properties":{"header":{"type":"string","minLength":1,"readOnly":true,"description":"The column header as it appeared in the pasted or uploaded table.","example":"Volume / month"},"field":{"type":"string","minLength":1,"readOnly":true,"description":"The quote line field the column was mapped to, or `ignored` when the column carries no known field.","enum":["country","monthly_volume","current_price","sms_offer","blended_offer","comment","ignored"],"example":"monthly_volume"},"confidence":{"type":"number","readOnly":true,"description":"How certain the mapping is, 0 to 1. An exact header match is 1; alias matches score lower.","example":0.8}}},"AdminDealImportLine":{"type":"object","additionalProperties":false,"required":["country_code","monthly_volume"],"properties":{"country_code":{"description":"ISO 3166-1 alpha-2 country code. Unique per import.","example":"RO","$ref":"#/components/schemas/CountryCode"},"monthly_volume":{"type":"integer","format":"int64","minimum":0,"description":"Committed monthly message volume for the country.","example":2500000},"current_price":{"type":["string","null"],"description":"The customer's current unit price as a decimal string. Null when unknown.","example":"0.0310"},"sms_offer":{"type":["string","null"],"description":"Offered SMS unit price as a decimal string.","example":"0.0225"},"blended_offer":{"type":["string","null"],"description":"Offered blended IP/SMS unit price as a decimal string. Null for an SMS-only line.","example":"0.0220"},"comment":{"type":"string","example":"Volume steps up in Q3."}}},"AdminDealImportFieldDiff":{"type":"object","additionalProperties":false,"required":["field"],"properties":{"field":{"type":"string","minLength":1,"description":"The line field that moved.","readOnly":true},"from":{"type":["string","null"],"description":"The value on the quote round the import is diffed against. Null when the field was unset.","readOnly":true},"to":{"type":["string","null"],"description":"The imported value. Null when the field is unset in the import.","readOnly":true}}},"AdminDealImportLineDiff":{"type":"object","additionalProperties":false,"required":["country_code","change"],"properties":{"country_code":{"description":"ISO 3166-1 alpha-2 country code of the line.","readOnly":true,"$ref":"#/components/schemas/CountryCode"},"change":{"type":"string","minLength":1,"enum":["added","removed","changed","unchanged"],"description":"How the imported line relates to the quote round it is diffed against.","readOnly":true},"fields":{"type":"array","readOnly":true,"description":"The line fields whose values moved, with the previous and imported value. Empty unless `change` is `changed`.","items":{"$ref":"#/components/schemas/AdminDealImportFieldDiff"}}}},"AdminDealQuoteImportResult":{"type":"object","additionalProperties":false,"required":["columns","lines","issues"],"properties":{"columns":{"type":"array","readOnly":true,"description":"How each table column was interpreted. Review before confirming.","items":{"$ref":"#/components/schemas/AdminDealImportColumn"}},"lines":{"type":"array","readOnly":true,"description":"The parsed per-country rows, in table order. A confirmed import applies them to the quote's SMS/WhatsApp usage section.","items":{"$ref":"#/components/schemas/AdminDealImportLine"}},"issues":{"type":"array","readOnly":true,"description":"Non-fatal warnings raised while parsing, such as skipped empty rows.","items":{"type":"string"}},"diff":{"type":"array","readOnly":true,"description":"Per-country diff of the imported lines against the quote round they replace — lines added, removed, or with moved prices or volumes.","items":{"$ref":"#/components/schemas/AdminDealImportLineDiff"}},"source_round":{"type":["integer","null"],"readOnly":true,"description":"The round the uploaded workbook was exported from, read from its embedded metadata. Null for pasted tables or workbooks without metadata."},"stale_round":{"type":"boolean","readOnly":true,"description":"True when the uploaded workbook was exported from a superseded round — the sheet being negotiated is older than the deal's latest pricing. Review the diff carefully before confirming."},"quote":{"readOnly":true,"description":"The new draft round the lines were applied to. Only present on a confirmed import.","oneOf":[{"$ref":"#/components/schemas/AdminDealQuote"},{"type":"null"}]}}},"AdminDealQuoteApprovalCreate":{"type":"object","additionalProperties":false,"required":["expected_request_id"],"properties":{"expected_request_id":{"$ref":"#/components/schemas/ApprovalRequestID"}}},"AdminApprovalRequestStatus":{"type":"string","description":"Current lifecycle state of an approval request.","minLength":1,"enum":["pending","approved","returned","rejected","withdrawn"]},"AdminApprovalRequestMetadata":{"type":"object","required":["id","entity_type","entity_id","submitted_by","submitted_at","status"],"properties":{"id":{"$ref":"#/components/schemas/ApprovalRequestID"},"entity_type":{"type":"string","minLength":1,"description":"Registered kind of entity submitted for approval.","example":"deal_quote"},"entity_id":{"type":"string","minLength":1,"description":"Typed identifier of the submitted entity.","example":"dqt_01krdgeqcxet5s7t44vh8rt9mg"},"submitted_by":{"$ref":"#/components/schemas/UserID"},"submitted_at":{"type":"string","format":"date-time","minLength":1},"status":{"$ref":"#/components/schemas/AdminApprovalRequestStatus"}}},"AdminApprovalRequestSummary":{"readOnly":true,"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AdminApprovalRequestMetadata"}]},"AdminApprovalRequestList":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminApprovalRequestSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminDealQuoteApprovalSendBackCreate":{"type":"object","additionalProperties":false,"required":["expected_request_id","note"],"properties":{"expected_request_id":{"$ref":"#/components/schemas/ApprovalRequestID"},"note":{"type":"string","minLength":1,"pattern":"\\S","maxLength":2000,"description":"Explanation returned to the quote author."}}},"AdminDealQuoteTransition":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","minLength":1,"description":"The transition to apply. `submitted` moves a draft to `approved` when every line clears the margin floor, or to `pending_approval` when any line prices below it. `sent` records that an approved quote went to the customer; `accepted` records the customer's acceptance of a sent quote.\n","enum":["submitted","sent","accepted"],"example":"sent"}}},"DealArtifactID":{"type":"string","minLength":1,"pattern":"^daf_[0-9a-hjkmnp-tv-z]{26}$","example":"daf_01krdgeqcxet5s7t44vh8rt9mg"},"AdminDealArtifact":{"type":"object","additionalProperties":false,"required":["id","deal_id","quote_id","kind","sha256","created_by","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/DealArtifactID"},"deal_id":{"readOnly":true,"$ref":"#/components/schemas/DealID"},"quote_id":{"readOnly":true,"description":"The quote round the artifact was rendered from.","$ref":"#/components/schemas/DealQuoteID"},"kind":{"type":"string","minLength":1,"enum":["annex_pdf","sheet_export"],"readOnly":true,"description":"What kind of document this artifact is."},"sha256":{"type":"string","minLength":1,"readOnly":true,"description":"SHA-256 fingerprint of the stored document bytes."},"created_by":{"readOnly":true,"description":"Staff user who generated the artifact.","$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminDealArtifactList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The deal's artifacts, newest first.","items":{"$ref":"#/components/schemas/AdminDealArtifact"}}}},"AdminDealArtifactCreate":{"type":"object","additionalProperties":false,"required":["quote_id"],"properties":{"quote_id":{"description":"The quote round to render. Must be approved, sent, or accepted.","$ref":"#/components/schemas/DealQuoteID"},"valid_until":{"type":"string","format":"date-time","description":"Instant the quoted prices stop being valid. The annex renders it as a date, so send the start of the day after the last day the offer should hold. Defaults to 30 days from generation.\n"}}},"DealSendID":{"type":"string","minLength":1,"pattern":"^dsn_[0-9a-hjkmnp-tv-z]{26}$","example":"dsn_01krdgeqcxet5s7t44vh8rt9mg"},"AdminDealSend":{"type":"object","additionalProperties":false,"required":["id","deal_id","artifact_id","subject","reply_to","status","created_by","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/DealSendID"},"deal_id":{"readOnly":true,"$ref":"#/components/schemas/DealID"},"artifact_id":{"readOnly":true,"description":"The artifact attached to every recipient's email.","$ref":"#/components/schemas/DealArtifactID"},"subject":{"type":"string","minLength":1,"readOnly":true,"description":"Subject line of the cover email."},"body":{"type":"string","readOnly":true,"description":"The staff-typed cover prose."},"reply_to":{"type":"string","minLength":1,"format":"email","readOnly":true,"description":"The rep's address replies route to."},"status":{"type":"string","minLength":1,"enum":["pending","running","completed","failed"],"readOnly":true,"description":"Lifecycle state of the send run. `failed` means the delivery jobs never reached the queue; nothing is in flight and the run must be recreated."},"created_by":{"readOnly":true,"description":"Staff user who started the send.","$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"AdminDealSendList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The deal's send runs, newest first.","items":{"$ref":"#/components/schemas/AdminDealSend"}}}},"AdminDealSendCreate":{"type":"object","additionalProperties":false,"required":["artifact_id","subject","body","reply_to","emails"],"properties":{"artifact_id":{"description":"The artifact to attach. Its quote must be approved, sent, or accepted.","$ref":"#/components/schemas/DealArtifactID"},"subject":{"type":"string","minLength":1,"maxLength":300,"description":"Subject line of the cover email."},"body":{"type":"string","minLength":1,"maxLength":10000,"description":"Cover email prose. Blank lines separate paragraphs."},"reply_to":{"type":"string","minLength":1,"format":"email","description":"The rep's address replies route to."},"emails":{"type":"array","minItems":1,"maxItems":50,"description":"Recipient addresses; each receives its own copy.","items":{"type":"string","minLength":3,"format":"email"}}}},"AdminDealSendRecipient":{"type":"object","additionalProperties":false,"required":["email","status"],"properties":{"email":{"type":"string","minLength":3,"format":"email","readOnly":true},"status":{"type":"string","minLength":1,"enum":["pending","sent","failed"],"readOnly":true,"description":"Dispatch state for this address. `pending` means the address has not been handed to the mail transport yet, `sent` that it was handed over, `failed` that the handover failed."},"message_id":{"type":["string","null"],"readOnly":true,"description":"Identifier of the dispatched message, or null when not yet sent."},"error":{"type":["string","null"],"readOnly":true,"description":"Reason the address failed, or null when not failed."},"sent_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Timestamp the address was handed to the mail transport, or null when not yet sent."}}},"AdminDealSendDetail":{"type":"object","additionalProperties":false,"required":["send","recipients"],"properties":{"send":{"$ref":"#/components/schemas/AdminDealSend"},"recipients":{"type":"array","description":"Per-recipient dispatch status.","items":{"$ref":"#/components/schemas/AdminDealSendRecipient"}}}},"ApprovalFlowID":{"type":"string","minLength":1,"pattern":"^afl_[0-9a-hjkmnp-tv-z]{26}$","example":"afl_01krdgeqcxet5s7t44vh8rt9mg"},"AdminApprovalFlowMetadata":{"type":"object","required":["id","entity_type","name","allow_reject"],"properties":{"id":{"$ref":"#/components/schemas/ApprovalFlowID"},"entity_type":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"allow_reject":{"type":"boolean"}}},"AdminApprovalFlowSummary":{"readOnly":true,"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AdminApprovalFlowMetadata"},{"$ref":"#/components/schemas/Timestamps"}]},"AdminApprovalFlowList":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminApprovalFlowSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminApprovalFlowStageFields":{"type":"object","required":["position","name","approver_scope","quorum","when_any_tag"],"properties":{"position":{"type":"integer","format":"int32","minimum":1,"description":"Display order only. Stages run in parallel."},"name":{"type":"string","minLength":1},"approver_scope":{"type":"string","minLength":1,"description":"Registered admin scope required at write level to decide this stage."},"quorum":{"type":"integer","format":"int32","minimum":1},"when_any_tag":{"type":"array","items":{"type":"string"},"description":"Applies when any tag matches the submission. Empty means always applies."}}},"ApprovalFlowStageID":{"type":"string","minLength":1,"pattern":"^afs_[0-9a-hjkmnp-tv-z]{26}$","example":"afs_01krdgeqcxet5s7t44vh8rt9mg"},"AdminApprovalFlowStage":{"readOnly":true,"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AdminApprovalFlowStageFields"},{"type":"object","required":["id"],"properties":{"id":{"$ref":"#/components/schemas/ApprovalFlowStageID"}}}]},"AdminApprovalFlow":{"readOnly":true,"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AdminApprovalFlowMetadata"},{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["stages"],"properties":{"stages":{"type":"array","items":{"$ref":"#/components/schemas/AdminApprovalFlowStage"}}}}]},"AdminApprovalFlowStageInput":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AdminApprovalFlowStageFields"}]},"AdminApprovalFlowUpdate":{"type":"object","additionalProperties":false,"required":["name","stages"],"properties":{"name":{"type":"string","minLength":1},"stages":{"type":"array","items":{"$ref":"#/components/schemas/AdminApprovalFlowStageInput"}}}},"AdminApprovalPendingFor":{"type":"string","description":"Staff member whose actionable approval queue to return.","enum":["me"]},"ApprovalRequestStageID":{"type":"string","minLength":1,"pattern":"^aps_[0-9a-hjkmnp-tv-z]{26}$","example":"aps_01krdgeqcxet5s7t44vh8rt9mg"},"AdminApprovalRequestStage":{"readOnly":true,"type":"object","additionalProperties":false,"required":["id","name","approver_scope","quorum","met_at"],"properties":{"id":{"$ref":"#/components/schemas/ApprovalRequestStageID"},"name":{"type":"string","minLength":1,"description":"Display name copied from the flow when the request was submitted."},"approver_scope":{"type":"string","minLength":1,"description":"Admin scope whose write holders may decide this stage.","example":"admin:deals_approve"},"quorum":{"type":"integer","minimum":1,"description":"Number of distinct approvals required to complete the stage."},"met_at":{"type":["string","null"],"format":"date-time","description":"Time the stage reached quorum, or null while unmet."}}},"ApprovalDecisionID":{"type":"string","minLength":1,"pattern":"^apd_[0-9a-hjkmnp-tv-z]{26}$","example":"apd_01krdgeqcxet5s7t44vh8rt9mg"},"AdminApprovalDecisionOutcome":{"type":"string","description":"Decision taken on an approval stage.","minLength":1,"enum":["approve","send_back","reject"]},"AdminApprovalDecision":{"readOnly":true,"type":"object","additionalProperties":false,"required":["id","request_stage_id","user_id","outcome","note","created_at"],"properties":{"id":{"$ref":"#/components/schemas/ApprovalDecisionID"},"request_stage_id":{"$ref":"#/components/schemas/ApprovalRequestStageID"},"user_id":{"$ref":"#/components/schemas/UserID"},"outcome":{"$ref":"#/components/schemas/AdminApprovalDecisionOutcome"},"note":{"type":"string","minLength":0,"description":"Context supplied with the decision, empty when none was supplied."},"created_at":{"type":"string","format":"date-time","minLength":1}}},"AdminApprovalRequest":{"readOnly":true,"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AdminApprovalRequestMetadata"},{"type":"object","required":["tags","object","previous_object","allow_reject","decided_at","decided_by","note","stages","decisions"],"properties":{"tags":{"type":"array","description":"Conditions present when the entity was submitted.","items":{"type":"string","minLength":1},"example":["below_floor"]},"object":{"type":"object","additionalProperties":true,"description":"Immutable entity snapshot supplied by the consuming service. Quote snapshots exclude raw costs and exact margins."},"previous_object":{"type":["object","null"],"additionalProperties":true,"description":"Previous submitted entity snapshot for comparison, or null when none exists. Quote snapshots exclude raw costs and exact margins."},"allow_reject":{"type":"boolean","description":"Whether this request permits a terminal rejection decision."},"decided_at":{"type":["string","null"],"format":"date-time","description":"Time of the terminal decision, or null while pending."},"decided_by":{"description":"Staff member who made the terminal decision, or null while pending.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"note":{"type":"string","minLength":0,"description":"Note recorded by the terminal decision, empty when none was supplied."},"stages":{"type":"array","description":"Approval stages copied from the flow when this request was submitted.","items":{"$ref":"#/components/schemas/AdminApprovalRequestStage"}},"decisions":{"type":"array","description":"Decisions recorded against this request in chronological order.","items":{"$ref":"#/components/schemas/AdminApprovalDecision"}}}}]},"AdminApprovalDecisionCreate":{"type":"object","additionalProperties":false,"required":["stage_id","outcome"],"properties":{"stage_id":{"$ref":"#/components/schemas/ApprovalRequestStageID"},"outcome":{"$ref":"#/components/schemas/AdminApprovalDecisionOutcome"},"note":{"type":"string","maxLength":2000,"description":"Context for the decision. Required with send_back and reject."}},"if":{"properties":{"outcome":{"enum":["send_back","reject"]}},"required":["outcome"]},"then":{"required":["note"],"properties":{"note":{"type":"string","minLength":1,"pattern":"\\S"}}}},"AdminTemporalOpenCounts":{"type":"object","additionalProperties":false,"readOnly":true,"required":["running"],"properties":{"running":{"type":"integer","format":"int64","readOnly":true,"description":"Number of currently running workflow executions."}}},"AdminTemporalFailuresByStatus":{"type":"object","additionalProperties":false,"readOnly":true,"required":["failed","terminated","timed_out"],"properties":{"failed":{"type":"integer","format":"int64","readOnly":true,"description":"Executions that ended with a failure."},"terminated":{"type":"integer","format":"int64","readOnly":true,"description":"Executions that were forcibly terminated."},"timed_out":{"type":"integer","format":"int64","readOnly":true,"description":"Executions that exceeded their schedule-to-close deadline."}}},"AdminTemporalTypeFailure":{"type":"object","additionalProperties":false,"readOnly":true,"required":["workflow_type","failed","terminated","timed_out"],"properties":{"workflow_type":{"type":"string","minLength":1,"readOnly":true,"description":"Workflow type name."},"failed":{"type":"integer","format":"int64","readOnly":true,"description":"Failed executions of this type."},"terminated":{"type":"integer","format":"int64","readOnly":true,"description":"Terminated executions of this type."},"timed_out":{"type":"integer","format":"int64","readOnly":true,"description":"Timed-out executions of this type."}}},"AdminTemporalSummaryFailures":{"type":"object","additionalProperties":false,"readOnly":true,"required":["total","by_status","by_type"],"properties":{"total":{"type":"integer","format":"int64","readOnly":true,"description":"Total failed, terminated, and timed-out executions in the window."},"by_status":{"allOf":[{"$ref":"#/components/schemas/AdminTemporalFailuresByStatus"}],"readOnly":true,"description":"Failure counts broken down by terminal status."},"by_type":{"type":"array","readOnly":true,"description":"Per-workflow-type failure breakdown.","items":{"$ref":"#/components/schemas/AdminTemporalTypeFailure"}}}},"AdminTemporalRegionFailures":{"type":"object","additionalProperties":false,"readOnly":true,"required":["total"],"properties":{"total":{"type":"integer","format":"int64","readOnly":true,"description":"Total failures in this region within the window."}}},"AdminTemporalSummaryRegion":{"type":"object","additionalProperties":false,"required":["region","namespace","open_counts","failures_in_window","degraded"],"properties":{"region":{"type":"string","minLength":1,"readOnly":true,"description":"Region code."},"namespace":{"type":"string","minLength":1,"readOnly":true,"description":"Temporal namespace for this region."},"open_counts":{"allOf":[{"$ref":"#/components/schemas/AdminTemporalOpenCounts"}],"readOnly":true,"description":"Counts of currently open executions in this region."},"failures_in_window":{"allOf":[{"$ref":"#/components/schemas/AdminTemporalRegionFailures"}],"readOnly":true,"description":"Failure totals for this region within the window."},"degraded":{"type":"boolean","readOnly":true,"description":"True if this region could not be reached during the query."}}},"AdminTemporalSummary":{"type":"object","additionalProperties":false,"required":["as_of","window","open_counts","failures_in_window","by_region","degraded"],"properties":{"as_of":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time at which these counts were collected."},"window":{"type":"string","minLength":1,"readOnly":true,"description":"Failure window that was requested."},"open_counts":{"allOf":[{"$ref":"#/components/schemas/AdminTemporalOpenCounts"}],"readOnly":true,"description":"Counts of currently open executions across all regions."},"failures_in_window":{"allOf":[{"$ref":"#/components/schemas/AdminTemporalSummaryFailures"}],"readOnly":true,"description":"Failure statistics aggregated over the window."},"by_region":{"type":"array","readOnly":true,"description":"Per-region breakdown of counts.","items":{"$ref":"#/components/schemas/AdminTemporalSummaryRegion"}},"degraded":{"type":"boolean","readOnly":true,"description":"True if one or more regions could not be reached; partial data may be returned."}}},"AdminTemporalSearchAttributes":{"type":"object","additionalProperties":false,"readOnly":true,"required":["organization_id","workspace_id","domain"],"properties":{"organization_id":{"type":["string","null"],"readOnly":true,"description":"Organization identifier, if set on this workflow."},"workspace_id":{"type":["string","null"],"readOnly":true,"description":"Workspace identifier, if set on this workflow."},"domain":{"type":["string","null"],"readOnly":true,"description":"Sending domain, if set on this workflow."}}},"AdminTemporalWorkflowExecution":{"type":"object","additionalProperties":false,"required":["region","workflow_id","run_id","workflow_type","status","start_time","close_time","execution_duration_ms","task_queue","search_attributes","cloud_url"],"properties":{"region":{"type":"string","minLength":1,"readOnly":true,"description":"Region where this workflow ran."},"workflow_id":{"type":"string","minLength":1,"readOnly":true,"description":"Unique workflow identifier."},"run_id":{"type":"string","minLength":1,"readOnly":true,"description":"Run identifier for this execution attempt."},"workflow_type":{"type":"string","minLength":1,"readOnly":true,"description":"Workflow type name."},"status":{"type":"string","minLength":1,"readOnly":true,"description":"Current execution status.","enum":["running","completed","failed","canceled","terminated","continued_as_new","timed_out"]},"start_time":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time when this execution started."},"close_time":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Time when this execution closed. Null for running executions."},"execution_duration_ms":{"type":"integer","format":"int64","readOnly":true,"description":"Elapsed execution time in milliseconds. Zero for still-running executions."},"task_queue":{"type":"string","minLength":1,"readOnly":true,"description":"Task queue this workflow was dispatched to."},"search_attributes":{"allOf":[{"$ref":"#/components/schemas/AdminTemporalSearchAttributes"}],"readOnly":true,"description":"Bird tenant-correlation attributes set on this workflow."},"cloud_url":{"type":["string","null"],"readOnly":true,"description":"Link to view this execution in the Temporal Cloud console. Null when no console URL is configured."}}},"AdminTemporalWorkflowList":{"type":"object","additionalProperties":false,"required":["data","as_of","degraded"],"properties":{"data":{"type":"array","readOnly":true,"description":"Workflow executions matching the query.","items":{"$ref":"#/components/schemas/AdminTemporalWorkflowExecution"}},"as_of":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time at which the list was fetched."},"degraded":{"type":"boolean","readOnly":true,"description":"True if the region could not be reached; results may be incomplete."}}},"AdminTemporalPendingActivity":{"type":"object","additionalProperties":false,"readOnly":true,"required":["activity_type","state","attempt","last_failure","scheduled_time","last_started_time","expiration_time"],"properties":{"activity_type":{"type":"string","minLength":1,"readOnly":true,"description":"Activity type name."},"state":{"type":"string","minLength":1,"readOnly":true,"description":"Current state of this activity (scheduled, started, etc.)."},"attempt":{"type":"integer","format":"int32","readOnly":true,"description":"Number of times this activity has been attempted."},"last_failure":{"type":["string","null"],"readOnly":true,"description":"Error message from the most recent failed attempt, if any."},"scheduled_time":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time this activity was scheduled."},"last_started_time":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time this activity last started execution."},"expiration_time":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time after which this activity will time out."}}},"AdminTemporalPendingChild":{"type":"object","additionalProperties":false,"readOnly":true,"required":["workflow_id","run_id","workflow_type"],"properties":{"workflow_id":{"type":"string","minLength":1,"readOnly":true,"description":"Child workflow identifier."},"run_id":{"type":"string","minLength":1,"readOnly":true,"description":"Child run identifier."},"workflow_type":{"type":"string","minLength":1,"readOnly":true,"description":"Child workflow type name."}}},"AdminTemporalFailure":{"type":"object","additionalProperties":false,"readOnly":true,"required":["type","message","stack_trace"],"properties":{"type":{"type":"string","minLength":1,"readOnly":true,"description":"Failure type."},"message":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable failure message."},"stack_trace":{"type":"string","minLength":1,"readOnly":true,"description":"Stack trace from the failing activity or workflow."}}},"AdminTemporalWorkflowDetail":{"type":"object","additionalProperties":false,"required":["region","workflow_id","run_id","workflow_type","status","start_time","close_time","execution_duration_ms","task_queue","search_attributes","cloud_url","history_length","pending_activities","pending_children","failure","degraded"],"properties":{"region":{"type":"string","minLength":1,"readOnly":true,"description":"Region where this workflow ran."},"workflow_id":{"type":"string","minLength":1,"readOnly":true,"description":"Unique workflow identifier."},"run_id":{"type":"string","minLength":1,"readOnly":true,"description":"Run identifier for this execution attempt."},"workflow_type":{"type":"string","minLength":1,"readOnly":true,"description":"Workflow type name."},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["running","completed","failed","canceled","terminated","continued_as_new","timed_out"],"description":"Current execution status."},"start_time":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time when this execution started."},"close_time":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Time when this execution closed. Null for running executions."},"execution_duration_ms":{"type":"integer","format":"int64","readOnly":true,"description":"Elapsed execution time in milliseconds."},"task_queue":{"type":"string","minLength":1,"readOnly":true,"description":"Task queue this workflow was dispatched to."},"search_attributes":{"allOf":[{"$ref":"#/components/schemas/AdminTemporalSearchAttributes"}],"readOnly":true,"description":"Bird tenant-correlation attributes set on this workflow."},"cloud_url":{"type":["string","null"],"readOnly":true,"description":"Link to view this execution in the Temporal Cloud console. Null when no console URL is configured."},"history_length":{"type":"integer","format":"int64","readOnly":true,"description":"Number of history events recorded for this execution."},"pending_activities":{"type":"array","readOnly":true,"description":"Activities that are currently scheduled or started.","items":{"$ref":"#/components/schemas/AdminTemporalPendingActivity"}},"pending_children":{"type":"array","readOnly":true,"description":"Child workflow executions that have not yet completed.","items":{"$ref":"#/components/schemas/AdminTemporalPendingChild"}},"failure":{"readOnly":true,"description":"Terminal failure details. Present only for closed executions that ended with an error.","oneOf":[{"$ref":"#/components/schemas/AdminTemporalFailure"},{"type":"null"}]},"degraded":{"type":"boolean","readOnly":true,"description":"True if the region could not be reached, so this detail may be incomplete."}}},"AdminTemporalSchedule":{"type":"object","additionalProperties":false,"required":["region","schedule_id","workflow_type","paused","note","next_run_times","recent_action_times","cloud_url"],"properties":{"region":{"type":"string","minLength":1,"readOnly":true,"description":"Region where this schedule is configured."},"schedule_id":{"type":"string","minLength":1,"readOnly":true,"description":"Unique schedule identifier."},"workflow_type":{"type":"string","minLength":1,"readOnly":true,"description":"Workflow type triggered by this schedule."},"paused":{"type":"boolean","readOnly":true,"description":"Whether this schedule is currently paused."},"note":{"type":["string","null"],"readOnly":true,"description":"Operator note attached to this schedule, if any."},"next_run_times":{"type":"array","readOnly":true,"description":"Upcoming scheduled execution times.","items":{"type":"string","format":"date-time"}},"recent_action_times":{"type":"array","readOnly":true,"description":"Times at which this schedule recently triggered a workflow.","items":{"type":"string","format":"date-time"}},"cloud_url":{"type":["string","null"],"readOnly":true,"description":"Link to view this schedule in the Temporal Cloud console. Null when no console URL is configured."}}},"AdminTemporalScheduleList":{"type":"object","additionalProperties":false,"required":["data","as_of","degraded"],"properties":{"data":{"type":"array","readOnly":true,"description":"Schedules across all configured regions.","items":{"$ref":"#/components/schemas/AdminTemporalSchedule"}},"as_of":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time at which the list was fetched."},"degraded":{"type":"boolean","readOnly":true,"description":"True if one or more regions could not be reached; results may be incomplete."}}},"AdminLitequeueTube":{"type":"object","additionalProperties":false,"description":"One litequeue tube's job counts in a single region, summed across that region's nodes.","required":["tube","group","region","queued","reserved","buried","content_scope"],"properties":{"tube":{"type":"string","minLength":1,"readOnly":true,"description":"Full tube name, including the per-workspace suffix where the tube carries one."},"group":{"type":"string","minLength":1,"readOnly":true,"description":"Tube family this tube belongs to: the tube name with its trailing workspace identifier removed, or the tube name itself when it carries none. A disposition addresses a family as `<group>-*`.\n"},"region":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Region"}],"description":"Region whose nodes were counted."},"queued":{"type":"integer","format":"int64","readOnly":true,"description":"Jobs waiting to be reserved."},"reserved":{"type":"integer","format":"int64","readOnly":true,"description":"Jobs currently held by a consumer. These are in flight, and no disposition on this page touches them."},"buried":{"type":"integer","format":"int64","readOnly":true,"description":"Jobs that exhausted their attempts, or that a consumer buried because it never retries. This is the dead-letter population the dispositions act on.\n"},"content_scope":{"type":["string","null"],"minLength":1,"readOnly":true,"example":"admin:sms_content","description":"Permission scope that authorizes reading the job bodies in this tube. Null when the tube family maps to no channel, in which case no scope authorizes reading them.\n"}}},"AdminLitequeueTubeList":{"type":"object","additionalProperties":false,"description":"Every tube in one region that currently holds a job. A tube holding nothing is absent rather than zero, because the nodes only report tubes they still hold. Not paginated: the node status call returns the whole set in one read.\n","required":["data","region"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminLitequeueTube"}},"region":{"allOf":[{"$ref":"#/components/schemas/Region"}],"description":"Region these counts were read from. Kept alongside each row's own `region` because it answers a different question: an empty `data` still names the region that reported nothing, which a row field cannot.\n"}}},"AdminLitequeueBuriedJob":{"type":"object","additionalProperties":false,"description":"One buried job's metadata, without its payload.","required":["job_id","tube","node","region","reserves","created_at","buried_at","body_bytes"],"properties":{"job_id":{"type":"string","minLength":32,"maxLength":32,"pattern":"^[0-9a-f]{32}$","readOnly":true,"description":"Job identifier, lowercase hex. Not a TypeID, because litequeue stores raw job ids."},"tube":{"type":"string","minLength":1,"readOnly":true,"description":"Tube holding the job."},"node":{"type":"string","minLength":1,"readOnly":true,"description":"Address of the node holding this job. Each node keeps its own local database, so a per-job disposition must address the node the job was listed from.\n"},"region":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Region"}]},"reserves":{"type":"integer","readOnly":true,"description":"Times the job was reserved before it buried, which is its attempt count."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the job was first enqueued."},"buried_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the job last changed state, which for a buried job is when it buried."},"body_bytes":{"type":"integer","readOnly":true,"description":"Compressed size of the job's stored body. Present whether or not the body itself may be read."}}},"AdminLitequeueBuriedJobList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminLitequeueBuriedJob"}},"partial_nodes":{"type":["array","null"],"items":{"type":"string"},"readOnly":true,"description":"Nodes that failed to answer this read. The jobs listed are still accurate for the nodes that did answer, so the page remains usable, but it is incomplete and a count taken from it undercounts. Null when every node answered.\n"}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminLitequeueBuriedHistoryPoint":{"type":"object","additionalProperties":false,"description":"One sample of a buried-job series.","required":["at","value"],"properties":{"at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Sample timestamp, UTC."},"value":{"type":"number","format":"double","readOnly":true,"description":"Buried jobs at that moment, as the metrics backend recorded it."}}},"AdminLitequeueBuriedHistorySeries":{"type":"object","additionalProperties":false,"description":"One labelled buried-job series.","required":["tube","points"],"properties":{"tube":{"type":"string","minLength":1,"readOnly":true,"description":"The tube or tube group this series covers. `total` is the region-wide aggregate."},"points":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminLitequeueBuriedHistoryPoint"}}}},"AdminLitequeueBuriedHistory":{"type":"object","additionalProperties":false,"description":"Buried-job counts over time for one region, read from that region's metrics backend.\nThis is history, not current state. The metric keeps reporting a stale value for a few minutes after a purge, and stops being reported at all once a tube family empties, so a series that ends above zero does not mean jobs remain. The tube list is the authoritative answer to what is buried now.\n","required":["region","series","available"],"properties":{"region":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Region"}],"description":"Region this history covers."},"series":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminLitequeueBuriedHistorySeries"}},"available":{"type":"boolean","readOnly":true,"description":"False when this region has no metrics backend configured, in which case `series` is empty. Distinguishes \"no history here\" from \"no buried jobs\", which a bare empty series cannot.\n"}}},"AdminLitequeueJobBody":{"type":"object","additionalProperties":false,"description":"A buried job's decoded payload, returned to staff authorized for that channel's content. The tube family decides which scope authorizes the read, and a family mapping to no channel has no authorizing scope and is refused outright.\n","required":["job_id","tube","node","body"],"properties":{"job_id":{"type":"string","minLength":32,"maxLength":32,"pattern":"^[0-9a-f]{32}$","readOnly":true,"description":"The job whose body this is."},"tube":{"type":"string","minLength":1,"readOnly":true,"description":"Tube holding the job, which is what selected the authorizing scope."},"node":{"type":"string","minLength":1,"readOnly":true,"description":"Node the body was read from."},"body":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"The job's payload, decompressed and returned verbatim. This is customer content, such as a send's recipients and message text, so the read is audit-logged.\n"}}},"AdminLitequeueRestoreRequest":{"type":"object","additionalProperties":false,"description":"The tube or family to restore. There is no job-id form: the queue exposes un-burying only per tube, so a restore always covers every buried job the pattern matches.\n","required":["tube"],"properties":{"tube":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9._-]+\\*?$","description":"An exact tube name, or a prefix followed by `*` to match a whole tube family. A bare `*` is refused, because it matches every tube on the node: that is a fleet-wide action rather than one an operator can mean by accident.\n","example":"email-spam-scan-*"}}},"AdminLitequeueActionResult":{"type":"object","additionalProperties":false,"description":"What the action actually did. A non-empty `failed_nodes` alongside a non-zero `affected` is a real outcome rather than a contradiction, because each node holds its own database and they settle independently.\n","required":["affected","failed_nodes"],"properties":{"affected":{"type":"integer","format":"int64","readOnly":true,"description":"Jobs restored or purged, summed over the nodes that answered."},"failed_nodes":{"type":"array","items":{"type":"string"},"readOnly":true,"description":"Errors from nodes that did not complete the action. When this is non-empty the action was partial: some of the tube's jobs are still buried, and repeating it is safe.\n"}}},"AdminLitequeuePurgeTube":{"type":"object","additionalProperties":false,"description":"Purge every buried job in a tube or family.","required":["tube"],"properties":{"tube":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9._-]+\\*?$","description":"An exact tube name, or a prefix followed by `*` to match a whole tube family. A bare `*` is refused, because it matches every tube on the node.\n","example":"email-spam-scan-*"}}},"AdminLitequeuePurgeJobs":{"type":"object","additionalProperties":false,"description":"Purge named jobs on one node. Use this to dispose of a single job as a canary before acting on its whole tube. An id that is not buried on that node is refused rather than deleted.\n","required":["node","job_ids"],"properties":{"node":{"type":"string","minLength":1,"description":"Node holding the jobs, as reported by the buried-jobs list. Each node keeps its own database."},"job_ids":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string","minLength":32,"maxLength":32,"pattern":"^[0-9a-f]{32}$"},"description":"The jobs to purge, all of them on `node`."}}},"AdminLitequeuePurgeRequest":{"oneOf":[{"$ref":"#/components/schemas/AdminLitequeuePurgeTube"},{"$ref":"#/components/schemas/AdminLitequeuePurgeJobs"}],"description":"What to purge, in exactly one of two forms: a tube pattern, or named jobs on one node. The two take different paths, so a request carrying both could not report a count that says what happened.\nThere is deliberately no status field. Only buried jobs are ever purged, fixed server-side rather than defaulted, so no caller can widen it to reach jobs that are queued or in flight. The job-id form confirms each id is buried on its node before deleting it, so it is not a way round that guarantee.\n"},"AdminLqschedJobStatus":{"type":"string","minLength":1,"enum":["pending","scheduled","dispatching","dispatched","dispatch_failed","canceled"],"description":"Scheduled-job lifecycle status. `pending` is waiting for its producer to finish preparation, `scheduled` is waiting for its due time, `dispatching` is mid-handoff, `dispatched` has been handed to its consumer, `dispatch_failed` could not finish preparation or dispatch, `canceled` was canceled before dispatch.\n"},"LiteQueueScheduledJobID":{"type":"string","minLength":1,"pattern":"^lqs_[0-9a-hjkmnp-tv-z]{26}$","example":"lqs_01krdgeqcxet5s7t44vh8rt9mg"},"AdminLqschedJob":{"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","additionalProperties":false,"description":"One scheduled background job, without its payload.","required":["id","key","status","due_at","tube","attempts","max_attempts","lifecycle_tube","published_status","labels","region"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/LiteQueueScheduledJobID"}],"description":"Unique job identifier."},"key":{"type":"string","minLength":1,"readOnly":true,"description":"Producer correlation key. For scheduled emails this embeds the workspace and message identifiers. At most one live (pending, scheduled, or dispatching) job exists per key at any time.\n"},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminLqschedJobStatus"}]},"due_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Time the job is (or was) due to dispatch."},"tube":{"type":"string","minLength":1,"readOnly":true,"description":"Destination queue the job dispatches onto."},"attempts":{"type":"integer","readOnly":true,"description":"Dispatch attempts made so far."},"max_attempts":{"type":"integer","readOnly":true,"description":"Dispatch-attempt ceiling before the job is marked failed."},"lifecycle_tube":{"type":["string","null"],"readOnly":true,"description":"Status-stream queue this job's lifecycle transitions are published to. Null when the producer requested no stream."},"published_status":{"readOnly":true,"description":"Lifecycle publication watermark. Pending is the preparation sentinel; no pending event is emitted, and the first event reports the next status. Null means a directly scheduled job has not published its scheduled event. It remains null when lifecycle publishing is disabled.","oneOf":[{"$ref":"#/components/schemas/AdminLqschedJobStatus"},{"type":"null"}]},"labels":{"type":["object","null"],"additionalProperties":{"type":"string"},"readOnly":true,"description":"Producer-set correlation labels (for example the organization and workspace identifiers). Null when the job carries none."},"region":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Region"}],"description":"Region this job is stored in."}}}]},"AdminLqschedJobsStatusCounts":{"type":"object","additionalProperties":false,"description":"Number of jobs in each status. Empty statuses have a count of zero.","required":["pending","scheduled","dispatching","dispatched","dispatch_failed","canceled"],"properties":{"pending":{"type":"integer","format":"int64","description":"Jobs waiting for their producer to finish preparation."},"scheduled":{"type":"integer","format":"int64","description":"Jobs waiting for their due time."},"dispatching":{"type":"integer","format":"int64","description":"Jobs mid-handoff to their consumer."},"dispatched":{"type":"integer","format":"int64","description":"Jobs handed to their consumer."},"dispatch_failed":{"type":"integer","format":"int64","description":"Jobs that could not finish preparation or dispatch."},"canceled":{"type":"integer","format":"int64","description":"Jobs canceled before dispatch."}}},"AdminLqschedJobList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminLqschedJob"}},"counts":{"oneOf":[{"$ref":"#/components/schemas/AdminLqschedJobsStatusCounts"},{"type":"null"}],"readOnly":true,"description":"Per-status job counts under the request's filters (excluding `status`, the counted dimension). Present only when `include_counts=true` was passed; otherwise null."}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"NumberType":{"type":"string","minLength":1,"x-extensible-enum":["mobile","local","national","short_code","short_code_fteu","toll_free"],"description":"Physical type of a phone number. New number types may be added over time, so treat unrecognized values as supported types rather than errors."},"NumberCapability":{"type":"string","minLength":1,"x-extensible-enum":["sms","mms","voice"],"description":"A capability supported by a phone number. New capabilities may be added over time, so treat unrecognized values as supported capabilities rather than errors."},"NumberOwnership":{"type":"object","additionalProperties":false,"required":["satisfied","next"],"description":"Where this number stands with the ownership paperwork its country requires before it may carry traffic. Present only for a number whose country requires any, so its absence means no paperwork was ever asked for and this number is unconditionally usable. Absent as well when the requirement cannot be established right now, since reporting either answer would state something about your paperwork that has not been checked.\n","properties":{"submission_id":{"allOf":[{"$ref":"#/components/schemas/ComplianceSubmissionID"}],"readOnly":true,"description":"The most recent ownership submission for this number, including after approval. Users with compliance read access can view the filed answers and their review status from the number's details in the dashboard. This may be a newer filing than the one that cleared the number for use. Absent when no submission was found or submission progress could not be read."},"satisfied":{"type":"boolean","readOnly":true,"description":"Whether the paperwork is accepted. Read `next` for what advances it while this is false. Whether sending is currently refused is reported by `blocked_at` instead: a number bought before its country asked for anything is unsatisfied and still usable until a review says otherwise.\n"},"blocked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the number stopped being able to carry traffic, and null while it can. Always null when `satisfied` is true, but null does not imply it: a number whose country began asking after you bought it is usable with its paperwork still outstanding. A number can also arrive blocked, and one that was usable can be blocked again if its approval is withdrawn.\n"},"next":{"type":"array","readOnly":true,"description":"What you do about it, in the order to do it. Empty only when `satisfied` is true, so while anything is outstanding there is always at least one step. When what you already sent is being reviewed and nothing is needed from you, that step has kind `wait` and says so. Re-read it after each call rather than caching the first list you saw.\n","items":{"$ref":"#/components/schemas/NextAction"}}}},"Number":{"type":"object","additionalProperties":false,"required":["id","kind","number","country_code","number_type","capabilities","status","allocated_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AllocatedNumberID"}],"description":"Identifier of this allocated number. Pass it as `number_id` to read this number, or to release it when kind is dedicated."},"kind":{"type":"string","minLength":1,"readOnly":true,"enum":["dedicated","shared"],"description":"How this number is allocated. `dedicated` belongs to your workspace and is billed as a subscription. `shared` is provided through Bird-managed shared infrastructure and is not owned or billed as a workspace subscription."},"number":{"type":"string","minLength":1,"readOnly":true,"description":"Phone number in E.164 format."},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"readOnly":true},"number_type":{"allOf":[{"$ref":"#/components/schemas/NumberType"}],"readOnly":true,"description":"Physical type of this phone number."},"capabilities":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/NumberCapability"},"description":"Capabilities supported by this number."},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["active","pending_ownership_registration","released"],"description":"The allocation and ownership-approval status of this number.\n\n- `active` means this number is allocated to your workspace and usable.\n- `pending_ownership_registration` means this number is allocated to your workspace and billed,\n  but outbound SMS and both inbound and outbound voice calls are blocked until the ownership paperwork\n  its country requires is accepted. This ownership status does not gate inbound SMS or WhatsApp.\n  Read `ownership.next` for what advances it, and re-read later if\n  `ownership` is momentarily `null`.\n- `released` means this number is no longer allocated to your workspace.\n\nAn allocated number is not always enough to send from it: some destination\ncountries also require an approved registration for the sender.\n"},"allocated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this number was allocated to your workspace."},"released_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this number was released. `null` while it is still allocated to your workspace."},"ownership":{"readOnly":true,"description":"Where this number stands with the ownership paperwork its country requires. `null` when the country requires none, which is the usual case: a number with no `ownership` object is usable as soon as it is allocated. Also `null` when that standing cannot be established right now; `status` still reads `pending_ownership_registration` while the number is blocked, so re-read this field rather than caching its absence. We manage the paperwork for shared short codes, so this field is always `null` for them.\n","oneOf":[{"$ref":"#/components/schemas/NumberOwnership"},{"type":"null"}]}}},"NumberList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Number"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AvailableNumber":{"type":"object","readOnly":true,"additionalProperties":false,"required":["number","country_code","number_type","capabilities","ownership_registration_required"],"properties":{"number":{"type":"string","minLength":1,"description":"Phone number in E.164 format."},"country_code":{"$ref":"#/components/schemas/CountryCode"},"number_type":{"allOf":[{"$ref":"#/components/schemas/NumberType"}],"description":"Physical type of this phone number."},"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/NumberCapability"},"description":"Capabilities supported by this number."},"ownership_registration_required":{"type":"boolean","description":"Whether ownership paperwork must be approved before outbound SMS and voice use. Customer availability accounts for organization exemptions; admin supplier searches report the general country and number-type requirement. You can acquire the number, including Bird stock, and submit paperwork afterward. Any setup fee is charged during purchase. Monthly billing starts at assignment even while approval is pending; assignment may follow completion of a pending supplier order."}}},"AvailableNumberList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AvailableNumber"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"NumbersOrderStatus":{"type":"string","minLength":1,"x-extensible-enum":["charging","ordering","pending","completed","failed"],"description":"Lifecycle state of a number purchase order:\n\n- `charging`: Securing funds.\n- `ordering`: Placing the order with the carrier.\n- `pending`: The carrier accepted the order and is provisioning the number.\n- `completed`: Your workspace owns the number.\n- `failed`: The purchase did not complete.\n\nA setup fee already charged is non-refundable. Contact support about a failed\norder."},"NumbersOrderID":{"type":"string","minLength":1,"pattern":"^nor_[0-9a-hjkmnp-tv-z]{26}$","example":"nor_01krdgeqcxet5s7t44vh8rt9mg"},"NumbersOrder":{"type":"object","additionalProperties":false,"required":["id","number","country_code","number_type","status","created_at","updated_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/NumbersOrderID"}],"description":"Identifier of this purchase order."},"number":{"type":"string","minLength":1,"readOnly":true,"description":"The number being acquired, in E.164 format."},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"readOnly":true},"number_type":{"allOf":[{"$ref":"#/components/schemas/NumberType"}],"readOnly":true,"description":"Physical type of the number being acquired."},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/NumbersOrderStatus"}]},"number_id":{"readOnly":true,"description":"Identifier of the number this order produced, set when `status` is `completed`. Pass it as `number_id` to `GET /v1/numbers/{number_id}` or `DELETE /v1/numbers/{number_id}`. `null` until the order completes.\n","oneOf":[{"$ref":"#/components/schemas/NumbersDedicatedAllocationID"},{"type":"null"}]},"failure_reason":{"type":["string","null"],"readOnly":true,"description":"Human-readable reason the purchase failed. `null` unless status is failed. An order can fail some time after it was created, so `updated_at` tells you when the failure was recorded rather than when the order was placed.\n"},"completed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the purchase completed and the number became owned (status completed). `null` for orders still in progress or failed.\n"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"NumbersOrderList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/NumbersOrder"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"NumbersOrderCompliance":{"type":"object","additionalProperties":false,"required":["answers"],"description":"Ownership paperwork for a number being ordered, where its country requires any. Optional always: the order completes and the number is yours either way, and supplying this only decides whether it can carry traffic sooner. Read GET /v1/compliance/requirements for the number to learn what is asked for, or file it after the order with POST /v1/compliance/submissions. Filing this cannot fail your order: if it is refused, the number is still yours and you can file again.\n","properties":{"answers":{"type":"array","minItems":1,"description":"Every answer at once, in the same shape POST /v1/compliance/submissions takes. There is no draft to fill in over several calls, so send a complete set.\n","example":[{"key":"business_registration_number","value":"90012768"}],"items":{"$ref":"#/components/schemas/ComplianceAnswer"}},"identity_id":{"allOf":[{"$ref":"#/components/schemas/ComplianceIdentityID"}],"description":"An existing party this number is held for. It fills in the business details you left out from what that party already holds, and an answer approved here is written back onto it for next time. An id belonging to another organization is rejected."}}},"NumbersOrderCreate":{"type":"object","additionalProperties":false,"required":["number"],"properties":{"number":{"type":"string","minLength":1,"description":"The number to acquire, in E.164 format, as returned by `GET /v1/numbers/available`.","example":"+18005550100"}}},"AdminNumbersSupplierConnectionStatus":{"type":"string","minLength":1,"enum":["unchecked","no_integration","not_configured","unreachable","connected"],"description":"Whether Bird can reach this supplier's integration, as of the last periodic check rather than of this request. unchecked is the state before the first check. no_integration means Bird has no integration for this supplier, which is the state for most suppliers and not an error. not_configured means an integration exists but this environment holds no credentials for it. connected means the last check reached the supplier and its credential was accepted, which says nothing about account balance or about which operations the supplier allows. unreachable means the last check did not get through.\n","example":"connected"},"NumbersSupplierID":{"type":"string","minLength":1,"pattern":"^nsp_[0-9a-hjkmnp-tv-z]{26}$","example":"nsp_01krdgeqcxet5s7t44vh8rt9mg"},"AdminNumbersSupplierSlug":{"type":"string","minLength":1,"maxLength":40,"pattern":"^[a-z0-9]+(_[a-z0-9]+)*$","description":"Stable identifier a voice routing rule names this supplier by: lowercase words joined by single underscores. It names the supplier ROW, not the vendor, so a vendor sold through two products is two suppliers (`inteliquent_tollfree` and `inteliquent_longcode`). Immutable once set, because a stored rule expression carries this text rather than the supplier's id.","example":"didww"},"AdminNumbersSupplierConnection":{"type":"object","readOnly":true,"additionalProperties":false,"description":"The result of the last periodic check of this supplier's integration, which uses a single read-only call and never changes anything at the supplier. Always present; staff cannot set it. Which of the remaining fields carry a value is decided by status.\n","required":["status","checked_at","failing_since","detail"],"properties":{"status":{"$ref":"#/components/schemas/AdminNumbersSupplierConnectionStatus"},"checked_at":{"type":["string","null"],"format":"date-time","description":"When the sweep last checked this supplier. Null before the sweep has run."},"failing_since":{"type":["string","null"],"format":"date-time","description":"When this supplier first started failing its probe, held unchanged across consecutive failures. Null except while status is unreachable.\n"},"detail":{"type":["string","null"],"maxLength":500,"description":"Categorical detail for the current status, never the vendor's own error text: the unset credential family for not_configured, the failure reason for unreachable, null or \"rate limited\" for connected, and always null for every other status.\n"}},"oneOf":[{"properties":{"status":{"const":"unchecked"},"checked_at":{"not":{"type":"string"}},"failing_since":{"not":{"type":"string"}},"detail":{"not":{"type":"string"}}}},{"properties":{"status":{"const":"no_integration"},"checked_at":{"type":"string","format":"date-time"},"failing_since":{"not":{"type":"string"}},"detail":{"not":{"type":"string"}}}},{"properties":{"status":{"const":"not_configured"},"checked_at":{"type":"string","format":"date-time"},"failing_since":{"not":{"type":"string"}},"detail":{"type":"string","maxLength":500}}},{"properties":{"status":{"const":"connected"},"checked_at":{"type":"string","format":"date-time"},"failing_since":{"not":{"type":"string"}},"detail":{"oneOf":[{"not":{"type":"string"}},{"const":"rate limited"}]}}},{"properties":{"status":{"const":"unreachable"},"checked_at":{"type":"string","format":"date-time"},"failing_since":{"type":"string","format":"date-time"},"detail":{"type":"string","maxLength":500}}}]},"AdminNumbersSupplier":{"type":"object","additionalProperties":false,"required":["id","name","created_at","updated_at","connection"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/NumbersSupplierID"},"name":{"type":"string","minLength":1,"description":"Name of the number supplier."},"slug":{"oneOf":[{"$ref":"#/components/schemas/AdminNumbersSupplierSlug"},{"type":"null"}],"description":"Names this supplier in a voice routing rule. Null on rows created before slugs existed; name one with a PATCH."},"dashboard_url":{"type":["string","null"],"description":"URL of the supplier management dashboard."},"contact_email":{"type":["string","null"],"description":"General contact email for this supplier."},"cancellation_email":{"type":["string","null"],"description":"Email address for number cancellation requests to this supplier."},"order_process":{"type":["string","null"],"description":"Notes on how to order numbers from this supplier."},"operations":{"type":"array","readOnly":true,"description":"What this supplier's integration can actually do, derived from the adapter wired for it. Empty for a supplier with no integration at all, which is most of them. The two that decide how numbers can be bought are `order_named`, buying a number somebody picked, and `order_bulk`, buying a count of a market and letting the supplier pick; a supplier can support one, both or neither.\n","items":{"type":"string","x-extensible-enum":["search","lookup_single","hold","order","order_named","order_bulk","disconnect","routing_config","compliance_submit","porting_submit"]}},"billing_currency":{"description":"ISO 4217 code this supplier's own amounts are in. Declared here because a carrier API reports a price without ever naming its currency; a procured row captures whatever is declared at the time it is created.","oneOf":[{"$ref":"#/components/schemas/CurrencyCode"},{"type":"null"}]},"connection":{"readOnly":true,"$ref":"#/components/schemas/AdminNumbersSupplierConnection"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminNumbersSupplierList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminNumbersSupplier"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminNumbersSupplierCreate":{"type":"object","additionalProperties":false,"required":["name","slug"],"properties":{"name":{"type":"string","minLength":1},"slug":{"$ref":"#/components/schemas/AdminNumbersSupplierSlug"},"dashboard_url":{"type":["string","null"]},"contact_email":{"type":["string","null"]},"cancellation_email":{"type":["string","null"]},"order_process":{"type":["string","null"]},"billing_currency":{"allOf":[{"$ref":"#/components/schemas/CurrencyCode"}],"description":"ISO 4217 code this supplier's own amounts are in. Declared here because a carrier API reports a price without ever naming its currency; a procured row captures whatever is declared at the time it is created."}}},"AdminNumbersSupplierUpdate":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":1},"slug":{"allOf":[{"$ref":"#/components/schemas/AdminNumbersSupplierSlug"}],"description":"Names this supplier in a voice routing rule. Accepted only while the supplier has none: rows predating the field need naming, and a second write would strand every rule already carrying the old text."},"dashboard_url":{"type":["string","null"]},"contact_email":{"type":["string","null"]},"cancellation_email":{"type":["string","null"]},"order_process":{"type":["string","null"]},"billing_currency":{"description":"ISO 4217 code this supplier's own amounts are in. Declared here because a carrier API reports a price without ever naming its currency; a procured row captures whatever is declared at the time it is created.","oneOf":[{"$ref":"#/components/schemas/CurrencyCode"},{"type":"null"}]}}},"AdminNumberPrefix":{"type":"string","pattern":"^[0-9]{1,10}$","description":"Digits right after the country dial code, with no country dial code and no national dialing prefix such as a leading 0."},"AdminNumbersSupplierAvailableList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Numbers the supplier holds for sale right now, as its own API reported them. This is a live carrier snapshot, so a number listed here can be gone by the time it is bought. There is no cursor: the supplier's stock has no stable order to page through.\n","items":{"$ref":"#/components/schemas/AvailableNumber"}}}},"AdminNumberScope":{"type":"string","minLength":1,"enum":["public","internal","shared"],"example":"public","description":"Controls how this number is made available. public numbers can be purchased by customers; internal numbers are reserved for manual admin assignment; shared numbers are shortcodes allocated to multiple workspaces simultaneously."},"NumbersInventoryID":{"type":"string","minLength":1,"pattern":"^nin_[0-9a-hjkmnp-tv-z]{26}$","example":"nin_01krdgeqcxet5s7t44vh8rt9mg"},"AdminNumberOutboundRouting":{"type":"string","minLength":1,"enum":["default","supplier"],"description":"Where a call presenting this number must leave by. `default` means the supplier was asked and imposes no constraint, so any carrier the dial plan picks may carry the call. `supplier` is a two-way number: the caller ID is only valid on the routes of the supplier that sold it, which suppliers label variously (DIDWW's portal calls it Local CLI).","example":"supplier"},"AdminVoiceCapabilityMetadata":{"type":"object","additionalProperties":false,"required":["outbound_routing"],"properties":{"outbound_routing":{"$ref":"#/components/schemas/AdminNumberOutboundRouting"}}},"AdminNumberCapabilityMetadata":{"type":"object","additionalProperties":false,"minProperties":1,"description":"Facts a supplier reports about individual capabilities of a number, keyed by capability. A capability is a bare string in `capabilities`, so there is nowhere on that list to carry one.\nUnknown is the MISSING KEY, at exactly two levels: a null document means nobody has asked the supplier about this number at all, and an absent `voice` means nobody has asked about that capability. Neither is the same as an answer of no constraint, which is `voice.outbound_routing: default`. A present capability always carries its answer, so the enum itself has no absent member.","properties":{"voice":{"$ref":"#/components/schemas/AdminVoiceCapabilityMetadata"}}},"AdminNumbersInventoryItem":{"type":"object","additionalProperties":false,"required":["id","supplier_id","number","country_code","number_type","capabilities","capability_metadata","release_strategy","status","scope","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/NumbersInventoryID"},"supplier_id":{"$ref":"#/components/schemas/NumbersSupplierID","description":"ID of the supplier that provides this number."},"supplier_ref":{"type":["string","null"],"description":"Supplier's own reference identifier for this number."},"number":{"type":"string","minLength":1,"description":"Phone number in E.164 format."},"country_code":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code."},"number_type":{"allOf":[{"$ref":"#/components/schemas/NumberType"}],"description":"Physical type of the phone number."},"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/NumberCapability"},"description":"Capabilities of this number."},"capability_metadata":{"oneOf":[{"$ref":"#/components/schemas/AdminNumberCapabilityMetadata"},{"type":"null"}],"description":"Per-capability facts from the supplier. Null when nobody has asked."},"release_strategy":{"type":"string","minLength":1,"enum":["cooldown","release"],"description":"Behavior when a customer's subscription ends."},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["available","reserved","assigned","cooldown","released"],"description":"Current lifecycle state."},"scope":{"$ref":"#/components/schemas/AdminNumberScope"},"tags":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/Tag"},"description":"Operator labels on this stock. They sit on the inventory item rather than on an allocation, so they survive allocation and release. Staff-only: they are never projected onto a customer-facing number."},"organization_id":{"readOnly":true,"description":"Organization that currently holds this number. Set when the number is assigned or in cooldown; null when available.","oneOf":[{"$ref":"#/components/schemas/OrganizationID"},{"type":"null"}]},"workspace_id":{"readOnly":true,"description":"Workspace within the organization that holds the subscription for this number.","oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}]},"allocation_id":{"readOnly":true,"description":"The number's active dedicated allocation. Set while the number is assigned to a single workspace; null otherwise (including while in cooldown or released).","oneOf":[{"$ref":"#/components/schemas/NumbersDedicatedAllocationID"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminNumbersInventoryList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminNumbersInventoryItem"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminNumbersInventoryCreate":{"type":"object","additionalProperties":false,"required":["supplier_id","number","country_code","number_type","capabilities","release_strategy"],"properties":{"supplier_id":{"$ref":"#/components/schemas/NumbersSupplierID"},"supplier_ref":{"type":["string","null"]},"number":{"type":"string","minLength":1},"country_code":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code. Case-insensitive — stored as uppercase."},"number_type":{"allOf":[{"$ref":"#/components/schemas/NumberType"}]},"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/NumberCapability"},"minItems":1,"description":"Capabilities carrier-enabled on this number. A capability the number does not carry makes traffic of that kind unroutable to it."},"capability_metadata":{"allOf":[{"$ref":"#/components/schemas/AdminNumberCapabilityMetadata"}],"description":"Per-capability facts about this number. Omit when nothing is known: an omitted document is not the same as one stating no constraint."},"tags":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/Tag"},"description":"Operator labels to attach to this stock, so a block loaded for one purpose stays identifiable later. Tag names are unique within the set and may not begin with `__bird`, Bird's own namespace; either returns a 422 `validation_error`. Staff-only: they are never projected onto a customer-facing number."},"release_strategy":{"type":"string","minLength":1,"enum":["cooldown","release"]},"scope":{"allOf":[{"$ref":"#/components/schemas/AdminNumberScope"}],"description":"Controls how this number is made available. public numbers can be purchased by customers; internal numbers are reserved for manual admin assignment; shared numbers are shortcodes allocated to multiple workspaces simultaneously. Defaults to public."}}},"NumbersStockThresholdID":{"type":"string","minLength":1,"pattern":"^nst_[0-9a-hjkmnp-tv-z]{26}$","example":"nst_01krdgeqcxet5s7t44vh8rt9mg"},"AdminNumbersStockThreshold":{"readOnly":true,"unevaluatedProperties":false,"allOf":[{"type":"object","description":"An operator minimum for one stock line. A rule that omits `number_type` or `capabilities` applies to every line in the country with that shape; the most specific rule wins, in the order type and capabilities, type, capabilities, country. The key is immutable: to move a rule, delete it and create another.\n","required":["id","country_code","min_available"],"properties":{"id":{"$ref":"#/components/schemas/NumbersStockThresholdID"},"country_code":{"$ref":"#/components/schemas/CountryCode"},"number_type":{"allOf":[{"$ref":"#/components/schemas/NumberType"}],"description":"Absent means the rule covers every number type in the country."},"capabilities":{"type":"array","description":"Exact capability set, sorted. Absent means the rule covers every set.","items":{"$ref":"#/components/schemas/NumberCapability"}},"min_available":{"type":"integer","format":"int64","minimum":0,"maximum":2147483647,"description":"The line is below when its public available count is under this."},"note":{"type":"string","maxLength":500,"description":"Why the line matters, shown on the summary and in the alert."}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminNumbersInventoryStockRow":{"type":"object","additionalProperties":false,"description":"Stock held for one country, number type, and capability set, optionally per scope. Released and surrendered numbers are not stock and are never counted. Capabilities group on the exact set, so a number offering SMS and voice is one line and a number offering only SMS is another. A zero in `available` with numbers still `assigned` is a group that has run out; a group with no rows at all is one Bird never stocked.\n","required":["country_code","number_type","capabilities","available","assigned","cooldown"],"properties":{"country_code":{"$ref":"#/components/schemas/CountryCode"},"number_type":{"$ref":"#/components/schemas/NumberType"},"capabilities":{"type":"array","description":"The exact capability set of every number in this group, sorted.","items":{"$ref":"#/components/schemas/NumberCapability"},"example":["sms","voice"]},"scope":{"$ref":"#/components/schemas/AdminNumberScope","description":"Present only when the summary was requested grouped by scope."},"available":{"type":"integer","format":"int64","minimum":0,"description":"Numbers whose lifecycle status is available. Unexpired cart reservations are not subtracted, and a shared shortcode counts here regardless of how many workspaces hold a grant on it.","example":120},"assigned":{"type":"integer","format":"int64","minimum":0,"description":"Numbers with an active dedicated allocation to a workspace. Shared-shortcode grants do not count.","example":480},"cooldown":{"type":"integer","format":"int64","minimum":0,"description":"Numbers released by a workspace and waiting out quarantine before returning to stock.","example":12},"public_available":{"type":"integer","format":"int64","minimum":0,"description":"Available numbers in the `public` scope, the figure a threshold is judged on."},"threshold":{"$ref":"#/components/schemas/AdminNumbersStockThreshold","description":"The most specific rule covering this line. Absent when no rule does."},"below_threshold":{"type":"boolean","description":"True when `public_available` is under the matched rule's `min_available`. Absent when no rule matches."}}},"AdminNumbersInventoryStockSummary":{"type":"object","additionalProperties":false,"description":"Stock counts for every stocked country, number type, and capability set. The list is complete rather than paginated: its size is bounded by the number of distinct groups Bird stocks, not by the number of phone numbers.\n","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminNumbersInventoryStockRow"}}}},"AdminNumbersInventoryCountryList":{"type":"object","additionalProperties":false,"description":"Every country Bird holds inventory in, sorted by code. The list is complete rather than paginated: its size is bounded by the number of countries Bird has ever stocked, not by the number of phone numbers.\n","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CountryCode"}}}},"AdminNumbersInventoryBatchCreate":{"type":"object","additionalProperties":false,"required":["supplier_id","country_code","number_type","capabilities","release_strategy","numbers"],"properties":{"supplier_id":{"$ref":"#/components/schemas/NumbersSupplierID"},"country_code":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code for all numbers in this batch. Case-insensitive — stored as uppercase."},"number_type":{"allOf":[{"$ref":"#/components/schemas/NumberType"}],"description":"Physical type of the phone numbers in this batch."},"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/NumberCapability"},"minItems":1,"description":"Capabilities shared by all numbers in this batch."},"capability_metadata":{"allOf":[{"$ref":"#/components/schemas/AdminNumberCapabilityMetadata"}],"description":"Per-capability facts about these numbers. Omit when nothing is known: an omitted document is not the same as one stating no constraint."},"tags":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/Tag"},"description":"Operator labels applied to every number in this batch, so a block loaded for one purpose stays identifiable later. Tag names are unique within the set and may not begin with `__bird`, Bird's own namespace; either returns a 422 `validation_error`. Staff-only: they are never projected onto a customer-facing number."},"release_strategy":{"type":"string","minLength":1,"enum":["cooldown","release"],"description":"Behavior when a customer's subscription ends, applied to all numbers in this batch."},"scope":{"allOf":[{"$ref":"#/components/schemas/AdminNumberScope"}],"description":"Controls how these numbers are made available. public numbers can be purchased by customers; internal numbers are reserved for manual admin assignment; shared numbers are shortcodes allocated to multiple workspaces simultaneously. Defaults to public."},"numbers":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":10000,"description":"Phone numbers in E.164 format to add to the inventory."}}},"AdminNumbersInventoryBatchResult":{"type":"object","additionalProperties":false,"required":["created_count","skipped","items"],"properties":{"created_count":{"type":"integer","readOnly":true,"description":"Number of phone numbers successfully added to the inventory."},"skipped":{"type":"integer","readOnly":true,"description":"Number of phone numbers skipped because they already exist in the inventory."},"items":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminNumbersInventoryItem"},"description":"Inventory items created by this batch."}}},"AdminNumbersInventoryUpdate":{"type":"object","additionalProperties":false,"not":{"required":["supplier_ref"],"minProperties":2,"properties":{"supplier_ref":{}},"not":{"properties":{"supplier_ref":{},"retain_supplier_stock":{"const":false}},"additionalProperties":false}},"properties":{"retain_supplier_stock":{"type":"boolean","default":false,"description":"Confirm that this number remains with its supplier and should be retained for reuse. Requires status=available and dedicated inventory with no active allocation. Ends quarantine and changes release_strategy to cooldown. Removes only release intents with no captured identity, removal request, completion, or active dispatch claim. Verify continued supplier ownership before using this override. Supplier reassignment must be a separate request."},"supplier_ref":{"type":"string","minLength":1,"maxLength":255,"description":"Repairs the supplier reference for an unresolved release. Send this field alone after verifying the reference with the existing supplier. The number must have no active allocation, dispatch claim, or captured canonical release identity. The pending release must still match the inventory supplier and number. Leading or trailing whitespace returns 422 `validation_error`. Omit to leave the reference unchanged."},"supplier_id":{"$ref":"#/components/schemas/NumbersSupplierID","description":"Reassign this number to a different supplier. A pending supplier release prevents reassignment and returns 422 `validation_error`."},"status":{"type":"string","minLength":1,"enum":["available","cooldown","released"],"description":"New lifecycle status for this inventory entry."},"scope":{"$ref":"#/components/schemas/AdminNumberScope"},"tags":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/Tag"},"description":"Replaces the whole tag set. Omit to leave the tags alone; send an empty array to clear them, which is how a mislabelled batch is corrected. Tag names are unique within the set and may not begin with `__bird`, Bird's own namespace; either returns a 422 `validation_error`."},"capability_metadata":{"allOf":[{"$ref":"#/components/schemas/AdminNumberCapabilityMetadata"}],"description":"Replaces the whole per-capability document. Omit to leave it alone; there is deliberately no way to clear one back to unknown, since a number that was classified once does not become unclassified."}}},"NumbersSharedAllocationID":{"type":"string","minLength":1,"pattern":"^nal_[0-9a-hjkmnp-tv-z]{26}$","example":"nal_01krdgeqcxet5s7t44vh8rt9mg"},"AdminNumbersAllocation":{"type":"object","additionalProperties":false,"required":["id","inventory_id","organization_id","workspace_id","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/NumbersSharedAllocationID"},"inventory_id":{"readOnly":true,"$ref":"#/components/schemas/NumbersInventoryID","description":"Shared shortcode this allocation grants access to."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization granted access to the shared shortcode."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace within the organization that may use the shared shortcode."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminNumbersAllocationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminNumbersAllocation"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminNumbersAllocationCreate":{"type":"object","additionalProperties":false,"required":["organization_id","workspace_id"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID","description":"Organization to grant access to the shared shortcode."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"Workspace within the organization that may use the shared shortcode."}}},"AdminNumbersStockThresholdList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminNumbersStockThreshold"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminNumbersStockThresholdCreate":{"type":"object","additionalProperties":false,"description":"Creates an operator minimum for one stock line. Omit `number_type` or `capabilities` to cover every line in the country with that shape.\n","required":["country_code","min_available"],"properties":{"country_code":{"$ref":"#/components/schemas/CountryCode"},"number_type":{"allOf":[{"$ref":"#/components/schemas/NumberType"}],"description":"Omit to cover every number type in the country."},"capabilities":{"type":"array","minItems":1,"description":"Exact capability set this rule covers. Omit to cover every set.","items":{"$ref":"#/components/schemas/NumberCapability"}},"min_available":{"type":"integer","format":"int64","minimum":0,"maximum":2147483647,"description":"The line is below when its public available count is under this."},"note":{"type":"string","maxLength":500,"description":"Why the line matters, shown on the summary and in the alert."}}},"AdminNumbersStockThresholdUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"description":"Updates the minimum and note of an existing rule. The key (`country_code`, `number_type`, `capabilities`) is immutable; delete and create a new rule to change it. At least one field is required.\n","properties":{"min_available":{"type":"integer","format":"int64","minimum":0,"maximum":2147483647,"description":"The line is below when its public available count is under this."},"note":{"type":["string","null"],"maxLength":500,"description":"Why the line matters, shown on the summary and in the alert. Null clears it."}}},"AdminNumbersProcurementStatus":{"type":"string","minLength":1,"enum":["pending","completed","unavailable","canceled"],"description":"Lifecycle state. pending covers everything before the carrier order is placed and while it is in flight; completed means the number has been written into inventory. unavailable is terminal and only reachable for a row that named its number: somebody else bought it before the order landed, which no retry can recover, so the row is written off rather than left pending. It is established before the write-off that nothing was placed and nothing was spent. canceled is declared ahead of the roadmap's row-cancellation feature (cancelling or expiring a row whose carrier order never placed) so its arrival is not a breaking change; no row reports it today. There is otherwise no failed state: once a carrier order is placed, money may have moved, so nothing marks a row failed. An unplaced row that never completes just stays pending until support re-drives it, or until the cancellation feature above retires it.\n","example":"pending"},"NumbersProcurementOrderID":{"type":"string","minLength":1,"pattern":"^npo_[0-9a-hjkmnp-tv-z]{26}$","example":"npo_01krdgeqcxet5s7t44vh8rt9mg"},"AdminNumbersProcurement":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["id","supplier_id","country_code","number_type","status"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/NumbersProcurementOrderID"},"supplier_id":{"readOnly":true,"$ref":"#/components/schemas/NumbersSupplierID","description":"ID of the supplier the number is being bought from."},"country_code":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"description":"ISO 3166-1 alpha-2 country code of the market being stocked."},"number_type":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/NumberType"}],"description":"Physical type of the number being bought."},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminNumbersProcurementStatus"}]},"prefix":{"readOnly":true,"type":["string","null"],"description":"The area code this row buys inside, matched right after the country dial code. Null when the request asked for the market as a whole, and always null on a row that named its number.\n"},"requested_number":{"readOnly":true,"type":["string","null"],"description":"The number this row was asked to buy, in E.164 format. Null when the request bought a count of the market and let the supplier pick. Set means the carrier order names this number and `number` must come back equal to it.\n"},"supplier_order_ref":{"readOnly":true,"type":["string","null"],"description":"Supplier's own reference for the placed carrier order. Null before placement."},"supplier_number_ref":{"readOnly":true,"type":["string","null"],"description":"Supplier's own reference for the bought number itself (DIDWW: the DID's UUID). Null until the carrier order completes. This is the handle ops needs to act on the number directly at the supplier, for example to terminate a row stuck pending.\n"},"number":{"readOnly":true,"type":["string","null"],"description":"Phone number in E.164 format the order bought. Null until the carrier order completes."},"setup_price":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Supplier's own one-time setup price. Null when the supplier had no declared billing currency when this row was created, or when it reports no setup price. Informational only: no Bird rate-card entry backs a procured number yet.\n"},"monthly_price":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Supplier's own recurring monthly price, same terms as setup_price."},"last_attempt_error":{"readOnly":true,"type":["string","null"],"description":"Why the row's last attempt failed, as the carrier reported it, truncated. Null until an attempt fails, and kept after the row completes.\n"},"last_attempt_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When `last_attempt_error` was recorded. Null until an attempt fails, and kept for as long as the error is.\n"},"inventory_id":{"readOnly":true,"description":"The inventory row this order produced. Null until the row is completed.","oneOf":[{"$ref":"#/components/schemas/NumbersInventoryID"},{"type":"null"}]},"completed_at":{"readOnly":true,"type":["string","null"],"format":"date-time"}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminNumbersProcurementList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminNumbersProcurement"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminNumbersProcurementCreate":{"type":"object","additionalProperties":false,"required":["supplier_id","country_code","number_type"],"oneOf":[{"required":["qty"]},{"required":["numbers"]}],"not":{"required":["numbers","prefix"],"properties":{"numbers":{},"prefix":{}}},"properties":{"supplier_id":{"$ref":"#/components/schemas/NumbersSupplierID","description":"Supplier to buy from. Must be registered with an adapter that supports the requested shape, with ordering configured."},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"description":"ISO 3166-1 alpha-2 country code of the market to stock. Case-insensitive, stored as uppercase."},"number_type":{"allOf":[{"$ref":"#/components/schemas/NumberType"}],"description":"Physical type of number to buy."},"prefix":{"allOf":[{"$ref":"#/components/schemas/AdminNumberPrefix"}],"description":"Buy inside one area code rather than anywhere in the market, in the same form `listAdminNumbersSupplierAvailable` takes. Goes with `qty` only, and is refused alongside `numbers`, which already say where they are. Omit it and the supplier picks any area code it sells, which for a market that lists no individual numbers means the area code is nobody's choice.\n"},"qty":{"type":"integer","minimum":1,"maximum":20,"description":"How many numbers to buy, letting the supplier pick them. Each number is placed as its own carrier order. Give this or `numbers`, never both and never neither.\n"},"numbers":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"string","minLength":1},"description":"The numbers to buy, each already listed as available by the supplier. Give them in E.164 or in the national format of `country_code`, which is what they are normalized against; the row and the response report the normalized form. Give this or `qty`, never both and never neither. Each is placed as its own carrier order and each row records the number it asked for, so a supplier delivering a different one is refused rather than stocked. A number sold to someone else before its order lands leaves that row `unavailable`; the others are unaffected. Naming the same number twice is refused.\n"}}},"AdminNumbersProcurementBatch":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"One row per number requested. A retried request carrying the same Idempotency-Key returns the same rows this call already created, rather than creating new ones.\n","items":{"$ref":"#/components/schemas/AdminNumbersProcurement"}}}},"AdminNumberRevokedReason":{"type":"string","minLength":1,"x-extensible-enum":["abuse","non_payment","org_banned","provisioning_error","customer_request","registration_incomplete"],"description":"Why a dedicated number was taken back. Additional reasons may appear over time.\n\n- `abuse` means staff reclaimed the number for abuse.\n- `non_payment` means staff reclaimed the number for non-payment.\n- `org_banned` means the owning organization was banned.\n- `provisioning_error` means the number should never have been allocated here.\n- `customer_request` means staff released the number on the customer's behalf.\n- `registration_incomplete` means ownership registration did not complete. The system uses it when the registration deadline expires; staff can also select it to release a blocked allocation without waiting for that deadline.\n"},"AdminNumberOwnershipState":{"type":"string","minLength":1,"enum":["not_required","satisfied","unverified","blocked","unknown"],"description":"Where a dedicated number stands with the ownership paperwork its country requires,\nderived from what is recorded against the number itself.\n\n- `not_required` means no country requirement covers this number. The usual case, and\n  the state every number is in until a country's form is published.\n- `satisfied` means a review accepted the paperwork.\n- `unverified` means a requirement covers this number, nothing has been accepted, and it\n  is sending anyway. The state worth acting on: it is reached by a number bought before\n  its country's requirement was published, or allocated while that requirement could not\n  be looked up.\n- `blocked` means the number cannot carry traffic until its paperwork is accepted.\n- `unknown` means whether anything is required could not be established, so nothing is\n  being claimed either way. Read it again before drawing a conclusion.\n\nThe set is closed because it is exhaustive: these five are every reading of the two\nstamps a number carries plus whether a requirement covers it.\n"},"AdminNumberOwnership":{"type":"object","additionalProperties":false,"required":["state"],"properties":{"state":{"allOf":[{"$ref":"#/components/schemas/AdminNumberOwnershipState"}],"readOnly":true},"blocked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the number stopped being able to carry traffic, and null while it can. Null does not mean the paperwork is accepted: a number a requirement was published for after it was bought is usable with nothing filed."},"decided_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this number's standing was last decided, and null when it never has been. Not always a review: a number that arrived blocked carries the instant it was allocated, which is the baseline a later decision is ordered against. On a number that is not blocked it does mean a review, and it is the only thing separating `satisfied` from `unverified`."}},"description":"Where this number stands with the ownership paperwork its country requires, from what is recorded against the number itself. It carries no submission: the answers and their review live in the owning organization's region, so read the compliance submission for this number to see them."},"AdminNumberAllocation":{"type":"object","additionalProperties":false,"required":["id","inventory_id","organization_id","workspace_id","number","country_code","number_type","capabilities","status","allocated_at","ownership","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/NumbersDedicatedAllocationID","description":"Identifier of this dedicated number, used as the billing subscription reference."},"inventory_id":{"readOnly":true,"$ref":"#/components/schemas/NumbersInventoryID","description":"Catalog number this allocation is held against."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization that owns this dedicated number."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace within the organization that holds this dedicated number."},"number":{"type":"string","minLength":1,"readOnly":true,"description":"Phone number in E.164 format."},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"readOnly":true},"number_type":{"allOf":[{"$ref":"#/components/schemas/NumberType"}],"readOnly":true,"description":"Physical type of this phone number."},"capabilities":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/NumberCapability"},"description":"Capabilities supported by this number."},"status":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["active","released"],"description":"Whether this dedicated number is currently held or has been released."},"allocated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this number was allocated to the workspace."},"released_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this number was released. Null while the allocation is active."},"revoked_reason":{"oneOf":[{"$ref":"#/components/schemas/AdminNumberRevokedReason"},{"type":"null"}],"readOnly":true,"description":"Why this number was taken back. Null while the allocation is active, and null on a released allocation the workspace gave up itself. Also null on anything released before this field existed, which reads the same as a customer release and cannot be told apart from one."},"ownership":{"allOf":[{"$ref":"#/components/schemas/AdminNumberOwnership"}],"readOnly":true,"description":"Where this number stands with the ownership paperwork its country requires. Always reported, including `not_required` for the numbers no country requirement covers, so a page can be scanned for the ones that need attention. Releasing a number clears neither stamp, so a released allocation keeps whatever standing it had when it was taken back. Nothing is owed on it any more, so read it as history there."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminNumberAllocationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminNumberAllocation"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminNumberAllocationCreate":{"type":"object","additionalProperties":false,"required":["organization_id","workspace_id"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID","description":"Organization that will own the number and be charged for it."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"Workspace within the organization to allocate the number to."},"inventory_id":{"$ref":"#/components/schemas/NumbersInventoryID","description":"Catalog number to allocate, for a number Bird already holds. Exactly one of this or number is required.\n"},"number":{"type":"string","minLength":1,"description":"Number to allocate, in E.164 format, for one Bird does not hold yet and must acquire from a supplier. Nothing seeds toll-free inventory, so this is the only way to get a toll-free number for a customer. Exactly one of this or inventory_id is required.\n"},"waive_setup_fee":{"type":"boolean","default":false,"description":"Skip the one-off acquisition fee for this allocation. Defaults to false, so an allocation charges whatever the rate card prices, the same as every other surface. Set it only for a deliberate commercial decision such as goodwill, support or a migration. It does not affect the recurring monthly charge.\n"},"unit_price":{"description":"Charge this amount for the number every period, instead of the price its rate card\nresolves. It is one whole period's net amount, in the organization's wallet currency,\nand it is locked on the subscription: renewals charge it too, and a later rate-card\nchange does not move it. Omit it to bill the rate card, the same as every other\nsurface. Set it for a number whose price comes from a contract we are honoring, such\nas one migrated from another platform.\n\nAccepted only for a number we already hold, so send it with `inventory_id`, or with a\n`number` that is in the catalog. A `number` we have to buy from a supplier returns 422\ninstead: that purchase can finish after the request does, and the price would not\nsurvive the wait.\n\nIt does not affect the one-off acquisition fee, which the rate card still prices. Use\n`waive_setup_fee` for that.\n","allOf":[{"$ref":"#/components/schemas/Money"}]}},"oneOf":[{"required":["inventory_id"]},{"required":["number"]}]},"AdminNumberAllocationBatchCreate":{"type":"object","additionalProperties":false,"required":["organization_id","workspace_id","inventory_ids"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID","description":"Organization that will own the numbers and be charged for them."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"Workspace within the organization to allocate the numbers to."},"inventory_ids":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/NumbersInventoryID"},"description":"Catalog numbers to allocate. Each is allocated and billed independently."},"waive_setup_fee":{"type":"boolean","default":false,"description":"Skip the one-off acquisition fee for every allocation in this batch. Defaults to false, so an allocation charges whatever the rate card prices, the same as every other surface. Set it only for a deliberate commercial decision such as goodwill, support or a migration. It does not affect the recurring monthly charge.\n"},"unit_price":{"description":"Charge this amount for every number in this batch, every period, instead of the price\neach rate card resolves. It is one whole period's net amount per number, in the\norganization's wallet currency, and it is locked on each subscription: renewals charge\nit too, and a later rate-card change does not move it. Omit it to bill the rate card,\nthe same as every other surface.\n\nOne price covers the whole batch, so send a batch per price when the numbers carry\ndifferent ones. It does not affect the one-off acquisition fee, which the rate card\nstill prices. Use `waive_setup_fee` for that.\n","allOf":[{"$ref":"#/components/schemas/Money"}]}}},"AdminNumberAllocationBatchFailure":{"type":"object","additionalProperties":false,"required":["inventory_id","message"],"properties":{"inventory_id":{"$ref":"#/components/schemas/NumbersInventoryID","description":"Catalog number that could not be allocated."},"message":{"type":"string","minLength":1,"description":"Why this number could not be allocated (e.g. no longer available, insufficient balance)."}}},"AdminNumberAllocationBatchResult":{"type":"object","additionalProperties":false,"required":["allocated","failed"],"properties":{"allocated":{"type":"array","items":{"$ref":"#/components/schemas/AdminNumberAllocation"},"description":"Numbers that were successfully allocated."},"failed":{"type":"array","items":{"$ref":"#/components/schemas/AdminNumberAllocationBatchFailure"},"description":"Numbers that could not be allocated, each with the reason. A number here is unchanged (not allocated, not charged)."}}},"AdminNumberRevokedReasonInput":{"type":"string","minLength":1,"enum":["abuse","non_payment","org_banned","provisioning_error","customer_request","registration_incomplete"],"description":"Why staff are taking a dedicated number back.\n\n- `abuse` means staff reclaimed the number for abuse.\n- `non_payment` means staff reclaimed the number for non-payment.\n- `org_banned` means the owning organization was banned.\n- `provisioning_error` means the number should never have been allocated here.\n- `customer_request` means staff released the number on the customer's behalf.\n- `registration_incomplete` means ownership registration did not complete. The system uses it when the registration deadline expires; staff can also select it to release a blocked allocation without waiting for that deadline.\n"},"AdminNumberHolderScope":{"type":"string","minLength":1,"enum":["allocation","shared"],"example":"allocation","description":"Whether the number was held under a dedicated allocation, which has exactly one holder at a time, or a shared allocation, which several organizations can hold at once."},"AdminNumberIdentityAddress":{"type":"object","additionalProperties":false,"description":"The holder's registered address, from whichever source resolved the identity. A part the source did not state is omitted, not an empty value.","properties":{"line1":{"type":"string","minLength":1,"description":"Street address. The compliance source's line1 and line2 answers are joined into it; the profile source has only one line to begin with.","readOnly":true},"city":{"type":"string","minLength":1,"description":"City or town.","readOnly":true},"postal_code":{"type":"string","minLength":1,"description":"Postal or ZIP code.","readOnly":true},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"description":"The holder's registered country.","readOnly":true}}},"AdminNumberIdentitySource":{"type":"string","minLength":1,"enum":["compliance","profile"],"example":"compliance","description":"Which store the resolved identity's values came from: the number's own approved ownership submission, or the holding organization's business profile when policy allowed that fallback. Omitted when the identity is a gap."},"AdminNumberIdentityGapKind":{"type":"string","minLength":1,"enum":["never_collected","awaiting_review","approval_expired","region_unreachable","profile_changed_since","compliance_changed_since","shared_scope"],"example":"never_collected","description":"Why the holder's identity could not be stated, whether or not the\nbusiness-profile fallback was tried too:\n\n- `never_collected`: no ownership submission existed for this number at\n  the requested as-of instant.\n- `awaiting_review`: a submission existed, but none of its identity items\n  had reached an approved verdict by the requested as-of instant.\n- `approval_expired`: a submission had an approval, but none was usable\n  at the requested as-of instant: its expiry had passed, or its grant\n  had lapsed or been superseded by a later definition version.\n- `region_unreachable`: the relation's own region is not one this read\n  could reach, so compliance was never asked and the fallback was never\n  tried either.\n- `profile_changed_since`: the business-profile fallback has a usable\n  legal name, but it was written after the instant this relation is\n  being stated as of. This is an over-approximation: `updated_at` is\n  row-level, so an edit to an unrelated profile field also produces this\n  gap. That is the safe direction: a gap, never a wrong value.\n- `compliance_changed_since`: an ownership compliance item was updated\n  after the requested as-of instant. Its current state cannot establish\n  the identity at that instant. Even a later update that\n  preserves the answer can produce this gap.\n- `shared_scope`: the relation is a shared allocation, which no ownership\n  submission can ever be filed against.\n"},"AdminNumberIdentityGap":{"type":"object","additionalProperties":false,"description":"Present only when the holder's identity could not be resolved from any source policy allowed.","required":["kind"],"properties":{"kind":{"$ref":"#/components/schemas/AdminNumberIdentityGapKind","readOnly":true}}},"AdminNumberIdentity":{"type":"object","additionalProperties":false,"description":"The holder's identity for this relation, resolved from the number's own compliance ownership submission first, and from the holding organization's business profile as a fallback where policy allows it. Administrative ownership lookup does not enable that fallback. A field no source stated is omitted, not an empty value. Exactly one of source or gap is present: source names where a resolved identity's values came from, gap names why nothing could be resolved at all.","properties":{"legal_entity_name":{"type":"string","minLength":1,"description":"The holder's legal entity name.","readOnly":true},"address":{"$ref":"#/components/schemas/AdminNumberIdentityAddress","readOnly":true},"contact_phone":{"type":"string","minLength":1,"description":"The holder's contact phone number, digits only where the source strips formatting.","readOnly":true},"source":{"$ref":"#/components/schemas/AdminNumberIdentitySource","readOnly":true},"gap":{"$ref":"#/components/schemas/AdminNumberIdentityGap","readOnly":true}},"oneOf":[{"required":["source","legal_entity_name"],"properties":{"gap":{"not":{}}}},{"required":["gap"],"properties":{"source":{"not":{}},"legal_entity_name":{"not":{}},"address":{"not":{}},"contact_phone":{"not":{}}}}]},"AdminNumberHolderOfRecord":{"type":"object","additionalProperties":false,"required":["number","country_code","number_type","supplier_id","organization_id","workspace_id","region","held_from","held_until","scope","identity"],"properties":{"number":{"type":"string","minLength":1,"readOnly":true,"description":"Phone number in E.164 format."},"country_code":{"$ref":"#/components/schemas/CountryCode","readOnly":true},"number_type":{"allOf":[{"$ref":"#/components/schemas/NumberType"}],"readOnly":true,"description":"Physical type of the phone number."},"supplier_id":{"$ref":"#/components/schemas/NumbersSupplierID","readOnly":true,"description":"ID of the supplier that provides this number."},"organization_id":{"$ref":"#/components/schemas/OrganizationID","readOnly":true,"description":"ID of the organization that held the number over this relation."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","readOnly":true,"description":"ID of the workspace that held the number over this relation."},"region":{"allOf":[{"$ref":"#/components/schemas/Region"}],"readOnly":true,"description":"Region of the organization that held the number."},"held_from":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this relation began."},"held_until":{"type":["string","null"],"format":"date-time","minLength":1,"readOnly":true,"description":"When this relation ended, or null while it is still current."},"scope":{"$ref":"#/components/schemas/AdminNumberHolderScope","readOnly":true},"identity":{"allOf":[{"$ref":"#/components/schemas/AdminNumberIdentity"}],"readOnly":true,"x-sensitive":true}}},"AdminNumberHolderOfRecordList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminNumberHolderOfRecord"}}}},"ReportingEntityID":{"type":"string","minLength":1,"pattern":"^nre_[0-9a-hjkmnp-tv-z]{26}$","example":"nre_01krdgeqcxet5s7t44vh8rt9mg"},"AdminReportingEntityUniverseKind":{"type":"string","minLength":1,"enum":["own","org"],"description":"Whose numbers a reporting entity files for: `own` is Bird's own holdings in the jurisdiction, `org` is a named organization's. `universe_organization_id` is required when this is `org`; when it is `own`, a create omits it and a PATCH omits or nulls it. In a response, `universe_organization_id` is always present: the organization's id when this is `org`, null when it is `own`.\n"},"AdminReportingEntityDestination":{"type":"string","minLength":1,"enum":["test","production"],"description":"The regulator destination configured for this entity."},"AdminReportingEntity":{"unevaluatedProperties":false,"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","jurisdiction","regulator_identifier","display_name","universe_kind","universe_organization_id","destination","active","credential_expires_at","credential_subject","credential_fingerprint"],"oneOf":[{"properties":{"universe_kind":{"const":"own"},"universe_organization_id":{"const":null}}},{"properties":{"universe_kind":{"const":"org"},"universe_organization_id":{"$ref":"#/components/schemas/OrganizationID"}}}],"properties":{"id":{"$ref":"#/components/schemas/ReportingEntityID"},"jurisdiction":{"$ref":"#/components/schemas/CountryCode","description":"The jurisdiction this entity files for."},"regulator_identifier":{"type":"string","minLength":1,"description":"The identifier the regulator assigned this entity."},"display_name":{"type":"string","minLength":1,"description":"A staff-facing label, not sent to the regulator."},"universe_kind":{"$ref":"#/components/schemas/AdminReportingEntityUniverseKind"},"universe_organization_id":{"description":"The organization whose numbers this entity files, set when `universe_kind` is `org` and null when it is `own`.\n","oneOf":[{"$ref":"#/components/schemas/OrganizationID"},{"type":"null"}]},"destination":{"$ref":"#/components/schemas/AdminReportingEntityDestination"},"active":{"type":"boolean","description":"Whether this entity is included in a jurisdiction's filing universe."},"credential_expires_at":{"type":["string","null"],"minLength":1,"format":"date-time","description":"The uploaded certificate's expiry, extracted at upload and kept in the clear so it can be monitored. Null until a certificate is uploaded.\n"},"credential_subject":{"type":["string","null"],"minLength":1,"description":"The uploaded certificate's subject. Null until a certificate is uploaded."},"credential_fingerprint":{"type":["string","null"],"minLength":1,"description":"Hex SHA-256 fingerprint of the uploaded certificate. Null until a certificate is uploaded. Never the certificate or key material itself.\n"}}}]},"AdminReportingEntityList":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminReportingEntity"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminReportingEntityCreate":{"type":"object","additionalProperties":false,"required":["jurisdiction","regulator_identifier","display_name","universe_kind","destination"],"oneOf":[{"properties":{"universe_kind":{"const":"own"}},"not":{"properties":{"universe_organization_id":{}},"required":["universe_organization_id"]}},{"properties":{"universe_kind":{"const":"org"}},"required":["universe_organization_id"]}],"properties":{"jurisdiction":{"$ref":"#/components/schemas/CountryCode","description":"The jurisdiction this entity files for."},"regulator_identifier":{"type":"string","minLength":1,"maxLength":100,"description":"The identifier the regulator assigned this entity."},"display_name":{"type":"string","minLength":1,"maxLength":200,"description":"A staff-facing label, not sent to the regulator."},"universe_kind":{"$ref":"#/components/schemas/AdminReportingEntityUniverseKind"},"universe_organization_id":{"description":"The organization whose numbers this entity files. Required when `universe_kind` is `org`; rejected when it is `own`.","$ref":"#/components/schemas/OrganizationID"},"destination":{"$ref":"#/components/schemas/AdminReportingEntityDestination"},"active":{"type":"boolean","default":false,"description":"Whether this entity is included in a jurisdiction's filing universe. Defaults to false: a newly created entity has no credential yet, and starting inactive keeps it out of a live filing universe until staff confirm it is ready.\n"}}},"AdminReportingEntityUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"description":"Fields to change on a reporting entity. Omit a field to leave it as it is. A certificate upload and a metadata change may ride in the same request; send at least one field. `universe_kind` and `universe_organization_id` travel together: send `universe_organization_id` only alongside `universe_kind`, never on its own.","dependentRequired":{"certificate_pfx":["passphrase"],"passphrase":["certificate_pfx"]},"oneOf":[{"not":{"anyOf":[{"properties":{"universe_kind":{}},"required":["universe_kind"]},{"properties":{"universe_organization_id":{}},"required":["universe_organization_id"]}]}},{"properties":{"universe_kind":{"const":"own"},"universe_organization_id":{"const":null}},"required":["universe_kind"]},{"properties":{"universe_kind":{"const":"org"},"universe_organization_id":{"$ref":"#/components/schemas/OrganizationID"}},"required":["universe_kind","universe_organization_id"]}],"properties":{"display_name":{"type":"string","minLength":1,"maxLength":200,"description":"A staff-facing label, not sent to the regulator."},"universe_kind":{"$ref":"#/components/schemas/AdminReportingEntityUniverseKind"},"universe_organization_id":{"description":"The organization whose numbers this entity files. Only accepted alongside `universe_kind`. Send null (or omit it) when moving `universe_kind` to `own`; required (non-null) when moving it to `org`.","oneOf":[{"$ref":"#/components/schemas/OrganizationID"},{"type":"null"}]},"destination":{"$ref":"#/components/schemas/AdminReportingEntityDestination"},"active":{"type":"boolean"},"certificate_pfx":{"type":"string","format":"byte","minLength":1,"maxLength":87384,"x-sensitive":true,"description":"A PKCS#12 (.pfx/.p12) file, base64-encoded. The 87384-character limit is the encoded size of a 64 KB file. Send together with `passphrase`; the certificate replaces any previously uploaded one and its expiry, subject and fingerprint are re-extracted. Rejected if the certificate is unparseable or already expired. The raw file and passphrase are stored encrypted and are never returned; only the extracted expiry, subject and fingerprint are."},"passphrase":{"type":"string","x-sensitive":true,"description":"The passphrase for `certificate_pfx`. Required together with it."}}},"AdminReportingEntityFilingState":{"type":"string","minLength":1,"enum":["generated","held","delivered","accepted","failed"],"example":"generated","description":"This filing's lifecycle stage.\n\n- `generated`: built and ready to deliver, not held back by any guard.\n- `held`: generated but not sent, for the reason `held_reason` names.\n- `delivered`: sent to the regulator, awaiting acknowledgement.\n- `accepted`: the regulator acknowledged it as filed.\n- `failed`: the regulator rejected it.\n"},"AdminReportingEntityFilingKind":{"type":"string","minLength":1,"enum":["scheduled","disclosure"],"example":"scheduled","description":"Whether this filing answers a schedule's period or an ad hoc disclosure request. `period_date` is set for `scheduled`, `request_ref` for `disclosure`, and never both."},"AdminReportingEntityFilingHeldReason":{"type":"string","minLength":1,"enum":["first_filing","collapse","zero_relations"],"example":"first_filing","description":"Why a generated filing was held rather than delivered.\n\n- `first_filing`: no previously accepted filing exists yet to compare\n  against, so there is no baseline to judge this one safe against.\n- `collapse`: this filing's filed-relation count fell too far below the\n  last accepted filing's own count, a signal worth a human look before it\n  reaches the regulator.\n- `zero_relations`: no relation resolved for this filing's universe at all.\n"},"ReportingEntityFilingID":{"type":"string","minLength":1,"pattern":"^nrf_[0-9a-hjkmnp-tv-z]{26}$","example":"nrf_01krdgeqcxet5s7t44vh8rt9mg"},"AdminReportingEntityFilingCounts":{"type":"object","additionalProperties":false,"description":"How this filing's universe resolved, by relation fate.","required":["total","filed","truncated","dropped","fallback","gaps_by_kind"],"properties":{"total":{"type":"integer","readOnly":true,"description":"Every relation this filing's universe considered."},"filed":{"type":"integer","readOnly":true,"description":"Relations that reached the document, truncated ones included, so `filed` plus `dropped` is `total`."},"truncated":{"type":"integer","readOnly":true,"description":"The subset of `filed` with one or more fields shortened to fit the format."},"dropped":{"type":"integer","readOnly":true,"description":"Relations left off the document entirely."},"fallback":{"type":"integer","readOnly":true,"description":"Filed or truncated relations whose identity came from the holding organization's business profile rather than the number's own compliance submission."},"gaps_by_kind":{"type":"object","readOnly":true,"additionalProperties":{"type":"integer"},"description":"Dropped-relation count, keyed by AdminReportingEntityFilingRelationGapKind."}}},"AdminReportingEntityFilingAttempt":{"type":"object","additionalProperties":false,"description":"One delivery of this filing to the regulator: which rendering was sent, and what came back. A re-fire replaces the filing's current content and verdict; the attempts are what keep an earlier answer readable.","required":["attempt_no","content_sha256","delivered_at","resulted_at","result_code","result_message","released_by_actor_type","released_by_actor_id","release_reason"],"properties":{"attempt_no":{"type":"integer","readOnly":true,"description":"Counts deliveries of this filing, from 1. Not `attempts`, which counts generation re-fires."},"content_sha256":{"type":"string","minLength":1,"readOnly":true,"description":"Hex SHA-256 of the document this attempt carried, which pins it even after a re-fire replaced the filing's current one."},"delivered_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the attempt was committed, which is before the regulator was called."},"resulted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the regulator's answer was recorded. Null while the attempt was sent and its outcome is unknown."},"result_code":{"type":["integer","null"],"readOnly":true,"description":"The regulator's own code, 0 for accepted. Null together with `resulted_at`."},"result_message":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The regulator's message. Null when it answered with none, and while it has not answered at all."},"released_by_actor_type":{"type":"string","minLength":1,"readOnly":true,"description":"Who released this attempt, which separates a human's release from the daily schedule's."},"released_by_actor_id":{"type":"string","minLength":1,"readOnly":true,"description":"The releasing actor's id."},"release_reason":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Why the filing was released, as the releasing person gave it. Null for the daily schedule, which is asked for none."}}},"AdminReportingEntityFilingRelationOutcome":{"type":"string","minLength":1,"enum":["stated","truncated","dropped"],"example":"stated","description":"One relation's fate on the generated document.\n\n- `stated`: included, with every field carried as resolved.\n- `truncated`: included, with one or more fields shortened to fit the format.\n- `dropped`: left off the document entirely, for the reason `gap` names.\n"},"AdminReportingEntityFilingRelationProvenance":{"type":"object","additionalProperties":false,"required":["source"],"description":"Where a relation's resolved identity came from. Present whenever an identity resolved for the relation, including one later dropped by the format layer for a blank required field (`gap.kind: absent`); absent when the identity resolver could not resolve one at all.","oneOf":[{"properties":{"source":{"const":"compliance"},"profile_updated_at":{"not":{}}},"required":["compliance_item_ids"]},{"properties":{"source":{"const":"profile"},"compliance_item_ids":{"not":{}}},"required":["profile_updated_at"]}],"properties":{"source":{"$ref":"#/components/schemas/AdminNumberIdentitySource","readOnly":true},"compliance_item_ids":{"type":"array","readOnly":true,"minItems":1,"items":{"$ref":"#/components/schemas/ComplianceItemID"},"description":"The compliance submission items this relation's identity was read from. Present when `source` is `compliance`."},"profile_updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"The holding organization's business profile row's own `updated_at`, at the instant this relation resolved. Present when `source` is `profile`."}}},"AdminReportingEntityFilingRelationGapKind":{"type":"string","minLength":1,"enum":["never_collected","awaiting_review","approval_expired","region_unreachable","profile_changed_since","compliance_changed_since","shared_scope","absent"],"example":"absent","description":"Why a relation was dropped from the document.\n\n- `never_collected`, `awaiting_review`, `approval_expired`,\n  `region_unreachable`, `profile_changed_since`, `compliance_changed_since`,\n  `shared_scope`: the identity resolver could not state an identity for\n  this relation at all; see AdminNumberIdentityGapKind for what each one\n  means.\n- `absent`: an identity resolved, but the format's required field for it\n  was blank, so the format layer dropped the relation rather than file it\n  incomplete.\n"},"AdminReportingEntityFilingRelationGap":{"type":"object","additionalProperties":false,"description":"Present only when `outcome` is `dropped`.","required":["kind","field","reason"],"properties":{"kind":{"$ref":"#/components/schemas/AdminReportingEntityFilingRelationGapKind","readOnly":true},"field":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The document element whose value blocked the relation, set whenever the format layer is the one that dropped it. Any `kind` can carry it, because a blank required element reports whatever gap its own source had. Null when the identity resolver dropped the relation before the format layer saw it, since that names no element."},"reason":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Format-specific detail on why the field could not be stated. Null unless the format layer supplied one."}}},"AdminReportingEntityFilingRelation":{"type":"object","additionalProperties":false,"description":"One relation this filing's universe considered, kept whichever of `stated`, `truncated` or `dropped` it ended up as: the row a disclosure's provenance answers \"why isn't this number on the document\" from, not only which numbers are.","required":["number","organization_id","allocation_id","outcome"],"oneOf":[{"properties":{"outcome":{"const":"stated"},"truncated_fields":{"not":{}},"gap":{"not":{}}},"required":["provenance"]},{"properties":{"outcome":{"const":"truncated"},"gap":{"not":{}}},"required":["provenance","truncated_fields"]},{"properties":{"outcome":{"const":"dropped"},"truncated_fields":{"not":{}}},"required":["gap"]}],"properties":{"number":{"type":"string","minLength":1,"readOnly":true,"description":"Phone number in E.164 format."},"organization_id":{"$ref":"#/components/schemas/OrganizationID","readOnly":true,"description":"ID of the organization that held the number over this relation."},"allocation_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AllocatedNumberID"}],"description":"ID of the allocation this relation was read from."},"outcome":{"$ref":"#/components/schemas/AdminReportingEntityFilingRelationOutcome","readOnly":true},"provenance":{"$ref":"#/components/schemas/AdminReportingEntityFilingRelationProvenance","readOnly":true},"truncated_fields":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"The format fields shortened to fit, in format order. Present only when `outcome` is `truncated`."},"gap":{"$ref":"#/components/schemas/AdminReportingEntityFilingRelationGap","readOnly":true}}},"AdminReportingEntityFiling":{"type":"object","additionalProperties":false,"description":"One generated regulatory document: a reporting entity's schedule-period or ad hoc disclosure snapshot, and its own delivery lifecycle.","required":["id","entity_id","kind","period_date","request_ref","as_of","format","format_version","content_sha256","content_expires_at","content_purged_at","state","held_reason","counts","result_code","result_message","attempts","generated_by_actor_type","generated_by_actor_id","generated_at","delivered_at","created_at","updated_at"],"oneOf":[{"properties":{"kind":{"const":"scheduled"},"period_date":{"type":"string","format":"date"},"request_ref":{"const":null}}},{"properties":{"kind":{"const":"disclosure"},"period_date":{"const":null},"request_ref":{"type":"string","minLength":1}}}],"if":{"required":["state"],"properties":{"state":{"const":"held"}}},"then":{"properties":{"held_reason":{"$ref":"#/components/schemas/AdminReportingEntityFilingHeldReason"}}},"else":{"properties":{"held_reason":{"const":null}}},"properties":{"id":{"$ref":"#/components/schemas/ReportingEntityFilingID","readOnly":true},"entity_id":{"$ref":"#/components/schemas/ReportingEntityID","readOnly":true},"kind":{"$ref":"#/components/schemas/AdminReportingEntityFilingKind","readOnly":true},"period_date":{"type":["string","null"],"format":"date","readOnly":true,"description":"The scheduled period this filing answers for. Set when `kind` is `scheduled`, null otherwise."},"request_ref":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The disclosure request this filing answers. Set when `kind` is `disclosure`, null otherwise."},"as_of":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"The instant this filing's universe was read as of."},"format":{"type":"string","minLength":1,"readOnly":true,"description":"The regulator format this filing was rendered in, lowercase (e.g. `lu`). With `format_version` lowercased and joined by a hyphen it is the id of the format itself, e.g. `lu-ver1`."},"format_version":{"type":"string","minLength":1,"readOnly":true},"content_sha256":{"type":"string","minLength":1,"readOnly":true,"description":"Hex SHA-256 of this filing's stored content, verified on every download."},"content_expires_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"The retention deadline stamped on this filing's stored content at generation. Nothing sweeps on it yet, and a download still serves content past it; `content_purged_at` is what says the bytes are gone."},"content_purged_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this filing's stored content was deleted. Null while it is still present, which is every filing today: the sweep that sets this does not exist yet."},"state":{"$ref":"#/components/schemas/AdminReportingEntityFilingState","readOnly":true},"held_reason":{"oneOf":[{"$ref":"#/components/schemas/AdminReportingEntityFilingHeldReason"},{"type":"null"}],"readOnly":true,"description":"Set when `state` is `held`, null otherwise."},"counts":{"$ref":"#/components/schemas/AdminReportingEntityFilingCounts","readOnly":true},"result_code":{"type":["integer","null"],"readOnly":true,"description":"The regulator's acknowledgement code. Null until delivery is attempted."},"result_message":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The regulator's acknowledgement message. Null until delivery is attempted."},"attempts":{"type":"integer","readOnly":true,"description":"How many times generation has run for this filing, including re-fires."},"generated_by_actor_type":{"type":"string","minLength":1,"readOnly":true,"description":"What generated this filing, e.g. `user` for a manual re-fire or `system` for the daily schedule."},"generated_by_actor_id":{"type":"string","minLength":1,"readOnly":true},"generated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"delivered_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this filing was sent to the regulator. Null until delivery is attempted."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"delivery_attempts":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminReportingEntityFilingAttempt"},"description":"Every delivery of this filing, newest first, so a verdict a later re-fire replaced is still readable. Present only when fetching a single filing; omitted from a list."},"relations":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminReportingEntityFilingRelation"},"description":"Every relation this filing's universe considered, in a stable order. Present only when fetching a single filing; omitted from a list."}}},"AdminReportingEntityFilingList":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminReportingEntityFiling"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminReportingEntityFilingGenerate":{"type":"object","additionalProperties":false,"required":["period"],"description":"Generates the reporting entity's scheduled filing for `period`. Re-fires the same filing in place when one already exists for it: the row keeps its id and its content is regenerated, unless the existing filing was already accepted or delivered, which this call refuses to overwrite.","properties":{"period":{"type":"string","minLength":1,"format":"date","description":"The scheduled period to generate or re-fire, as a civil date.","example":"2026-09-05"}}},"AdminReportingEntityFilingDeliver":{"type":"object","additionalProperties":false,"required":["reason"],"description":"Releases the filing for regulator delivery.","properties":{"reason":{"type":"string","minLength":1,"maxLength":500,"description":"Why this filing is being released, recorded on the audit event.","example":"Monthly ILR filing, September 2026."}}},"AdminReportingFormatField":{"type":"object","additionalProperties":false,"description":"One row of a regulatory format's transform table: the XSD element it fills, the operation applied, and whether that element's compliance source is bound by a live definition today.","required":["element","operation","catalogue_key","profile_field","bound","required"],"properties":{"element":{"type":"string","minLength":1,"readOnly":true,"description":"Dotted path into the regulator's XSD, e.g. `establishmentPlace.locality`."},"operation":{"type":"string","minLength":1,"readOnly":true,"description":"The named transform this row applies, for a human reading the table."},"catalogue_key":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The compliance catalogue field this row reads. Null when the row reads none: the XSD's service block reads the number and its allocation directly, and `establishmentPlace.locality` is always a copy of the resolved city, so it takes the city row's value and its coverage."},"profile_field":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The organization business-profile field that supplies this row's value when a relation falls back to the holding organization's own profile, e.g. `RegistrationAddress.Street` or `ContactInformation.PrimaryPhone`. Null wherever `catalogue_key` is null."},"bound":{"type":["boolean","null"],"readOnly":true,"description":"Whether an active compliance definition for this format's jurisdiction currently binds the key this row's value comes from. A row that copies another one carries the source row's answer, so it is bound exactly when that one is. Null where coverage does not apply at all: the XSD's service block reads the number and its allocation directly, never a compliance answer."},"required":{"type":["boolean","null"],"readOnly":true,"description":"Whether the binding that makes `bound` true also marks the field required. Null whenever `bound` is not true."}}},"AdminReportingFormat":{"type":"object","additionalProperties":false,"description":"One registered regulatory format: its identity, plus (when fetched by id) its full transform table with today's compliance coverage.","required":["id","jurisdiction","format_version","schema_sha256"],"properties":{"id":{"type":"string","minLength":1,"readOnly":true,"description":"`<jurisdiction>-<version>`, lowercase, e.g. `lu-ver1`. A filing names the same format as `format` plus `format_version`; lowercase and join them with a hyphen to address it here."},"jurisdiction":{"$ref":"#/components/schemas/CountryCode","readOnly":true},"format_version":{"type":"string","minLength":1,"readOnly":true,"description":"The regulator's own contract version this format serialises against."},"schema_sha256":{"type":"string","minLength":1,"readOnly":true,"description":"Hex SHA-256 over every schema file this format embeds, path and contents: the regulator publishes its report schema across several XSDs, and the transform table's facets come from more than one."},"fields":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminReportingFormatField"},"description":"Every row of this format's transform table, in wire order. Present only when fetching a single format; omitted from a list."},"unbound_fields":{"type":"array","readOnly":true,"items":{"type":"string"},"description":"Every element whose value comes from compliance and that no active definition binds today, so the regulator asks for a value nothing collects. Elements read straight from the number and its allocation are outside coverage and never listed here. Present only when fetching a single format; omitted from a list."}}},"AdminReportingFormatList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminReportingFormat"}}}},"SMSAlphaSenderID":{"type":"string","minLength":1,"pattern":"^sas_[0-9a-hjkmnp-tv-z]{26}$","example":"sas_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSMSAlphaSender":{"type":"object","additionalProperties":false,"required":["id","organization_id","workspace_id","sender","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SMSAlphaSenderID","description":"Identifier of this alphanumeric sender."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization that owns this alphanumeric sender."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that owns this alphanumeric sender."},"sender":{"type":"string","minLength":1,"readOnly":true,"description":"The alphanumeric sender string, for example \"BIRD\"."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminSMSAlphaSenderList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSMSAlphaSender"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSMSAlphaSenderCreate":{"type":"object","additionalProperties":false,"required":["organization_id","workspace_id","sender"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID","description":"Organization that owns the workspace the alphanumeric sender belongs to."},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that will own the alphanumeric sender."},"sender":{"type":"string","minLength":1,"maxLength":11,"description":"The alphanumeric sender string to create, for example \"BIRD\". 1–11 letters, digits, spaces, dashes, or underscores, at least one of them a letter, and no separator at either end.\n"}}},"AdminSMSSender":{"type":"object","additionalProperties":false,"required":["id","organization_id","workspace_id","sender","source_kind","sender_type","status","source_ref","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SMSSenderID","description":"Identifier of this sender."},"cursor":{"type":"string","minLength":1,"readOnly":true,"description":"Opaque boundary for this row's (created_at, id) ordering. Included on list responses; reusable across regions as starting_after or ending_before."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization that owns this sender."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that owns this sender."},"sender":{"type":"string","minLength":1,"readOnly":true,"description":"The value messages are sent from: a number in E.164 format, a shortcode, or an alphanumeric sender string.\n"},"source_kind":{"allOf":[{"$ref":"#/components/schemas/SMSSenderSourceKind"}],"readOnly":true},"sender_type":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/SMSSenderType"},{"type":"null"}],"description":"The sender identity type recorded against this sender at provisioning, which decides what each destination country asks of it. Finer than `source_kind`: a toll-free number and an ordinary long code are both dedicated numbers but follow different rules. Null for a sender provisioned before the type was recorded. Unlike the customer-facing field, this one is not backfilled from `source_kind`, because that guess cannot tell a toll-free number from a long code and the 10DLC registration paths refuse an unrecorded type.\n"},"status":{"allOf":[{"$ref":"#/components/schemas/SMSSenderStatus"}],"readOnly":true,"description":"Where this sender stands on its own setup: whether its number's ownership paperwork is in place (`pending_number_ownership` covers unfiled and undecided alike) and the registrations it holds. Not per-destination sendability, so a sender reading `active` may still need a registration in the country the customer is asking about.\n"},"country_code":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"description":"The country of the number this sender sends from. Null for an alphanumeric sender."},"source_ref":{"type":"string","minLength":1,"readOnly":true,"description":"Typed identifier of what this sender is backed by, determined by `source_kind`: the alphanumeric sender for `alpha`, the dedicated number allocation for `number`, or the shared allocation for `shared_number`. Use it to address the sender's management endpoints (for an alphanumeric sender, its registrations and removal).\n"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminSMSSenderList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSMSSender"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSMSCountryRegistrationCreate":{"type":"object","additionalProperties":false,"required":["organization_id","country_code"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID","description":"Organization that owns the sender and is charged for the registration."},"country_code":{"$ref":"#/components/schemas/CountryCode"},"category":{"allOf":[{"$ref":"#/components/schemas/SMSMessageCategory"}],"description":"Message category to approve the sender for. Omit to approve the sender for all categories.\n"},"status":{"type":"string","enum":["approved","pending"],"default":"approved","description":"Initial status for the registration. The default \"approved\" registers the sender following the destination country's policy — instant-approve and charge where the country allows it, otherwise held for review. \"pending\" always holds the registration for review, unbilled, and charges it only when it is approved.\n"}}},"AdminSMSCountryRegistration":{"type":"object","additionalProperties":false,"required":["id","workspace_id","sender_id","country_code","status","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SMSCountryRegistrationID","description":"Identifier of this country registration, used as the billing subscription reference."},"cursor":{"type":"string","minLength":1,"readOnly":true,"description":"Opaque boundary for this row's (created_at, id) ordering. Included on list responses; reusable across regions as starting_after or ending_before."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that owns this registration."},"sender_id":{"readOnly":true,"$ref":"#/components/schemas/SMSSenderID","description":"Sender this registration gates."},"sender":{"type":"string","minLength":1,"readOnly":true,"description":"The sender's value (e.g. an alphanumeric string like \"BIRD\"). Included on the cross-workspace review-queue list so a reviewer can identify the sender without a second lookup; omitted from single-registration responses.\n"},"organization_id":{"allOf":[{"$ref":"#/components/schemas/OrganizationID"}],"readOnly":true,"description":"Organization that owns the sender. Included on the review-queue list; omitted from single-registration responses.\n"},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"readOnly":true},"registration_mode":{"allOf":[{"$ref":"#/components/schemas/SMSRegistrationMode"}],"readOnly":true,"description":"Current registration policy for this destination and sender type. Omitted if the policy or sender is no longer available."},"sender_id_behavior":{"allOf":[{"$ref":"#/components/schemas/SMSSenderIDBehavior"}],"readOnly":true,"description":"Expected sender ID behavior for this registration's message categories. Optional registration preserves the sender ID once approved. Required registration can still use an overwritten sender ID after approval. Omitted if the policy or sender is no longer available.\n"},"program":{"readOnly":true,"description":"The registration programme behind this row. Null for a country that required none, which is the state a row records rather than a programme called \"none\".\n","oneOf":[{"$ref":"#/components/schemas/SMSRegistrationProgram"},{"type":"null"}]},"category":{"type":["string","null"],"minLength":1,"readOnly":true,"x-extensible-enum":["transactional","marketing","authentication","service"],"description":"Message category this registration is approved for. Null means the registration is unrestricted and admits any category.\n"},"status":{"allOf":[{"$ref":"#/components/schemas/SMSRegistrationStatus"}],"readOnly":true},"rejection_reason":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Reason the registration was rejected, shown when the status is \"rejected\". Null for registrations that were never rejected.\n"},"cp_relayed_at":{"type":["string","null"],"format":"date-time","minLength":1,"readOnly":true,"description":"When this registration's dossier was filed with the carrier platform's originator registry, which is when its wait on a third party began. This is when the wait started, not when it was last checked. Null when nothing has been filed, either because the destination needs no third-party approval or because the filing failed (see `cp_relay_error`).\n"},"cp_relay_error":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Why the most recent attempt to file this registration with the carrier platform's originator registry failed, as the carrier platform reported it. Null when the filing succeeded or was never attempted. This is third-party text: render it as text, never as markup.\n"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"removed_at":{"type":["string","null"],"format":"date-time","minLength":1,"readOnly":true,"description":"When the registration was removed, which retires it: it no longer gates sends, cannot be approved, rejected or deregistered, and no longer holds the sender's slot for the country, so registering that country again creates a new registration. Null for a live one. Removed registrations only appear when the list is asked for them with `include_removed`.\n"}}},"AdminSMSCountryRegistrationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSMSCountryRegistration"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSMSRegistrationRejectRequest":{"type":"object","additionalProperties":false,"required":["reason"],"properties":{"reason":{"type":"string","minLength":1,"description":"Reason the registration is rejected, surfaced to the customer."}}},"AdminTFNVerification":{"description":"The admin view of a toll-free verification — the full dossier plus the owning organization and the carrier's verbatim status.","allOf":[{"$ref":"#/components/schemas/TFNVerificationBase"},{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","minLength":1,"readOnly":true,"description":"Opaque boundary for this row's (created_at, id) ordering. Included on list responses; reusable across regions as starting_after or ending_before."},"organization_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/OrganizationID"}],"description":"The organization that owns this verification."},"external_status":{"type":"string","readOnly":true,"description":"The verification's status as reported by the carrier, verbatim. Included on the admin view so a reviewer can compare it against the mapped lifecycle status when the two look out of step — for example after a missed carrier update.\n"}}}]},"AdminTFNVerificationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminTFNVerification"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"ComplianceFieldClass":{"type":"string","minLength":1,"enum":["entity","attested","evidence","judgment"],"description":"How a catalogue field's answer can be adjudicated.\n\n- `entity` means the value can be checked against a company register, and only as a set: a registration number means nothing without the jurisdiction whose register holds it.\n- `attested` means the value is adequate once it is present and well formed, with nothing further checked.\n- `evidence` means the value is a document a vendor can authenticate and read an asserted value off.\n- `judgment` means the value needs a person to read it, because adequacy turns on intent, authority, or content that a machine cannot decide.\n"},"AdminComplianceField":{"type":"object","additionalProperties":false,"required":["key","label","help_text","data_type","option_set","validation","class","identity_field","retired_at"],"properties":{"key":{"type":"string","minLength":1,"readOnly":true,"description":"Stable identifier. Immutable once created, since bindings and stored answers key on it."},"label":{"type":"string","minLength":1,"readOnly":true,"description":"Default label, overridable per binding."},"help_text":{"type":["string","null"],"readOnly":true,"description":"Default guidance, overridable per binding."},"data_type":{"readOnly":true,"$ref":"#/components/schemas/ComplianceDataType"},"option_set":{"type":["string","null"],"readOnly":true,"description":"Named option set backing an enum field. Never a query."},"validation":{"readOnly":true,"$ref":"#/components/schemas/ComplianceFieldValidation"},"class":{"readOnly":true,"$ref":"#/components/schemas/ComplianceFieldClass"},"identity_field":{"type":"boolean","readOnly":true,"description":"Whether this is a fact about the party rather than the subject. Governs prefill and verdict inheritance, and is deliberately a property of the field rather than of any binding.\n"},"retired_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this field was retired, or `null` while it is still bindable. A retired field cannot be bound to a definition, and the definitions that already bind it are untouched: their forms keep rendering and the verdicts filed against them stand. Retirement rather than deletion because a definition that has ever been live is frozen, so the form a submission was judged against has to stay readable.\n"}}},"AdminComplianceFieldList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminComplianceField"}}}},"AdminComplianceFieldCreate":{"type":"object","additionalProperties":false,"required":["key","label","data_type","identity_field"],"properties":{"key":{"type":"string","minLength":1,"description":"Stable identifier. Immutable once created, since bindings and stored answers key on it."},"label":{"type":"string","minLength":1,"description":"Default label, overridable per binding."},"help_text":{"type":"string","description":"Default guidance, overridable per binding."},"data_type":{"$ref":"#/components/schemas/ComplianceDataType"},"option_set":{"type":"string","description":"Named option set backing an enum field. Never a query."},"validation":{"$ref":"#/components/schemas/ComplianceFieldValidation"},"class":{"$ref":"#/components/schemas/ComplianceFieldClass","description":"Optional; omitting it sets judgment. An identity field cannot be judgment, so this must be set explicitly to one of the other values when `identity_field` is `true`.\n"},"identity_field":{"type":"boolean","description":"Whether this is a fact about the party rather than the subject. Governs prefill and verdict inheritance, and is deliberately a property of the field rather than of any binding.\n"}}},"AdminComplianceFieldUpdate":{"type":"object","additionalProperties":false,"properties":{"label":{"type":"string","minLength":1,"description":"Default label, overridable per binding."},"help_text":{"type":["string","null"],"description":"Default guidance, overridable per binding."},"option_set":{"type":["string","null"],"description":"Named option set backing an enum field. Never a query."},"validation":{"$ref":"#/components/schemas/ComplianceFieldValidation"},"class":{"$ref":"#/components/schemas/ComplianceFieldClass","description":"Omitted leaves the stored class unchanged. The class and `identity_field` are validated together against the merged result, not against this request alone, so a patch is rejected when the two would end up in conflict: an identity field cannot be classed judgment. That happens whether this request sets the class, sets `identity_field`, or sets neither and the stored pair already conflicts.\n"},"identity_field":{"type":"boolean","description":"Whether this is a fact about the party rather than the subject. Governs prefill and verdict inheritance, and is deliberately a property of the field rather than of any binding.\n"},"retired":{"type":"boolean","description":"Retire this field, so no new definition can bind it, or `false` to make it bindable again. The definitions that already bind it are untouched either way. Retiring an already-retired field leaves its `retired_at` where it was rather than moving it, so a repeated request changes nothing.\n"}}},"AdminComplianceSection":{"type":"object","additionalProperties":false,"required":["key","label"],"properties":{"key":{"type":"string","minLength":1,"readOnly":true,"description":"Stable identifier. Immutable once created, since bindings key on it."},"label":{"type":"string","minLength":1,"readOnly":true,"description":"What to call this section when presenting its fields together."}}},"AdminComplianceSectionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminComplianceSection"}}}},"AdminComplianceSectionCreate":{"type":"object","additionalProperties":false,"required":["key","label"],"properties":{"key":{"type":"string","minLength":1,"description":"Stable identifier. Immutable once created, since bindings key on it."},"label":{"type":"string","minLength":1,"description":"What to call this section when presenting its fields together."}}},"AdminComplianceSectionUpdate":{"type":"object","additionalProperties":false,"properties":{"label":{"type":"string","minLength":1,"description":"What to call this section when presenting its fields together."}}},"AdminComplianceDefinitionStatus":{"type":"string","minLength":1,"enum":["draft","live","retired"],"description":"Where a definition version stands at its coordinate.\n\n- `draft` means it has never been published, and is the only state in which it\n  can still be edited.\n- `live` means it is the version the coordinate resolves to now.\n- `retired` means it was published and no longer holds the coordinate, either\n  because it was withdrawn and nothing replaced it or because a newer version\n  superseded it.\n\nUnlike `published`, this separates the two ways a version can read as not live.\n"},"ComplianceDefinitionID":{"type":"string","minLength":1,"pattern":"^cdf_[0-9a-hjkmnp-tv-z]{26}$","example":"cdf_01krdgeqcxet5s7t44vh8rt9mg"},"AdminComplianceBinding":{"type":"object","additionalProperties":false,"required":["field_key","section_key","required"],"properties":{"field_key":{"type":"string","minLength":1,"description":"A field that must already exist in the catalogue."},"section_key":{"type":"string","minLength":1,"description":"Groups bindings for presentation."},"required":{"type":"boolean","description":"Whether readiness needs this. A property of the country's demand rather than of the field, so it varies between definitions binding the same field.\n"},"label_override":{"type":"string","description":"Replaces the catalogue label for this definition only."},"help_override":{"type":"string","description":"Replaces the catalogue help text for this definition only."},"template_asset":{"type":"string","description":"A blank form for a document slot, such as a letter of authorisation."},"validity_days":{"type":"integer","minimum":1,"description":"How long an approval of this requirement stays good, in days. Absent means the approval never goes stale. When set, approving this requirement stamps an expiry on the answer, and once that date passes the answer stops satisfying the requirement and the customer is asked for it again. The registration keeps its authorisation while that happens.\n"},"max_items":{"type":"integer","minimum":2,"description":"How many answers this requirement takes, when it takes more than one. Absent means one, which is the ordinary case; the minimum is 2, because a one-answer list is a single-valued requirement written the long way. When set, the customer answers with a list and every element is checked against the field's validation individually. The whole list is one item and takes one verdict, so a reviewer approves or rejects the set rather than its elements.\n"}}},"AdminComplianceDefinition":{"readOnly":true,"type":"object","additionalProperties":false,"required":["id","subject_kind","program","country_code","version","grace_days","requires_catch_up","published","status","published_at","bindings","verifiable","auto_approvable","ai_prompt_extension"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ComplianceDefinitionID"},"subject_kind":{"readOnly":true,"$ref":"#/components/schemas/ComplianceSubjectKind"},"program":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/ComplianceProgram"}],"description":"The programme this definition covers. A number definition with `*` is the country fallback, used when no active exact programme definition exists."},"country_code":{"readOnly":true,"$ref":"#/components/schemas/CountryCode"},"version":{"type":"integer","readOnly":true,"description":"Increments per coordinate. One version is active at a time."},"requires_catch_up":{"readOnly":true,"type":"boolean","description":"Whether publishing this version asks registrations held on an older version\nto catch up by `grace_days`, which has nothing to measure when this is false.\nFalse makes the version govern new registrations only, which is what a\ncoordinate still being revised needs: otherwise every edit dates a deadline\nagainst every customer already registered there.\n"},"grace_days":{"type":"integer","minimum":0,"readOnly":true,"description":"How long an existing grant has to catch up when this version publishes."},"published":{"type":"boolean","readOnly":true,"description":"Whether this version is the live one for its coordinate. It does not tell you whether the version can be edited: publishing freezes a version permanently, and a version stays frozen after a later one supersedes it, at which point this reads false again. Only a version that has never been published can be changed.\n"},"status":{"readOnly":true,"$ref":"#/components/schemas/AdminComplianceDefinitionStatus"},"published_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this version went live. Null for a draft, and for a retired version, whose stamp is cleared when it stops holding its coordinate.\n"},"bindings":{"type":"array","readOnly":true,"description":"The fields this definition requires, in presentation order.","items":{"$ref":"#/components/schemas/AdminComplianceBinding"}},"verifiable":{"type":["boolean","null"],"readOnly":true,"description":"Whether this version's required bindings carry enough entity fields to identify a company against a register, frozen at publish. Null means it was never computed, either because the version is still a draft or because it was published before the check existed. Null never means it failed.\n"},"auto_approvable":{"type":["boolean","null"],"readOnly":true,"description":"Whether this version can complete unattended: verifiable, and no required binding needs a person to judge it. Frozen at publish. Null means it was never computed, either because the version is still a draft or because it was published before the check existed, never that it failed.\n"},"ai_prompt_extension":{"x-sensitive":true,"type":"string","minLength":0,"maxLength":32000,"description":"Additional assessment instructions for this definition, frozen when the version is published. An empty value adds nothing to the register prompt. The limit is 32000 characters."}}},"AdminComplianceDefinitionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminComplianceDefinition"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminComplianceDefinitionCreate":{"type":"object","additionalProperties":false,"required":["subject_kind","program","country_code","grace_days","bindings"],"properties":{"subject_kind":{"$ref":"#/components/schemas/ComplianceSubjectKind"},"program":{"allOf":[{"$ref":"#/components/schemas/ComplianceProgram"}],"description":"The programme this definition covers. For the number register, use `*` as the fallback for every number type in this country. An active exact programme definition takes precedence over the fallback."},"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"description":"The country whose rules this definition carries. An `identity` definition is bound to no country and takes `ZZ`, the international sentinel, which no other subject kind accepts."},"requires_catch_up":{"type":"boolean","default":true,"description":"Whether publishing this version asks registrations on an older version to\ncatch up by `grace_days`. Omit it for the ordinary case; false makes the\nversion govern new registrations only, and leaves `grace_days` with\nnothing to measure.\n"},"grace_days":{"type":"integer","minimum":0,"description":"How long an existing grant has to catch up when this version publishes.\nStored but never read when `requires_catch_up` is false, where `0` says\nplainly that no catch-up was asked for. Required either way: a definition\nthat publishes with no period stated has none to fall back on, and an\nomission defaulting to `0` under catch-up would be due immediately.\n"},"bindings":{"type":"array","description":"The fields this definition requires, in presentation order.","items":{"$ref":"#/components/schemas/AdminComplianceBinding"}},"ai_prompt_extension":{"x-sensitive":true,"type":["string","null"],"maxLength":32000,"description":"Additional assessment instructions for this definition. An empty or null value adds nothing to the register prompt. The limit is 32000 characters."}}},"AdminComplianceDefinitionUpdate":{"type":"object","additionalProperties":false,"properties":{"requires_catch_up":{"type":"boolean","description":"Whether publishing this version asks registrations on an older version to\ncatch up. False makes the version govern new registrations only, and leaves\n`grace_days` with nothing to measure. Omitted leaves the current value\nalone, like every other field here.\n"},"grace_days":{"type":"integer","minimum":0,"description":"How long an existing grant has to catch up when this version publishes."},"bindings":{"type":"array","description":"The fields this definition requires, in presentation order.","items":{"$ref":"#/components/schemas/AdminComplianceBinding"}},"ai_prompt_extension":{"x-sensitive":true,"type":["string","null"],"maxLength":32000,"description":"Additional assessment instructions for this definition. Send an empty or null value to remove the definition-specific addition; omit it to keep the current value. The limit is 32000 characters."}}},"AdminComplianceReorder":{"type":"object","additionalProperties":false,"description":"The complete ordered list of this definition's field keys. Sort values are rewritten as 1..n in one transaction, so a partial list is rejected.\n","required":["field_keys"],"properties":{"field_keys":{"type":"array","minItems":1,"items":{"type":"string","minLength":1}}}},"ComplianceRegisterPromptID":{"type":"string","minLength":1,"pattern":"^crp_[0-9a-hjkmnp-tv-z]{26}$","example":"crp_01krdgeqcxet5s7t44vh8rt9mg"},"AdminComplianceRegisterPrompt":{"readOnly":true,"type":"object","additionalProperties":false,"required":["subject_kind","prompt","revision"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ComplianceRegisterPromptID"},"subject_kind":{"readOnly":true,"$ref":"#/components/schemas/ComplianceSubjectKind"},"prompt":{"type":"string","minLength":0,"maxLength":32000,"description":"The instruction prepended to assessments for this register. An empty value disables automated assessment for the register. The limit is 32000 characters."},"revision":{"type":"integer","format":"int64","minimum":0,"description":"Increments after each saved change. Zero means the register has no saved prompt."},"updated_by":{"readOnly":true,"$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","format":"date-time","readOnly":true},"updated_at":{"type":"string","format":"date-time","readOnly":true}}},"AdminComplianceRegisterPromptUpdate":{"type":"object","additionalProperties":false,"required":["prompt","revision"],"properties":{"prompt":{"type":"string","minLength":0,"maxLength":32000,"description":"The instruction prepended to assessments for this register. Send an empty value to disable automated assessment. The limit is 32000 characters."},"revision":{"type":"integer","format":"int64","minimum":0,"description":"The revision returned by the latest read. Use zero when no prompt has been saved."}}},"AdminComplianceAIDecisionFilter":{"type":"string","minLength":1,"description":"Filter by the effective assessment decision, or none for submissions without a current assessment.","enum":["none","pending","approve","review","reject"]},"ComplianceObligationID":{"type":"string","minLength":1,"pattern":"^cob_[0-9a-hjkmnp-tv-z]{26}$","example":"cob_01krdgeqcxet5s7t44vh8rt9mg"},"ComplianceGrantID":{"type":"string","minLength":1,"pattern":"^cgr_[0-9a-hjkmnp-tv-z]{26}$","example":"cgr_01krdgeqcxet5s7t44vh8rt9mg"},"AdminComplianceObligation":{"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","additionalProperties":false,"required":["id","grant_id","reason","state","due_at","target_definition_id","target_version","extended_to","note"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ComplianceObligationID"},"grant_id":{"readOnly":true,"$ref":"#/components/schemas/ComplianceGrantID","description":"The standing authorisation this demand is dated against."},"reason":{"readOnly":true,"$ref":"#/components/schemas/ComplianceObligationReason"},"state":{"readOnly":true,"$ref":"#/components/schemas/ComplianceObligationState","description":"Where the obligation stands: open until the due date passes, satisfied once the registration reached the version asked for, lapsed when the date passed unmet, extended while a later date is in force.\n"},"due_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"The date first asked for. Where an extension was granted, `extended_to` is the date that binds and this is kept as the record of what was originally asked.\n"},"target_definition_id":{"readOnly":true,"$ref":"#/components/schemas/ComplianceDefinitionID","description":"The version the registration must reach. The submission's own version for an evidence expiry, a newer one for a policy change.\n"},"target_version":{"type":["integer","null"],"minimum":1,"readOnly":true,"description":"That version's number, so a reviewer need not resolve the id. Null when the version behind `target_definition_id` can no longer be read, which leaves the rest of the queue readable rather than failing the page over one row.\n"},"extended_to":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"A deliberate extension of the due date, and the date that binds while it is set. Null when no extension has been granted.\n"},"note":{"type":["string","null"],"readOnly":true,"description":"Why the date was moved, when it was."}}}]},"AdminComplianceSubmissionItem":{"type":"object","additionalProperties":false,"required":["id","key","state","value","attachment_id","values","attachment_ids","reason","decided_at","decided_by","expires_at","inherited_from_item_id"],"properties":{"inherited_from_item_id":{"oneOf":[{"$ref":"#/components/schemas/ComplianceItemID"},{"type":"null"}],"readOnly":true,"description":"The already-approved answer this item was born holding, when the same answer to the same requirement had already been approved for this party and was allowed to travel here. What allows it is the field's class: an attested answer travels on its own, while an entity or evidence answer must have been approved against the same business registration country. Null for anything a reviewer decided here. An inherited item carries the original verdict and its expiry, so nothing about it was reviewed for this registration.\n"},"id":{"readOnly":true,"$ref":"#/components/schemas/ComplianceItemID"},"key":{"type":"string","minLength":1,"readOnly":true,"description":"The requirement this answers."},"state":{"readOnly":true,"$ref":"#/components/schemas/ComplianceItemState"},"value":{"type":["string","null"],"readOnly":true,"description":"The submitted answer, for a requirement answered inline. Every answer travels as a string whatever its data type, so a number reads back as `\"1500\"` and a yes-or-no as `\"true\"`. Use the data type to parse it. Null for a requirement that takes a document.\n"},"attachment_id":{"readOnly":true,"description":"The document supplied, for a requirement that takes one. Null otherwise.","oneOf":[{"$ref":"#/components/schemas/AttachmentID"},{"type":"null"}]},"values":{"type":["array","null"],"readOnly":true,"description":"The submitted answers, for a requirement that takes several and is answered inline. Null for one answered once. In the order the customer sent them, which for a keyword set is part of what is being declared. The whole list is one item and takes one verdict, so decide the set rather than an element.\n","items":{"type":"string","minLength":1}},"attachment_ids":{"type":["array","null"],"readOnly":true,"description":"The documents supplied, for a requirement that takes several. Null otherwise.\n","items":{"$ref":"#/components/schemas/AttachmentID"}},"reason":{"type":["string","null"],"readOnly":true,"description":"Why this was rejected, when it was. Null otherwise."},"decided_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this was decided. Null while no decision has been made."},"decided_by":{"readOnly":true,"description":"Which staff member decided this. Null while undecided.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this answer goes stale and must be supplied again. Null when it does not expire."}}},"AdminComplianceSubjectRef":{"type":"object","additionalProperties":false,"description":"The sender, number, or business party a registration is about, resolved for display. Read on the single submission only.\n","required":["label","sms_sender_id"],"properties":{"label":{"type":"string","minLength":1,"readOnly":true,"description":"The value a human recognises: an alphanumeric sender such as \"BIRD\", a phone number in E.164, or a party's business name.\n"},"sms_sender_id":{"readOnly":true,"description":"The sender this country registration hangs off. Null for a number or identity subject, whose own identifier is the submission's subject_id.\n","oneOf":[{"$ref":"#/components/schemas/SMSSenderID"},{"type":"null"}]}}},"AdminComplianceKYBVerdict":{"type":"string","minLength":1,"readOnly":true,"description":"What the provider's transaction decided, reduced to the two values Bird acts on.\nTwo values, not a grade. `not_strict` covers every outcome short of an approval, including one the provider referred to its own reviewer, and decides nothing at all rather than deciding part of the form: the whole submission reaches a human, which is what would have happened with no provider.\n","enum":["strict","not_strict"],"example":"strict"},"AdminComplianceKYBCheckStatus":{"type":"string","minLength":1,"readOnly":true,"description":"What the provider reported for one named check.\n`not_applicable` covers both a check its template disabled and one it could not attempt for want of data. It is deliberately not the same as `passed`: nothing examined the answer, so nothing may stand in for having examined it.\n","enum":["passed","failed","not_applicable"],"example":"passed"},"AdminComplianceKYBCheckOutcome":{"type":"object","additionalProperties":false,"description":"One named check as the provider returned it, whatever its status and whether or not Bird's decision rests on it. The verdict distils these away, so a reviewer holding a form the provider declined has nowhere else to read what it actually said.\n","required":["name","status"],"properties":{"name":{"type":"string","minLength":1,"readOnly":true,"description":"The provider's own name for the check, recorded verbatim rather than translated. A name Bird does not recognise is still shown, because the reviewer's question is what ran, not what Bird gated on.\n","example":"database_business_identity_comparison"},"status":{"readOnly":true,"$ref":"#/components/schemas/AdminComplianceKYBCheckStatus"}}},"AdminComplianceKYBCheck":{"type":"object","additionalProperties":false,"description":"One provider check made against this submission, and the evidence for the verdict it produced.\nA submission can be checked more than once: an expired registration is resupplied on the same submission and checked again, so these accumulate rather than replacing one another.\nThe request Bird sent is deliberately absent. It carries the answers as transmitted, including anything a provider extracted from an uploaded document, and no surface that reads a submission displays it.\n","required":["transaction_id","verdict","outcomes","matched_fields","asserted_jurisdiction","queried_jurisdiction","checked_at"],"properties":{"transaction_id":{"type":"string","minLength":1,"readOnly":true,"description":"The provider's own identifier for the check. What makes a later finding that names nothing but a transaction attributable back to this registration.\n","example":"txn_A83o64k6yU2ikcwRpDJzyWrSpZ9gfd"},"verdict":{"readOnly":true,"description":"Null while the check is still in flight: it was filed, and the provider has not answered yet. A reviewer seeing null is waiting on the provider rather than on themselves.\n","oneOf":[{"$ref":"#/components/schemas/AdminComplianceKYBVerdict"},{"type":"null"}]},"outcomes":{"type":"array","readOnly":true,"description":"Every named check the response carried. Empty for a check that produced none, which is itself worth seeing.\n","items":{"$ref":"#/components/schemas/AdminComplianceKYBCheckOutcome"}},"matched_fields":{"type":"array","readOnly":true,"description":"The field keys the register matched TOGETHER, as a tuple rather than a union of separately confirmed facts. This is what governs whether another country's form may reuse these answers, so a field absent here was not matched even when the same submission approved it.\n","items":{"type":"string","minLength":1},"example":["legal_entity_name","business_registration_number"]},"asserted_jurisdiction":{"readOnly":true,"description":"The country the customer answered for the business registration, at the moment the check was made.\n","$ref":"#/components/schemas/CountryCode"},"queried_jurisdiction":{"readOnly":true,"description":"The register the provider reports having actually looked in, null when it reports none. Kept apart from the asserted country because where the two differ, only this one describes what was really checked.\n","oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}]},"checked_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the check was made. Orders the checks on a submission."}}},"ComplianceAIAssessmentID":{"type":"string","minLength":1,"pattern":"^caa_[0-9a-hjkmnp-tv-z]{26}$","example":"caa_01krdgeqcxet5s7t44vh8rt9mg"},"AdminComplianceAIAssessmentStatus":{"type":"string","minLength":1,"enum":["pending","running","completed","failed"],"description":"The processing state of a compliance assessment:\n\n- `pending`: Queued or waiting for required verification evidence; wait for processing.\n- `running`: Gathering evidence or running inference; wait for the result.\n- `completed`: An automated outcome or staff disposition was recorded; inspect the decision.\n- `failed`: Processing exhausted its retries; human review is required unless a prior staff disposition remains effective."},"AdminComplianceAIDecision":{"type":"string","minLength":1,"enum":["pending","approve","reject","review"],"description":"A compliance assessment outcome."},"AdminComplianceAIRiskBand":{"type":"string","minLength":1,"enum":["low","medium","high","unknown"]},"AdminComplianceAIFindingSeverity":{"type":"string","minLength":1,"enum":["information","warning","critical"]},"AdminComplianceAIFinding":{"type":"object","readOnly":true,"additionalProperties":false,"required":["code","severity","summary","evidence_refs","field_keys"],"properties":{"code":{"type":"string","minLength":1},"severity":{"$ref":"#/components/schemas/AdminComplianceAIFindingSeverity"},"summary":{"type":"string","minLength":1},"evidence_refs":{"type":"array","items":{"type":"string"}},"field_keys":{"type":"array","items":{"type":"string"}}}},"AdminComplianceAIResult":{"type":"object","readOnly":true,"additionalProperties":false,"required":["verdict","risk_band","summary","findings","missing_evidence","customer_reason"],"properties":{"verdict":{"description":"The original automated recommendation, preserved after any subsequent human disposition.","$ref":"#/components/schemas/AdminComplianceAIDecision"},"risk_band":{"$ref":"#/components/schemas/AdminComplianceAIRiskBand"},"summary":{"type":"string","minLength":1},"findings":{"type":"array","items":{"$ref":"#/components/schemas/AdminComplianceAIFinding"}},"missing_evidence":{"type":"array","items":{"type":"string"}},"customer_reason":{"type":"string","minLength":0,"description":"The customer-facing explanation when the result rejects the submission."}}},"AdminComplianceAIReviewDisposition":{"type":"string","minLength":1,"enum":["approve","reject"],"description":"The staff disposition that confirms or replaces the automated assessment outcome."},"AdminComplianceAIHumanDecision":{"type":"object","readOnly":true,"additionalProperties":false,"required":["decision","reason","actor","created_at"],"properties":{"decision":{"$ref":"#/components/schemas/AdminComplianceAIReviewDisposition"},"reason":{"type":"string","minLength":0,"description":"The reviewer's explanation. Empty when an approval was recorded without a reason."},"actor":{"$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminComplianceAIAssessment":{"type":"object","readOnly":true,"additionalProperties":false,"required":["id","basis_assessment_id","status","decision","register_prompt","register_revision","definition_prompt","system_version","model","persona_status","applied_at","failure_reason","human_decisions","notice_pending","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/ComplianceAIAssessmentID"},"basis_assessment_id":{"description":"The human-dispositioned assessment this advisory reassessment follows, or null when this assessment can become the effective decision.","oneOf":[{"$ref":"#/components/schemas/ComplianceAIAssessmentID"},{"type":"null"}]},"status":{"$ref":"#/components/schemas/AdminComplianceAIAssessmentStatus"},"decision":{"description":"The outcome of this assessment, including any subsequent human disposition. When basis_assessment_id is set, this is advisory and does not replace the effective assessment.","$ref":"#/components/schemas/AdminComplianceAIDecision"},"register_prompt":{"type":"string","minLength":1},"register_revision":{"type":"integer","format":"int64"},"definition_prompt":{"x-sensitive":true,"type":"string","minLength":0},"system_version":{"type":"string","minLength":1},"model":{"type":"string","minLength":0},"persona_status":{"type":"string","minLength":1},"input_snapshot":{"x-sensitive":true,"type":["object","null"],"additionalProperties":true,"description":"The frozen business and submission evidence supplied to this assessment."},"result":{"x-sensitive":true,"description":"The automated result, which can contain protected identity evidence. Absent without identity-review access; null before inference completes.","oneOf":[{"$ref":"#/components/schemas/AdminComplianceAIResult"},{"type":"null"}]},"applied_at":{"type":["string","null"],"minLength":1,"format":"date-time"},"failure_reason":{"type":["string","null"],"minLength":0},"human_decisions":{"type":"array","items":{"$ref":"#/components/schemas/AdminComplianceAIHumanDecision"}},"notice_pending":{"type":"boolean"},"created_at":{"type":"string","minLength":1,"format":"date-time"},"updated_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminComplianceDecisionState":{"type":"string","minLength":1,"enum":["approved","rejected","not_applicable"],"description":"The verdict. Only these three can be set by an operator."},"AdminComplianceReviewDraft":{"type":"object","additionalProperties":false,"description":"One staged verdict, visible only to reviewers. Nothing here reaches the customer until the review is published.\n","required":["field_key","state","reason","updated_at"],"properties":{"field_key":{"type":"string","minLength":1,"readOnly":true,"description":"The requirement this verdict is staged against."},"state":{"readOnly":true,"$ref":"#/components/schemas/AdminComplianceDecisionState"},"reason":{"type":["string","null"],"readOnly":true,"description":"Why this answer is being rejected, when it is. Null otherwise."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this verdict was last staged."}}},"AdminComplianceSubmission":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","additionalProperties":false,"required":["cursor","id","workspace_id","organization_id","subject_kind","subject_id","program","country_code","definition_id","satisfied","obligations","items","general_reasons","drafts","submitted_by","subject","persona_verifications","kyb_checks","ai_assessment_id","ai_assessments"],"properties":{"cursor":{"type":"string","minLength":1,"description":"Opaque pagination boundary for this exact row, encoding (created_at, id). Reusable across regions; pass it unchanged as `starting_after` or `ending_before`.\n"},"id":{"readOnly":true,"$ref":"#/components/schemas/ComplianceSubmissionID"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that submitted this."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization owning that workspace."},"subject_kind":{"readOnly":true,"$ref":"#/components/schemas/ComplianceSubjectKind"},"subject_id":{"$ref":"#/components/schemas/ComplianceSubjectID","readOnly":true},"program":{"readOnly":true,"$ref":"#/components/schemas/ComplianceProgram"},"country_code":{"readOnly":true,"$ref":"#/components/schemas/CountryCode"},"definition_id":{"$ref":"#/components/schemas/ComplianceDefinitionID","readOnly":true,"description":"The definition version this submission was validated against. A coordinate can have several versions live at once during a grace period, so this is what identifies the rules actually applied.\n"},"obligations":{"type":"array","readOnly":true,"description":"What this registration has been asked for to keep its authorisation, newest first, closed ones included. A resupply under review is usually answering one of these, and which one decides what the reviewer is looking for.\n","items":{"$ref":"#/components/schemas/AdminComplianceObligation"}},"satisfied":{"type":"boolean","readOnly":true,"description":"Whether this country's requirements are met. True once every required item is approved or marked not applicable. A verifier request can leave this true; submitting a correction makes it false until all selected answers pass review, including optional answers.\n"},"general_reasons":{"$ref":"#/components/schemas/ComplianceGeneralReasons","readOnly":true},"items":{"type":"array","readOnly":true,"description":"Every answer and where it stands. Each is decided on its own.","items":{"$ref":"#/components/schemas/AdminComplianceSubmissionItem"}},"submitted_by":{"readOnly":true,"description":"The user who supplied the answers. Null when no user was behind the call, which is normal for an API-key caller. A resupply overwrites it, so this is whoever is currently working the form.\n","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"subject":{"readOnly":true,"description":"The sender, number, or business party this registration is about. Null when the subject no longer resolves, such as a released number or a removed registration. Resolved on the single-submission read only, so every other response, the list and the verdict responses alike, reports it null.\n","oneOf":[{"$ref":"#/components/schemas/AdminComplianceSubjectRef"},{"type":"null"}]},"kyb_checks":{"type":"array","readOnly":true,"description":"The provider checks made against this submission, newest first. Empty for a registration no check was ever made against, which is the ordinary case: most are decided by a person. Resolved on the single-submission read only, like the verification history below.\n","items":{"$ref":"#/components/schemas/AdminComplianceKYBCheck"}},"ai_assessment_id":{"description":"The current automated assessment, or null when this submission has no assessment.","oneOf":[{"$ref":"#/components/schemas/ComplianceAIAssessmentID"},{"type":"null"}]},"ai_assessments":{"type":"array","description":"Automated assessment history, newest first. Single-submission reads and staging or publishing responses include the history; list responses include the current assessment.","items":{"$ref":"#/components/schemas/AdminComplianceAIAssessment"}},"persona_verifications":{"type":"array","readOnly":true,"description":"The organization's provider verification history, every requirement, newest first and capped at the ten most recent. Resolved on the single-submission read only, so every other response, the list and the verdict responses alike, reports it empty.\n","items":{"$ref":"#/components/schemas/AdminTrustVerification"}},"drafts":{"type":"array","readOnly":true,"description":"The staged verdicts nobody outside the review can see yet. Empty when nothing is staged. The list endpoint always reports it empty; single-submission reads and staging responses include current drafts.\n","items":{"$ref":"#/components/schemas/AdminComplianceReviewDraft"}},"closed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this submission was permanently declined. Null while it remains open."},"closure_reason":{"type":["string","null"],"readOnly":true,"description":"The decline reason for a closed submission, or null while open."},"closed_by":{"readOnly":true,"description":"Staff member who closed the submission, or null while open.","oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}]},"closure_notify_customer":{"type":"boolean","readOnly":true,"description":"Whether the closure requests a decline email."}}}]},"AdminComplianceSubmissionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminComplianceSubmission"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminComplianceDecision":{"type":"object","additionalProperties":false,"description":"A verdict on one item. Rejecting requires a reason, and the reason reaches the customer.\n","required":["state"],"properties":{"state":{"$ref":"#/components/schemas/AdminComplianceDecisionState"},"reason":{"type":"string","description":"Why. Required when the state is rejected."}}},"AdminComplianceSubmissionClose":{"type":"object","additionalProperties":false,"required":["notify_customer"],"properties":{"reason":{"type":"string","maxLength":2000,"description":"Customer-facing decline reason. Omit or leave blank to use the generic decline."},"notify_customer":{"type":"boolean","description":"Whether to send a decline email to the customer."}}},"AdminComplianceAIReviewDecision":{"type":"object","additionalProperties":false,"required":["assessment_id","decision","reason"],"properties":{"assessment_id":{"$ref":"#/components/schemas/ComplianceAIAssessmentID"},"decision":{"$ref":"#/components/schemas/AdminComplianceAIReviewDisposition"},"reason":{"type":"string","minLength":0,"maxLength":2000,"description":"The reason for the disposition. May be empty for approval. Rejection requires a nonblank reason that customers see as correction guidance."}}},"AdminComplianceReviewPublish":{"type":"object","additionalProperties":false,"properties":{"ai_decision":{"$ref":"#/components/schemas/AdminComplianceAIReviewDecision"}}},"AdminComplianceVerdictEntry":{"type":"object","additionalProperties":false,"description":"One verdict in a batch. The verdict half mirrors the single decide body; the field key names the answer, since a batch entry has no path segment to name it.\n","required":["field_key","state"],"properties":{"field_key":{"type":"string","minLength":1,"description":"The field this verdict is on."},"state":{"$ref":"#/components/schemas/AdminComplianceDecisionState"},"reason":{"type":"string","description":"Why. Required when the state is rejected."}}},"AdminComplianceVerdictsCreate":{"type":"object","additionalProperties":false,"description":"A batch of verdicts to stage at once, at most one per field. The batch validates as a whole and is refused whole: one failing entry refuses every entry, and nothing is staged partially.\n","required":["verdicts"],"properties":{"verdicts":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/AdminComplianceVerdictEntry"},"description":"The verdicts to stage. At most one hundred per call."}}},"AdminTCRBrandImport":{"type":"object","additionalProperties":false,"required":["organization_id","workspace_id","mbcr_brand_id"],"properties":{"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"mbcr_brand_id":{"type":"string","format":"uuid","minLength":36,"maxLength":36,"description":"MBCR brand UUID, not the downstream TCR brand ID."}}},"AdminTCRImportableBrand":{"type":"object","readOnly":true,"additionalProperties":false,"description":"An MBCR brand the destination organization's CP credentials can read, offered as a candidate for import. The registration fields carry the registry's own values, not Bird's. A brand with `linked_brand_id` set is already recorded against a Bird brand and cannot be imported again.\n","required":["mbcr_brand_id","tcr_brand_id","display_name","company_name","entity_type","status","identity_status","country","linked_brand_id","linked_workspace_id"],"properties":{"mbcr_brand_id":{"type":"string","format":"uuid","minLength":36,"maxLength":36,"description":"MBCR brand UUID, the value to import by."},"tcr_brand_id":{"type":["string","null"],"description":"Identifier The Campaign Registry assigned the brand, such as `BF9FEJS`. Null until MBCR has pushed the brand to the registry.\n"},"display_name":{"type":"string","minLength":1,"maxLength":255,"description":"Brand name as registered at MBCR."},"company_name":{"type":["string","null"],"description":"Legal company name as registered at MBCR, when it differs from the display name."},"entity_type":{"type":"string","minLength":1,"maxLength":64,"description":"Business entity type the registry holds, lowercased. Not every type can be imported; an unsupported one is refused on import rather than hidden here.\n"},"status":{"type":"string","minLength":1,"maxLength":64,"description":"Registration status as reported by MBCR, such as `ACTIVE` or `PENDING`."},"identity_status":{"type":"string","minLength":1,"maxLength":64,"description":"Identity vetting verdict as reported by MBCR, such as `VERIFIED`."},"country":{"type":["string","null"],"description":"ISO 3166-1 alpha-2 country of the registered business."},"linked_brand_id":{"oneOf":[{"$ref":"#/components/schemas/TCRBrandID"},{"type":"null"}],"description":"Bird brand this MBCR brand is already linked to, or null when it is importable. A linked brand may since have been deleted in Bird, and the MBCR ID stays claimed either way.\n"},"linked_workspace_id":{"oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}],"description":"Workspace holding the linked Bird brand, or null when it is importable."}}},"AdminTCRImportableBrandList":{"type":"object","readOnly":true,"additionalProperties":false,"description":"MBCR brands offered as import candidates, newest first. The list is capped rather than paged: when `truncated` is true the oldest brands are missing, and a `q` search is what reaches them.\n","required":["data","truncated"],"properties":{"data":{"type":"array","description":"Candidate brands, most recently created first.","items":{"$ref":"#/components/schemas/AdminTCRImportableBrand"}},"truncated":{"type":"boolean","description":"Whether the registry reported more candidates than this response carries. Narrow the list with `q` to reach them. A registry read that cannot be completed fails the request instead of reporting a short list here.\n"}}},"AdminTCRVettingClass":{"type":"string","minLength":1,"enum":["auth_plus","standard","enhanced","political"],"description":"Which TCR external vetting product an attempt ordered or imported. auth_plus is required for public_profit brands to create any campaign; standard, enhanced, and political apply to any entity type.\n"},"AdminTCRVettingStatus":{"type":"string","minLength":1,"enum":["pending","active","unscored","failed","expired"],"description":"Lifecycle state of one external vetting attempt."},"AdminTCRBrandVetting":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","description":"One attempt to order or import external vetting for a 10DLC brand, as staff see it. This read is the staff view of it; customers order and read their own attempts through the workspace-scoped vettings endpoints. Auth+, Standard, Enhanced, and Political share this one shape, differing only by vetting_class and the provider that scored it.","required":["id","brand_id","organization_id","workspace_id","vetting_class","status","evp_id","vetting_id","reasons"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/TCRBrandVettingID"},"brand_id":{"readOnly":true,"$ref":"#/components/schemas/TCRBrandID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"vetting_class":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminTCRVettingClass"}]},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminTCRVettingStatus"}]},"evp_id":{"type":["string","null"],"readOnly":true,"description":"Identifier of the external vetting provider that scored this attempt, as the registry names it. Null until a provider is assigned."},"vetting_id":{"type":["string","null"],"readOnly":true,"description":"The provider's own identifier for this attempt, used to chase it with the provider directly. Null until the order reaches the registry."},"score":{"type":["integer","null"],"readOnly":true,"description":"The 0-100 score the provider returned, when one was given. Null while pending, or if the provider reported none."},"reasons":{"type":"array","readOnly":true,"description":"Feedback the provider returned about this attempt, one entry per reason. Empty when none was given.","items":{"type":"string","minLength":1}},"vetted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this attempt reached a terminal state (active, failed, or expired). Null while pending."}}}]},"AdminTCRBrandVettingList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminTCRBrandVetting"}}}},"AdminTCRBrandVettingPage":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminTCRBrandVetting"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminTCRCampaignNumberLink":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","description":"One US long code registered under a 10DLC campaign, as staff see it. This read is the staff view of it; customers assign and read their own numbers through the workspace-scoped campaign numbers endpoints. Rows are kept after a number is removed, so a campaign's history of numbers stays legible.","required":["id","campaign_id","organization_id","workspace_id","sender_id","phone_number","status","external_link_status","rejection_reasons","linked_at","unlinked_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/TCRCampaignNumberLinkID"},"campaign_id":{"readOnly":true,"$ref":"#/components/schemas/TCRCampaignID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"sender_id":{"readOnly":true,"description":"The sender the number is held as.","allOf":[{"$ref":"#/components/schemas/SMSSenderID"}]},"phone_number":{"type":"string","readOnly":true,"minLength":1,"description":"The number in E.164 format.","example":"+15551234567"},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TCRCampaignNumberLinkStatus"}]},"external_link_status":{"type":["string","null"],"readOnly":true,"description":"The campaign registry's own word for where this number stands, unmapped, for chasing a case with the registry directly. Null until the registry has reported on it."},"rejection_reasons":{"type":"array","readOnly":true,"description":"Why the carriers did not accept this number for the campaign, one entry per problem. Empty unless the number was refused. Removing a refused number leaves them in place, so an `unlinked` row can still carry the reasons it failed for.","items":{"$ref":"#/components/schemas/TCRSubmissionRejectionReason"}},"linked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the carriers confirmed the number for this campaign. Null until then."},"unlinked_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the number was removed from this campaign. Null while it is still linked."}}}]},"AdminTCRCampaignNumberLinkPage":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminTCRCampaignNumberLink"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminTCRCampaignNumberLinkAssignResult":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"One entry per number in the request, in submission order.","items":{"$ref":"#/components/schemas/AdminTCRCampaignNumberLink"}}}},"AdminTCRCampaignSubmission":{"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","additionalProperties":false,"description":"One attempt to register a 10DLC campaign's details, as staff see it. Mirrors the customer-facing TCRCampaignSubmission property for property and adds external_registry_id, the registry's own reference for the attempt, which is not part of the customer API. A field added to TCRCampaignSubmission must be added here too.","required":["id","campaign_id","organization_id","workspace_id","attempt_number","rejection_reasons","status","use_case_category","sub_use_cases","name","use_case_description","opt_in_description","message_samples","opt_in_keywords","opt_out_keywords","help_keywords","embedded_link","embedded_phone","age_gated","direct_lending","subscriber_opt_in","subscriber_opt_out","subscriber_help"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/TCRCampaignSubmissionID"},"campaign_id":{"readOnly":true,"$ref":"#/components/schemas/TCRCampaignID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"attempt_number":{"type":"integer","minimum":1,"readOnly":true,"description":"The 1-based position of this attempt in the campaign's submission history."},"status":{"readOnly":true,"$ref":"#/components/schemas/TCRSubmissionStatus"},"external_registry_id":{"type":["string","null"],"readOnly":true,"description":"The identifier the campaign registry assigned this attempt, used to look the attempt up in the registry's own tooling. Each resubmit is registered as a new campaign, so this differs per attempt. Null until the attempt has been registered."},"rejection_reasons":{"type":"array","readOnly":true,"description":"Why this submission was rejected, one entry per problem. Empty for an attempt that has not been rejected.","items":{"$ref":"#/components/schemas/TCRSubmissionRejectionReason"}},"use_case_category":{"readOnly":true,"$ref":"#/components/schemas/TCRCampaignUseCase"},"sub_use_cases":{"type":"array","readOnly":true,"description":"Sub-use-cases, required when use_case is mixed or low_volume.","items":{"$ref":"#/components/schemas/TCRCampaignUseCase"}},"name":{"type":"string","minLength":1,"readOnly":true,"description":"A short name for the campaign."},"use_case_description":{"type":"string","minLength":1,"readOnly":true,"description":"What the campaign is used for."},"opt_in_description":{"type":"string","minLength":1,"readOnly":true,"description":"How recipients opt in to the campaign and what messages they will receive."},"help_response":{"type":"string","readOnly":true,"description":"The reply sent when a recipient texts a help keyword. Absent for an `m2m` campaign, which has no human recipient."},"message_samples":{"type":"array","readOnly":true,"description":"Example messages the campaign will send.","items":{"type":"string","minLength":1}},"opt_in_keywords":{"type":"array","readOnly":true,"description":"Keywords a recipient can text to opt in.","items":{"type":"string"}},"opt_in_confirmation_response":{"type":"string","readOnly":true,"description":"The confirmation reply sent when a recipient opts in."},"opt_out_keywords":{"type":"array","readOnly":true,"description":"Keywords a recipient can text to opt out.","items":{"type":"string"}},"opt_out_confirmation_response":{"type":"string","readOnly":true,"description":"The confirmation reply sent when a recipient opts out."},"help_keywords":{"type":"array","readOnly":true,"description":"Keywords a recipient can text to request help.","items":{"type":"string"}},"embedded_link":{"type":"boolean","readOnly":true,"description":"Whether campaign messages contain a link."},"embedded_phone":{"type":"boolean","readOnly":true,"description":"Whether campaign messages contain a phone number."},"age_gated":{"type":"boolean","readOnly":true,"description":"Whether the campaign contains age-gated content."},"direct_lending":{"type":"boolean","readOnly":true,"description":"Whether the campaign involves direct lending or a loan arrangement."},"subscriber_opt_in":{"type":"boolean","readOnly":true,"description":"Whether recipients can opt in to the campaign."},"subscriber_opt_out":{"type":"boolean","readOnly":true,"description":"Whether recipients can opt out of the campaign."},"subscriber_help":{"type":"boolean","readOnly":true,"description":"Whether recipients can request help from the campaign."},"attachments":{"type":"array","readOnly":true,"description":"The supporting documents submitted with this attempt.","items":{"$ref":"#/components/schemas/Attachment"}},"submitted_to_registry_at":{"type":"string","format":"date-time","readOnly":true,"description":"When this attempt was sent to the campaign registry, once it has been sent."},"resolved_at":{"type":"string","format":"date-time","readOnly":true,"description":"When this attempt was approved or rejected, when applicable."}}}]},"AdminTCRCampaignSubmissionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminTCRCampaignSubmission"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"TCRSubmissionResolve":{"type":"object","additionalProperties":false,"description":"Records the terminal outcome of a 10DLC brand or campaign submission attempt.","required":["outcome"],"properties":{"outcome":{"type":"string","minLength":1,"enum":["approved","rejected"],"description":"The outcome to record for the submission."},"external_registry_id":{"type":"string","maxLength":100,"description":"The identifier assigned to the submission by the campaign registry."},"rejection_reasons":{"type":"array","minItems":1,"maxItems":20,"description":"Why the submission was rejected, one entry per problem. Applies when the outcome is rejected.","items":{"$ref":"#/components/schemas/TCRSubmissionRejectionReason"}}}},"AdminTCRBrandSubmission":{"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","additionalProperties":false,"description":"One attempt to register a 10DLC brand's details, as staff see it. Mirrors the customer-facing TCRBrandSubmission property for property and adds external_registry_id, the registry's own reference for the attempt, which is not part of the customer API. A field added to TCRBrandSubmission must be added here too.","required":["id","brand_id","organization_id","workspace_id","attempt_number","rejection_reasons","status","business_entity_type","business_display_name","contact_phone","business_address_line1","business_address_city","business_address_postal_code","business_address_country","contact_email"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/TCRBrandSubmissionID"},"brand_id":{"readOnly":true,"$ref":"#/components/schemas/TCRBrandID"},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"attempt_number":{"type":"integer","minimum":1,"readOnly":true,"description":"The 1-based position of this attempt in the brand's submission history."},"status":{"readOnly":true,"$ref":"#/components/schemas/TCRSubmissionStatus"},"external_registry_id":{"type":["string","null"],"readOnly":true,"description":"The identifier the campaign registry assigned this attempt, used to look the attempt up in the registry's own tooling. Null until the attempt has been registered."},"rejection_reasons":{"type":"array","readOnly":true,"description":"Why this submission was rejected, one entry per problem. Empty for an attempt that has not been rejected.","items":{"$ref":"#/components/schemas/TCRSubmissionRejectionReason"}},"business_entity_type":{"readOnly":true,"$ref":"#/components/schemas/TCREntityType"},"business_display_name":{"type":"string","minLength":1,"readOnly":true,"description":"The name the business trades under, shown to recipients."},"legal_entity_name":{"type":"string","readOnly":true,"description":"Legal company name."},"contact_first_name":{"type":"string","readOnly":true,"description":"Contact first name."},"contact_last_name":{"type":"string","readOnly":true,"description":"Contact last name."},"business_registration_number":{"type":"string","readOnly":true,"description":"Government-issued tax ID (e.g. US EIN or Canadian BN9)."},"business_registration_country":{"readOnly":true,"description":"ISO 3166-1 alpha-2 country code that issued the tax ID.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"stock_symbol":{"type":"string","readOnly":true,"description":"Stock ticker symbol."},"stock_exchange":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TCRStockExchange"}]},"contact_phone":{"type":"string","minLength":1,"readOnly":true,"description":"Contact phone number in E.164 format, e.g. +12025550123."},"contact_mobile_phone":{"type":"string","minLength":1,"readOnly":true,"description":"The mobile number the campaign registry texts its identity check to, in E.164 format. Set only for sole-proprietor brands."},"business_address_line1":{"type":"string","minLength":1,"readOnly":true,"description":"Street address of the registered business."},"business_address_city":{"type":"string","minLength":1,"readOnly":true,"description":"City of the registered business."},"business_address_state":{"type":"string","readOnly":true,"description":"State, province, or region of the registered business."},"business_address_postal_code":{"type":"string","minLength":1,"readOnly":true,"description":"Postal or ZIP code of the registered business."},"business_address_country":{"readOnly":true,"description":"ISO 3166-1 alpha-2 country code of the registered business.","allOf":[{"$ref":"#/components/schemas/CountryCode"}]},"contact_email":{"type":"string","minLength":1,"readOnly":true,"description":"Contact email address for the brand."},"business_website":{"type":"string","readOnly":true,"description":"The brand's website URL."},"vertical":{"readOnly":true,"description":"The brand's industry. Absent on attempts made before the registry required it.","allOf":[{"$ref":"#/components/schemas/TCRVertical"}]},"alt_business_id":{"type":"string","readOnly":true,"description":"An alternative business identifier (e.g. DUNS, GIIN, or LEI number)."},"alt_business_id_type":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TCRAltBusinessIDType"}]},"business_verification_email":{"type":"string","readOnly":true,"description":"Business contact email used to verify a publicly-traded company."},"submitted_to_registry_at":{"type":"string","format":"date-time","readOnly":true,"description":"When this attempt was sent to the campaign registry, once it has been sent."},"resolved_at":{"type":"string","format":"date-time","readOnly":true,"description":"When this attempt was approved or rejected, when applicable."}}}]},"AdminTCRBrandSubmissionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminTCRBrandSubmission"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminSMSTopOrganization":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One organization in the top-senders ranking, with its SMS volume and delivery rate over the requested window.\n","required":["organization_id","name","volume","delivered"],"properties":{"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"The organization these figures are for."},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the organization.","example":"Acme Retail Group"},"volume":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of SMS messages the organization had accepted for sending in the window.","example":1240100},"delivered":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of the organization's SMS messages the carrier confirmed as delivered in the window.","example":1231800},"delivery_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of accepted messages that were delivered, between 0 and 1, or null when no messages were accepted in the window.","example":0.9933}}},"AdminSMSTopOrganizationList":{"type":"object","additionalProperties":false,"description":"The organizations sending the most SMS in a single region over the requested window, ranked by volume. Combine the regions to see a global ranking.\n","required":["region","period","organizations"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these figures are drawn from."},"period":{"$ref":"#/components/schemas/SMSStatsSeriesPeriod","description":"The window and bucket grain the ranking covers, echoed from the request."},"organizations":{"type":"array","readOnly":true,"description":"Organizations ranked by SMS volume, highest first.","items":{"$ref":"#/components/schemas/AdminSMSTopOrganization"}}}},"AdminSMSMessage":{"type":"object","additionalProperties":false,"description":"Non-PII summary of a single SMS message, for admin triage. Carries only sender and structural metadata; the recipient number and the message body are deliberately omitted.\n","required":["id","region","workspace_id","organization_id","direction","status","from","segments","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SMSMessageID","description":"Message ID."},"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"Region this message was sent in."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that sent the message."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization that owns the sending workspace."},"direction":{"type":"string","minLength":1,"readOnly":true,"enum":["outbound","inbound"],"description":"Whether the message was sent from a Bird sender (`outbound`) or received from a subscriber (`inbound`)."},"status":{"allOf":[{"$ref":"#/components/schemas/SMSMessageStatus"}],"readOnly":true},"category":{"oneOf":[{"$ref":"#/components/schemas/SMSMessageCategory"},{"type":"null"}],"description":"Content classification supplied on the send. Null for inbound messages."},"from":{"type":"string","minLength":1,"readOnly":true,"description":"Sender the message was sent from — an E.164 number, an alphanumeric sender ID, or a short code.","example":"+15557654321"},"sender_type":{"oneOf":[{"$ref":"#/components/schemas/SMSSenderType"},{"type":"null"}],"readOnly":true,"description":"The kind of sender identity used. Null for inbound messages."},"segments":{"$ref":"#/components/schemas/SMSSegments","description":"Segment breakdown for the body."},"cost":{"$ref":"#/components/schemas/MessageCost","description":"What the message cost, split into Bird's charge and any third-party fees passed through. Null until the message has been priced."},"error_code":{"oneOf":[{"$ref":"#/components/schemas/SMSErrorCode"},{"type":"null"}],"readOnly":true,"description":"Failure reason on a message that failed, was rejected, was not delivered, or expired. Null otherwise."},"carrier_error_code":{"type":["string","null"],"readOnly":true,"description":"Raw provider-supplied code for the failure, finer-grained than `error_code`. Null when the provider reported none, including any failure decided before one was reached."},"carrier":{"type":["string","null"],"readOnly":true,"description":"Carrier that handled the message, when known. Populated once a delivery receipt identifies it.","example":"Verizon"},"mcc_mnc":{"type":["string","null"],"readOnly":true,"description":"Mobile country code and mobile network code of the carrier, when known.","example":"311480"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the message was accepted (outbound) or received (inbound)."},"sent_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the message was handed to the carrier. Null until then."},"delivered_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When delivery was confirmed. Null until then."}}},"AdminSMSMessageList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of SMS messages for this region, newest first.","items":{"$ref":"#/components/schemas/AdminSMSMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminSMSContentEnvelope":{"type":"object","additionalProperties":false,"description":"Identifying details of the SMS message whose content was returned.","properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SMSMessageID","description":"Message ID."},"from":{"type":"string","minLength":1,"readOnly":true,"description":"Sender the message was sent from — an E.164 number, an alphanumeric sender ID, or a short code.","example":"+15557654321"},"direction":{"type":"string","minLength":1,"readOnly":true,"enum":["outbound","inbound"],"description":"Whether the message was sent from a Bird sender (`outbound`) or received from a subscriber (`inbound`)."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the message was accepted (outbound) or received (inbound)."}},"required":["id","from","direction","created_at"]},"AdminSMSContentReveal":{"type":"object","additionalProperties":false,"description":"The recipient and body of an SMS message, returned to authorized staff for review.","properties":{"message":{"$ref":"#/components/schemas/AdminSMSContentEnvelope"},"to_address":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Recipient phone number in E.164 format.","example":"+15551234567"},"message_body":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"The full text of the message. `**REDACTED**` for an authentication-category message (one carrying a one-time code)."}},"required":["message","to_address","message_body"]},"AdminSMSSuppression":{"type":"object","additionalProperties":false,"description":"One suppression period for a subscriber and one sender: when it started, what started it, and what ended it. A subscriber who opts out, opts back in, and opts out again has three such periods on record rather than one current state, so a past date can be answered as well as today.\n","required":["id","cursor","region","workspace_id","destination","originator","reason","origin","applies_to","blocking","effective_at","created_at","last_asserted_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SMSSuppressionID","description":"Suppression ID."},"cursor":{"type":"string","minLength":1,"description":"Opaque boundary for this row's (effective_at, id) ordering; reusable across regions as starting_after or ending_before."},"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"Region the suppression was recorded in."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace whose messages this suppression stops."},"destination":{"type":"string","minLength":1,"readOnly":true,"description":"The subscriber, in E.164 format. Always the number that was looked up."},"originator":{"type":"string","minLength":1,"readOnly":true,"description":"The sender the subscriber opted out of. A suppression covers this sender only, so the workspace's other senders still reach this subscriber.\n"},"reason":{"allOf":[{"$ref":"#/components/schemas/SMSSuppressionReason"}],"readOnly":true},"origin":{"allOf":[{"$ref":"#/components/schemas/SMSSuppressionOrigin"}],"readOnly":true},"applies_to":{"allOf":[{"$ref":"#/components/schemas/SMSSuppressionCoverage"}],"readOnly":true},"blocking":{"type":"boolean","readOnly":true,"description":"Whether this suppression is stopping messages right now. False once it has ended, which is also when `ended_at` is set.\n"},"source_sms_id":{"oneOf":[{"$ref":"#/components/schemas/SMSMessageID"},{"type":"null"}],"readOnly":true,"description":"The inbound message the subscriber opted out with, or the outbound message whose delivery report reported the opt-out. Null when neither applies, such as an opt-out recorded by staff.\n"},"effective_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the subscriber opted out, as reported by whoever reported it. This is what orders one subscriber's history, and it can be earlier than `created_at` when a message reached Bird late.\n"},"ended_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the suppression stopped applying. Null while it is still stopping messages."},"ended_reason":{"oneOf":[{"$ref":"#/components/schemas/SMSSuppressionEndReason"},{"type":"null"}],"readOnly":true,"description":"What ended the suppression. Null while it is still stopping messages."},"ended_effective_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the subscriber opted back in, as reported. Null while the suppression is still stopping messages.\n"},"source_end_sms_id":{"oneOf":[{"$ref":"#/components/schemas/SMSMessageID"},{"type":"null"}],"readOnly":true,"description":"The inbound message the subscriber opted back in with, when there was one."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When Bird recorded the suppression."},"last_asserted_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When Bird last recorded the subscriber opting out of this sender. Later than `created_at` when they opted out again while already suppressed, which repeats no record but does earn a fresh confirmation reply.\n"},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the record last changed."}}},"AdminSMSSuppressionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The subscriber's suppression periods for this region, most recent opt-out first.\n","items":{"$ref":"#/components/schemas/AdminSMSSuppression"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminWhatsAppGroup":{"allOf":[{"$ref":"#/components/schemas/WhatsAppGroup"},{"type":"object","required":["organization_id","workspace_id"],"properties":{"organization_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/OrganizationID"},{"type":"null"}],"description":"Organization that owns this group. Read from the group's number, which is where the region holds it, so it is null for a number onboarded before its workspace had a business account.\n"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that owns this group."}}}]},"AdminWhatsAppGroupList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminWhatsAppGroup"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminWhatsAppSuppression":{"type":"object","additionalProperties":false,"description":"One period during which a WhatsApp address is suppressed: when it started, what started it, and what ended it. An address suppressed, ended and suppressed again has two such periods on record rather than one current state, so a past date can be answered as well as today.\n","required":["id","region","workspace_id","address","reason","origin","applies_to","blocking","effective_at","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppSuppressionID","description":"Suppression ID."},"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"Region the suppression was recorded in."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace whose messages this suppression stops."},"address":{"type":"string","minLength":1,"readOnly":true,"description":"The suppressed WhatsApp address, canonical E.164 with a leading \"+\" for a phone number. Always the address that was looked up.\n","example":"+5511977670804"},"waba_id":{"type":["string","null"],"readOnly":true,"description":"The WhatsApp Business Account the suppression covers. Null for a workspace-wide suppression, which stops messages from every business account the workspace owns.\n"},"reason":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["manual"],"description":"Why the address is suppressed. `manual` means it was added directly rather than created automatically from a delivery outcome. This list grows over time.\n"},"origin":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["api_key","user"],"description":"How the suppression came to exist: `api_key` (added through the API with an API key) or `user` (added by a user in the dashboard). This list grows over time.\n"},"applies_to":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["all"],"description":"Blocking policy. `all` blocks every message category. This list grows over time; treat an unknown value as blocking.\n"},"blocking":{"type":"boolean","readOnly":true,"description":"Whether this suppression is stopping messages right now. False once it has ended, which is also when `ended_at` is set.\n"},"source_whatsapp_id":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppMessageID"},{"type":"null"}],"readOnly":true,"description":"The message the suppression was created from, when it was created automatically. Null for an address added manually.\n"},"effective_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the suppression took effect, as reported by whoever reported it. This is what orders one address's history, and it can be earlier than `created_at` when the report arrived late.\n"},"ended_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the suppression stopped applying. Null while it is still stopping messages."},"ended_reason":{"type":["string","null"],"x-extensible-enum":["api_key","user"],"readOnly":true,"description":"What ended the suppression: `api_key` (deleted through the API with an API key) or `user` (deleted by a user in the dashboard). Null while it is still stopping messages. This list grows over time.\n"},"ended_effective_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the suppression was lifted, as reported. Null while it is still stopping messages.\n"},"source_end_whatsapp_id":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppMessageID"},{"type":"null"}],"readOnly":true,"description":"The message the suppression was ended from, when there was one."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the suppression was recorded."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the record last changed."}}},"AdminWhatsAppSuppressionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"The address's suppression periods for this region, most recent suppression first.\n","items":{"$ref":"#/components/schemas/AdminWhatsAppSuppression"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminWhatsAppKeywordOperation":{"type":"string","minLength":1,"enum":["opt_in","opt_out"],"description":"What Bird does when an inbound message matches this row.","example":"opt_out"},"AdminWhatsAppKeywordRule":{"type":"object","additionalProperties":false,"readOnly":true,"required":["id","operation","keywords","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/WhatsAppKeywordRuleID"},"country":{"type":["string","null"],"minLength":2,"maxLength":2,"description":"The country this row applies in, as an ISO 3166-1 alpha-2 code, or null for the row that applies everywhere."},"operation":{"$ref":"#/components/schemas/AdminWhatsAppKeywordOperation"},"keywords":{"type":"array","description":"The keywords every workspace inherits for this country and operation.","items":{"type":"string","minLength":1}},"reply":{"type":["string","null"],"description":"The reply sent when a workspace has not overridden it, or null when no reply is sent."},"created_at":{"type":"string","format":"date-time","minLength":1},"updated_at":{"type":"string","format":"date-time","minLength":1}}},"AdminWhatsAppKeywordRuleList":{"type":"object","additionalProperties":false,"required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminWhatsAppKeywordRule"},"description":"Matching catalogue rows, ordered by country, then operation."},"total":{"type":"integer","format":"int64","description":"Total number of catalogue rows matching the filters, ignoring pagination."}}},"AdminWhatsAppKeywordRuleUpsert":{"type":"object","additionalProperties":false,"required":["operation","keywords"],"properties":{"country":{"type":"string","minLength":2,"maxLength":2,"description":"The country this row applies in, as an ISO 3166-1 alpha-2 code. Omit for the row that applies everywhere."},"operation":{"$ref":"#/components/schemas/AdminWhatsAppKeywordOperation"},"keywords":{"type":"array","minItems":1,"description":"The keywords every workspace inherits for this country and operation. A keyword already bound to the other operation is rejected, because one keyword means one thing everywhere.\n","items":{"type":"string","minLength":1}},"reply":{"type":"string","minLength":1,"description":"The reply sent when a workspace has not overridden it. Omit to send nothing."}}},"AdminWhatsAppTopOrganizationList":{"type":"object","additionalProperties":false,"description":"The organizations sending the most WhatsApp in a single region over the requested window, ranked by volume. Combine the regions to see a global ranking.\n","required":["region","period","organizations"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these figures are drawn from."},"period":{"$ref":"#/components/schemas/WhatsAppStatsSeriesPeriod","description":"The window and bucket grain the ranking covers, echoed from the request."},"organizations":{"type":"array","readOnly":true,"description":"Organizations ranked by WhatsApp volume, highest first.","items":{"$ref":"#/components/schemas/AdminTopOrganization"}}}},"AdminWhatsAppMessageBusiness":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The business identity that sent the message. `phone_number` is always present; `phone_number_id` is included only for account-owned numbers.\n","properties":{"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"E.164 phone number of the WhatsApp business account that sent the message.","example":"+15557654321"},"phone_number_id":{"type":"string","minLength":1,"readOnly":true,"description":"The WhatsApp phone number identifier. Present only for account-owned numbers.","example":"397968058767338"}}},"AdminWhatsAppMessageTemplate":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The template a message was sent from — its slug, language, and category. The filled-in placeholder values are not part of the log row; they are returned separately by the content-reveal endpoint.\n","required":["slug","language","category"],"properties":{"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"readOnly":true,"description":"The template's stable handle (for example `bird_otp`).","example":"bird_otp"},"category":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateCategory"}],"readOnly":true,"description":"The category this message was priced at, as it stood when the message was sent. For a workspace-authored template this is the category Meta applies to the language the send resolved to, which can differ from the category declared on the template. A built-in `bird_` template is priced at the single category the built-in declares, the same in every language.\n"},"language":{"type":"string","minLength":1,"readOnly":true,"description":"The language code of the template variant that was sent (for example `en`).","example":"en"}}},"AdminWhatsAppMessage":{"type":"object","additionalProperties":false,"description":"Non-PII summary of a single WhatsApp message, for admin triage. Carries only sender and structural metadata; the recipient and the template's filled-in values are deliberately omitted.\n","required":["id","region","workspace_id","organization_id","direction","business","status","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppMessageID","description":"Message ID."},"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"Region this message was sent in."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that sent the message."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization that owns the sending workspace."},"direction":{"type":"string","minLength":1,"readOnly":true,"enum":["outbound","inbound"],"description":"Whether the message was sent by the business (`outbound`) or received from the contact (`inbound`)."},"business":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminWhatsAppMessageBusiness"}]},"template":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminWhatsAppMessageTemplate"}],"description":"The template the message was sent from. Absent for a free-form text message."},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppMessageStatus"}]},"controller":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppAgentController"}],"description":"Who was handling the conversation when the message happened. Absent on a number with no WhatsApp Business Agent switched on, which is most traffic.\n"},"error_code":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppErrorCode"},{"type":"null"}],"readOnly":true,"description":"Failure reason on a message that failed to reach the recipient. Null when there is no failure."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the message was accepted (outbound) or received (inbound)."},"sent_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the message was handed to the WhatsApp network. Null until then."},"delivered_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When delivery was confirmed. Null until then."},"read_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the message was read by the recipient. Null until then."}}},"AdminWhatsAppMessageList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of WhatsApp messages for this region, newest first.","items":{"$ref":"#/components/schemas/AdminWhatsAppMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminWhatsAppMessageContact":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Contact on the other end of the message. Fields are omitted when not available; at least one is always present.","properties":{"revealed_phone_number":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Contact's phone number in E.164 format, when known.","example":"+15551234567"},"revealed_bsuid":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Business-scoped user ID (Meta's WhatsApp identifier for this contact within the business account), when available.","example":"NL.xxxx"}}},"AdminWhatsAppRevealedText":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The text of a free-form WhatsApp message, returned by the content-reveal endpoint for staff review.\n","required":["revealed_body"],"properties":{"revealed_body":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"The message text as it was sent.","example":"Does it come in another color?"}}},"AdminWhatsAppRevealedTemplate":{"type":"object","additionalProperties":false,"readOnly":true,"description":"The template a message was sent from together with the filled-in placeholder values, returned by the content-reveal endpoint. Components are empty for an authentication template, whose content is never returned.\n","required":["slug","language","category","revealed_components"],"properties":{"slug":{"allOf":[{"$ref":"#/components/schemas/TemplateSlug"}],"readOnly":true,"description":"The template's stable handle (for example `bird_otp`).","example":"bird_otp"},"category":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateCategory"}],"readOnly":true,"description":"The category this message was priced at, as it stood when the message was sent. For a workspace-authored template this is the category Meta applies to the language the send resolved to, which can differ from the category declared on the template. A built-in `bird_` template is priced at the single category the built-in declares, the same in every language.\n"},"language":{"type":"string","minLength":1,"readOnly":true,"description":"The language code of the template variant that was sent (for example `en`).","example":"en"},"revealed_components":{"type":"array","readOnly":true,"x-sensitive":true,"description":"The values that filled the template's placeholders. Empty for an authentication template, whose content is never returned.\n","items":{"$ref":"#/components/schemas/WhatsAppMessageTemplateComponent"}}}},"AdminWhatsAppContentReveal":{"type":"object","additionalProperties":false,"description":"The recipient and content of a WhatsApp message, returned to authorized staff for review: the filled-in template for a template send, the message text for a free-form one. The message's non-sensitive envelope (status, timestamps, sender) is already on the log row this reveal was opened from.\n","properties":{"contact":{"$ref":"#/components/schemas/AdminWhatsAppMessageContact"},"text":{"$ref":"#/components/schemas/AdminWhatsAppRevealedText"},"template":{"$ref":"#/components/schemas/AdminWhatsAppRevealedTemplate"}},"required":["contact"]},"AdminWhatsAppNumberSetupStatus":{"type":"string","minLength":1,"enum":["pending","active","preparing","awaiting_signup","failed"],"description":"Bird's own setup lifecycle for a WhatsApp number, distinct from Meta's\noperational state.\n\n- `preparing`: Bird is verifying a number it pre-verifies on the customer's\n  behalf. No WABA exists yet.\n- `awaiting_signup`: pre-verification finished and the customer must\n  complete signup. No WABA exists yet.\n- `pending`: a WABA exists but WhatsApp has not completed registration.\n- `active`: registration completed and the number can send and receive.\n- `failed`: connection was refused permanently.\n","example":"active"},"AdminWhatsAppNumber":{"type":"object","additionalProperties":false,"required":["id","organization_id","workspace_id","waba","phone_number","status","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppNumberID","description":"Identifier of this number."},"organization_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/OrganizationID"},{"type":"null"}],"description":"Organization that owns this number. Null for a number onboarded before its workspace had a business account, whose owner this region cannot yet resolve.\n"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that owns this number."},"waba":{"readOnly":true,"type":["string","null"],"description":"Meta's identifier for the business account this number is attached to. Null before the number is attached to one."},"phone_number":{"readOnly":true,"type":["string","null"],"description":"The number in E.164 format. Null until Meta reports it."},"name":{"readOnly":true,"type":"string","description":"Display name for this number."},"status":{"readOnly":true,"$ref":"#/components/schemas/AdminWhatsAppNumberSetupStatus","description":"Bird's setup lifecycle state for this number, distinct from Meta's own operational state."},"meta_status":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WhatsAppNumberStatus"},{"type":"null"}],"description":"Meta's operational state for this number, as of the last metadata sync. Null when never synced."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminWhatsAppNumberList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminWhatsAppNumber"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminWhatsAppNumberMetaError":{"type":"object","additionalProperties":false,"readOnly":true,"required":["description"],"description":"The last error recorded for a number's connection failure.","properties":{"code":{"readOnly":true,"type":["string","null"],"description":"WhatsApp's most specific code for the failure: its error subcode when it sent one, otherwise its top-level code. Opaque, treat it as a string.\n","example":"136024"},"description":{"readOnly":true,"type":"string","minLength":1,"description":"WhatsApp's own explanation when WhatsApp answered; our own explanation when the number was refused before WhatsApp was asked, and a generic sentence when WhatsApp refused without giving a reason."}}},"AdminWhatsAppPreVerificationAttempt":{"type":"object","additionalProperties":false,"readOnly":true,"required":["method","requested_at"],"description":"One attempt to pre-verify a number with Meta, with whatever Meta answered.","properties":{"method":{"type":"string","minLength":1,"readOnly":true,"description":"How the verification code was requested, as Meta names it (for example `sms`)."},"requested_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the attempt was made."},"meta_error_code":{"type":["integer","null"],"readOnly":true,"description":"Meta's top-level error code for the attempt. Null when the attempt did not fail."},"meta_error_subcode":{"type":["integer","null"],"readOnly":true,"description":"Meta's error subcode, which is the specific cause: it answers most refusals with the catch-all top-level code 100. Null when the attempt did not fail, or when Meta sent no subcode.\n"},"meta_error_description":{"type":["string","null"],"readOnly":true,"description":"Meta's own explanation of the failure, passed through unmodified. Null when the attempt did not fail."}}},"AdminWhatsAppNumberDetail":{"allOf":[{"$ref":"#/components/schemas/AdminWhatsAppNumber"},{"type":"object","additionalProperties":false,"properties":{"number_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/AllocatedNumberID"},{"type":"null"}],"description":"The Bird numbers-inventory holding this number was pre-verified from. Null for the own-number path, which never crosses that boundary.\n"},"meta_phone_number_id":{"readOnly":true,"type":["string","null"],"description":"Meta's own identifier for this phone number. Null until Meta reports it."},"verified_name":{"readOnly":true,"type":["string","null"],"description":"WhatsApp's display name for the number. Null until Meta reports one."},"data_localization_region":{"readOnly":true,"type":["string","null"],"description":"The region Meta pins this number's message storage to. Null when unset."},"status_reason":{"readOnly":true,"type":["string","null"],"description":"Why the number holds its current setup status. Null when the status needs no qualifier."},"setup_step":{"readOnly":true,"type":["string","null"],"x-extensible-enum":["requested","registering","registered"],"description":"How far the onboarding workflow got. Open-ended: the ladder gains steps, and a client must tolerate a value it does not know.\n"},"setup_error":{"readOnly":true,"type":["string","null"],"description":"The reason onboarding stopped, as recorded by the workflow. Null while nothing has failed."},"register_attempts":{"readOnly":true,"type":"integer","description":"How many times Bird has called Meta's register endpoint for this number."},"quality_rating":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WhatsAppNumberQualityRating"},{"type":"null"}],"description":"Meta's quality rating as of the last metadata sync. Null when never synced."},"messaging_limit":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WhatsAppNumberMessagingLimit"},{"type":"null"}],"description":"Meta's messaging tier as of the last metadata sync. Null when never synced."},"throughput_level":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WhatsAppNumberThroughputLevel"},{"type":"null"}],"description":"Meta's throughput level as of the last metadata sync. Null when never synced."},"is_official_business_account":{"readOnly":true,"type":["boolean","null"],"description":"Whether Meta grants this number Official Business Account status, as of the last metadata sync. Null when never synced. Status is per phone number, so two numbers under one WABA can differ."},"meta_error":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/AdminWhatsAppNumberMetaError"},{"type":"null"}],"description":"The last error recorded for this number. Null until an attempt records a failure."},"meta_synced_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When the metadata sweep last read this number from Meta. Null when never synced."},"meta_sync_failures":{"readOnly":true,"type":["integer","null"],"description":"Consecutive failures of the metadata sweep on this row. A non-zero value means the Meta fields above are staler than `meta_synced_at` suggests.\n"},"meta_preverified_id":{"readOnly":true,"type":["string","null"],"description":"The pre-verified-pool entry this number was claimed from. Meta destroys the object when signup claims it and nothing clears the column, so on a connected number this id names nothing.\n"},"preverify_step":{"readOnly":true,"type":["string","null"],"x-extensible-enum":["requested","verified"],"description":"How far pre-verification got. Open-ended, on the same terms as `setup_step`."},"has_registration_pin":{"readOnly":true,"type":"boolean","description":"Whether onboarding stored a registration PIN for this number. The PIN itself is never returned.\n"},"has_business_token":{"readOnly":true,"type":"boolean","description":"Whether this number holds its own business token, which is what an Embedded Signup number sends on. The token itself is never returned.\n"},"repairable":{"readOnly":true,"type":"boolean","description":"Whether `repairAdminWhatsAppNumber` has a flow to re-drive for this number right now. False for a number that did not fail, one already pre-verified, one Bird did not pre-verify, and one that is deleted. It cannot be derived from the other fields here, because the pre-verification completion it reads is not among them. Absent from the number list, which carries no repair operation to gate. Does not account for the minute WhatsApp holds between two verification codes, which is only known when the retry is asked for.\n"},"pre_verification_attempts":{"readOnly":true,"type":"array","description":"Pre-verification attempts for this number, newest first, capped at the 50 most recent. A number past that cap is a pathology the attempt count already reveals.\n","items":{"$ref":"#/components/schemas/AdminWhatsAppPreVerificationAttempt"}},"deleted_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When the number was soft-deleted. Null for a live number. Unlike the list, this endpoint returns a deleted number: an operator investigating a teardown is asking about a row that is already gone.\n"}}}]},"AdminWhatsAppBusinessAccount":{"type":"object","additionalProperties":false,"required":["id","organization_id","workspace_id","waba","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppBusinessAccountID","description":"Identifier of this business account."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"Organization that owns this business account."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that owns this business account."},"waba":{"readOnly":true,"type":"string","minLength":1,"description":"Meta's own identifier for this WhatsApp Business Account."},"name":{"readOnly":true,"type":["string","null"],"description":"The account's name, as WhatsApp reports it. Null until Meta has reported it."},"meta_status":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountStatus"},{"type":"null"}],"description":"Meta's operational state for this business account, as of the last metadata sync. Null when never synced."},"account_review_status":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountReviewStatus"},{"type":"null"}],"description":"How far Meta's own review of this business account had got, as of the last metadata sync. Null when never synced."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminWhatsAppBusinessAccountList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminWhatsAppBusinessAccount"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminWhatsAppBusinessAccountDetail":{"allOf":[{"$ref":"#/components/schemas/AdminWhatsAppBusinessAccount"},{"type":"object","additionalProperties":false,"properties":{"waba_currency":{"readOnly":true,"type":["string","null"],"description":"The currency Meta bills this business account in, as a three-letter code. Fixed by Meta when the account is created and never changes, so a mismatch with the workspace's own currency is permanent.\n"},"credit_allocation_config_id":{"readOnly":true,"type":["string","null"],"description":"Meta's identifier for the credit line attached to this account. Null until credit is attached."},"credit_attach_attempts":{"readOnly":true,"type":"integer","description":"How many times Bird has tried to attach its credit line to this account."},"credit_attach_attempted_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When the last credit-attach attempt ran. Null when none has."},"portfolio":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WhatsAppBusinessPortfolio"},{"type":"null"}],"description":"The Meta business portfolio this account sits under, as of the last metadata sync. Null when never synced."},"business_verification_status":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppBusinessVerificationStatus"},{"type":"null"}],"readOnly":true,"description":"How far Meta's verification of the owning business had got, as of the last metadata sync. Null when never synced."},"health_status":{"readOnly":true,"type":["string","null"],"description":"Meta's own health verdict for this account, as of the last metadata sync. Null when never synced."},"marketing_messages_onboarding_status":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountMarketingMessagesStatus"},{"type":"null"}],"readOnly":true,"description":"Whether this account can use WhatsApp's Marketing Messages API, as of the last metadata sync. Null when never synced. Distinct from the owning portfolio's `marketing_messages_onboarding_status` (`portfolio.marketing_messages_onboarding_status`), which Meta gives the same field name but a different vocabulary: this one is the account's own eligibility, that one is the portfolio's Terms-of-Service progress."},"ban":{"oneOf":[{"$ref":"#/components/schemas/WhatsAppBusinessAccountBan"},{"type":"null"}],"readOnly":true,"description":"WhatsApp's ban on this account, as WhatsApp announced it on the webhook. Null when Bird was told of no ban, including a ban announced before Bird began recording them, or one whose notification never arrived, so `meta_status` is what says whether the account can send."},"meta_synced_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When the metadata sweep last read this account from Meta. Null when never synced."},"deleted_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When the account was soft-deleted. Null for a live account. Unlike the list, this endpoint returns a deleted account too.\n"}}}]},"AdminWhatsAppTemplate":{"type":"object","additionalProperties":false,"required":["id","organization_id","workspace_id","waba","slug","name","category","status","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppTemplateID","description":"Identifier of this template."},"organization_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/OrganizationID"},{"type":"null"}],"description":"Organization that owns this template. Null for a template authored before its workspace had a business account, whose owner this region cannot yet resolve.\n"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"Workspace that owns this template."},"waba":{"readOnly":true,"type":"string","minLength":1,"description":"Meta's identifier for the business account this template's languages live on."},"slug":{"readOnly":true,"type":"string","minLength":1,"description":"The template's immutable handle."},"name":{"readOnly":true,"type":"string","minLength":1,"description":"Display name for the template."},"description":{"readOnly":true,"type":["string","null"],"description":"What the template is for, as its author described it. Null when unset."},"category":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppTemplateCategory","description":"The category declared for the template."},"status":{"readOnly":true,"$ref":"#/components/schemas/TemplateStatus","description":"The template's lifecycle, aggregated over its languages."},"default_language":{"readOnly":true,"$ref":"#/components/schemas/LanguageTag","description":"The language the template falls back to when a send does not name one."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminWhatsAppTemplateList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminWhatsAppTemplate"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminWhatsAppTemplateParameterFormat":{"type":"string","minLength":1,"x-extensible-enum":["positional","named"],"description":"Whether a template's variables are addressed by name or by position. Fixed at creation. Open: the vocabulary is Meta's, so a value Bird has not seen is a new Meta format rather than a bug.\n"},"AdminWhatsAppTemplateDetail":{"allOf":[{"$ref":"#/components/schemas/AdminWhatsAppTemplate"},{"type":"object","additionalProperties":false,"properties":{"parameter_format":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminWhatsAppTemplateParameterFormat"}],"description":"Whether this template's variables are addressed by name or by position. Fixed at creation, and it decides the shape of every send that names the template.\n"},"on_missing_language":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/TemplateOnMissingLanguage"}],"description":"What a send does when the language it asks for is not sendable."},"language_source_required":{"readOnly":true,"type":"boolean","description":"Whether a send must name a language explicitly rather than falling back to the default."},"draft_version_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WhatsAppTemplateVersionID"},{"type":"null"}],"description":"The version currently open for editing. Null when there is no open draft."},"live_version_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WhatsAppTemplateVersionID"},{"type":"null"}],"description":"The version sends resolve against. Null until a version has been approved."},"deleted_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When the template was soft-deleted. Null for a live template. Unlike the list, this endpoint returns a deleted template too.\n"}}}]},"AdminWhatsAppTemplateVersionState":{"type":"string","minLength":1,"enum":["draft","live","superseded"],"description":"A version's role for its template right now, derived from the template's `draft_version_id` and `live_version_id`: there is no `state` column, and a reader who looks for one will not find it. Closed: the three values are assigned by Bird, not received from Meta.\n"},"AdminWhatsAppTemplateVersionSummary":{"type":"object","additionalProperties":false,"required":["id","template_id","revision","language_count","statuses","state","created_at","updated_at"],"description":"One version of a template as a row in the admin versions table.","properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppTemplateVersionID","description":"Stable Bird identifier for the version."},"template_id":{"readOnly":true,"$ref":"#/components/schemas/WhatsAppTemplateID","description":"The template this version belongs to."},"version_number":{"readOnly":true,"type":["integer","null"],"minimum":1,"description":"The version's sequence number, assigned on submission. Null on a draft, which has no place in the sequence yet."},"revision":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateRevision"}],"description":"The version's write counter."},"submitted_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When the version was submitted to Meta. Null on a draft."},"language_count":{"readOnly":true,"type":"integer","description":"How many languages this version holds."},"statuses":{"readOnly":true,"type":"object","propertyNames":{"$ref":"#/components/schemas/WhatsAppTemplateLanguageStatus"},"additionalProperties":{"type":"integer"},"description":"How many of this version's languages sit in each status, so the row reads without fetching the languages.\n"},"state":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminWhatsAppTemplateVersionState"}]},"created_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time","description":"When this version was created. Never null, unlike the customer schema's: the admin surface excludes the managed catalogue, so every version it returns is an authored row.\n"},"updated_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time","description":"When this version was last modified. Never null, on the same terms as `created_at`.\n"}}},"AdminWhatsAppTemplateVersionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminWhatsAppTemplateVersionSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminWhatsAppTemplateContentOrigin":{"type":"string","minLength":1,"enum":["authored","ingested"],"description":"Whether a language's content was authored in the workspace or came in from Bird's managed catalogue. Closed: this is Bird's own record of provenance, not a value Meta reports.\n"},"AdminWhatsAppTemplateLanguageOperation":{"type":"string","minLength":1,"enum":["create","edit","delete"],"description":"The kind of change a submission makes to a language. Closed: this is Bird's own classification of the request it sent, not a value Meta reports.\n"},"AdminWhatsAppTemplateMetaLock":{"type":"object","additionalProperties":false,"required":["locked_at"],"description":"Meta's hold on one template language, which blocks further edits to it.","properties":{"locked_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time","description":"When Meta locked the language."},"title":{"readOnly":true,"type":["string","null"],"description":"Meta's short label for the lock. Null when Meta sent none."},"description":{"readOnly":true,"type":["string","null"],"description":"Meta's explanation of the lock, passed through unmodified. Null when Meta sent none."}}},"AdminWhatsAppTemplateLanguage":{"allOf":[{"$ref":"#/components/schemas/WhatsAppTemplateLanguage"},{"type":"object","additionalProperties":false,"properties":{"meta_name":{"readOnly":true,"type":["string","null"],"description":"The name Meta holds for this language, which diverges from the template's own name for a per-language submission. Null until Meta has accepted a submission.\n"},"meta_template_id":{"readOnly":true,"type":["string","null"],"description":"Meta's identifier for this language. It is what the status webhook matches on, so a null here means no Meta callback can ever land on this row.\n"},"meta_updated_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When Meta last reported a change to this language. Null when it never has."},"content_origin":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminWhatsAppTemplateContentOrigin"}],"description":"Whether this content was authored in the workspace or came in from Bird's managed catalogue."},"pending_operation":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/AdminWhatsAppTemplateLanguageOperation"},{"type":"null"}],"description":"The operation currently in flight on this language. Null when none is."},"submitted_operation":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/AdminWhatsAppTemplateLanguageOperation"},{"type":"null"}],"description":"What the last submission was trying to do. Null when nothing has been submitted."},"submitted_from_status":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WhatsAppTemplateLanguageStatus"},{"type":"null"}],"description":"The status this language held when the last submission left. Together with `submitted_operation` it is the only record of a submission's intent, which is what makes an interrupted submission legible.\n"},"meta_lock":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/AdminWhatsAppTemplateMetaLock"},{"type":"null"}],"description":"Meta's own hold on this language, which blocks edits. Null when unlocked."},"rendered_components":{"readOnly":true,"type":["array","null"],"items":{"$ref":"#/components/schemas/WhatsAppTemplateComponent"},"description":"What Bird actually sent Meta, as against `components`, which is the stored draft. A disagreement between the two is the shape of most submission bugs. Null when nothing has been submitted.\n"},"failure_reason":{"readOnly":true,"type":["string","null"],"description":"Bird's raw record of why the submission did not complete. Unlike `error`, which surfaces this only while `status` still reads `submit_failed` or `outcome_unknown`, this column is never cleared, so it stays populated after the status has moved on.\n"},"meta_error_code":{"readOnly":true,"type":["string","null"],"description":"WhatsApp's most specific code for the failure, opaque and treated as a plain string. The same never-cleared relationship to `error.meta_error_code` applies here as to `failure_reason`.\n"}}}]},"AdminWhatsAppTemplateVersionDetail":{"allOf":[{"$ref":"#/components/schemas/AdminWhatsAppTemplateVersionSummary"},{"type":"object","additionalProperties":false,"required":["languages"],"properties":{"languages":{"readOnly":true,"type":"object","propertyNames":{"$ref":"#/components/schemas/LanguageTag"},"additionalProperties":{"$ref":"#/components/schemas/AdminWhatsAppTemplateLanguage"},"description":"This version's languages, keyed by BCP-47 tag, each with its full content and everything Meta holds about it."}}}]},"AdminAMBBusiness":{"type":"object","additionalProperties":false,"description":"An Apple Messages for Business brand record, with the tenant it belongs to and the fields customer-facing reads keep hidden. Superset of what the owning workspace sees on the same record. Returned by both the list and the single-record read; neither returns a soft-deleted record.\n","required":["id","workspace_id","organization_id","apple_business_id","display_name","status","status_reason","msp_app","invitations_enabled","deleted_at","created_at","updated_at"],"properties":{"id":{"allOf":[{"$ref":"#/components/schemas/AMBBusinessID"}],"readOnly":true,"description":"Unique identifier for the business record."},"cursor":{"type":"string","minLength":1,"readOnly":true,"description":"Opaque boundary for this row's (created_at, id) ordering. Included on list responses; reusable across regions as starting_after or ending_before."},"workspace_id":{"allOf":[{"$ref":"#/components/schemas/WorkspaceID"}],"readOnly":true,"description":"The workspace this business record belongs to."},"organization_id":{"allOf":[{"$ref":"#/components/schemas/OrganizationID"}],"readOnly":true,"description":"The organization that owns the workspace above."},"apple_business_id":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"The Business ID Apple issued for this brand. Null until the workspace supplies one, which moves the record from `draft` to `submitted`.","example":"260817-Acme-Retail-EU"},"display_name":{"type":"string","minLength":1,"maxLength":100,"readOnly":true,"description":"The brand name shown for this business record. This is not the name Apple shows to customers; that name comes from the Apple Business Register account behind the Business ID.","example":"Acme Retail"},"status":{"allOf":[{"$ref":"#/components/schemas/AMBBusinessStatus"}],"readOnly":true,"description":"Where the business record stands with Apple."},"status_reason":{"type":["string","null"],"readOnly":true,"description":"Free-text detail behind `status`, such as why Apple's review ended in `rejected`, or why an `approved` business was moved to `suspended`. Null when staff have recorded no detail for the current status.","example":null},"msp_app":{"allOf":[{"$ref":"#/components/schemas/AMBMSPApp"}],"readOnly":true,"description":"The regional Messaging Service Provider application this business is registered under."},"invitations_enabled":{"type":"boolean","readOnly":true,"description":"Whether this business can send invitations. Apple grants invitation access separately from approving the business itself, so this can be false on an `approved` business.","example":false},"deleted_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the workspace disconnected this brand. Null for a connected business. A disconnected brand keeps its conversation history, so its record is soft-deleted rather than removed, but both the list and the single-record read omit a soft-deleted row, so this is always null here.","example":null},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the business record was created.","example":"2026-08-20T09:14:52Z"},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the business record was last changed.","example":"2026-08-25T16:42:01Z"}}},"AdminAMBBusinessList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Business records across every workspace, newest first. Excludes soft-deleted records, same as the single-record read.","items":{"$ref":"#/components/schemas/AdminAMBBusiness"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminAMBInvitationAccessUpdate":{"type":"object","additionalProperties":false,"required":["enabled"],"description":"Staff record of Apple's invitation approval for a business.","properties":{"enabled":{"type":"boolean","description":"Whether Apple has approved this business to send invitations. Recording this value does not request or grant access at Apple."}}},"AdminAMBBusinessTransitionTarget":{"type":"string","minLength":1,"enum":["in_review","approved","rejected","suspended"],"description":"The status to move a business record to. Staff record Apple's own review\noutcome; the legal moves are:\n\n- `in_review`: Apple has picked up the submission. Reachable from\n  `submitted`, or from `rejected` when a business that fixed what Apple\n  objected to goes back for another look.\n- `approved`: Apple approved the business. Reachable from `submitted` or `in_review`, or\n  from `suspended` to reinstate a business whose ability to send had been\n  withdrawn.\n- `rejected`: Apple rejected the business. Reachable from `in_review`, or\n  straight from `submitted` when Apple turns a submission down without\n  taking it into review.\n- `suspended`: The business cannot send or receive messages; conversation-close\n  callbacks are still accepted.\n  Only reachable from `approved`.\n\n`draft` and `submitted` are not reachable through this operation: a\nworkspace enters both by supplying its own Apple Business ID, not staff\naction.\n","example":"approved"},"AdminAMBBusinessTransition":{"type":"object","additionalProperties":false,"description":"Move a business record to a new status in its Apple review lifecycle.","required":["status"],"properties":{"status":{"$ref":"#/components/schemas/AdminAMBBusinessTransitionTarget"},"reason":{"type":["string","null"],"minLength":1,"maxLength":1000,"description":"Free-text detail behind this transition, such as why Apple rejected the business or why an approved business is being suspended. Stored as the business record's `status_reason`. Omit or pass null to clear it.","example":"Rejected by Apple for an unverifiable brand name."}}},"AdminAMBErrorCodeCount":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Count of one failure reason for one business over the requested window.","required":["error_code","send_failed","rejected"],"properties":{"error_code":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBStatsErrorCode"}],"description":"The failure reason these messages share."},"send_failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of the business's messages that failed to send with this reason in the window.","example":14},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of the business's messages Bird refused before any send attempt with this reason in the window. Never charged, and not counted in `send_failed`.","example":3}}},"AdminAMBBusinessErrorCodesResponse":{"type":"object","additionalProperties":false,"description":"Failure-reason counts for one business over the requested window, the drill-down behind the platform-wide error-code breakdown. This reads the underlying event log directly rather than a rollup, so it carries no `data_as_of` and no series: only messages carrying an error code appear, and the window is UTC only.\n","required":["business_id","from","to","attribution","data"],"properties":{"business_id":{"readOnly":true,"$ref":"#/components/schemas/AMBBusinessID","description":"The business these figures are scoped to."},"from":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"Inclusive start of the window, as a UTC instant.","example":"2026-05-01T00:00:00Z"},"to":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"Inclusive end of the window, as a UTC instant.","example":"2026-05-25T00:00:00Z"},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `event_time`. A failure is counted in the window it was recorded in, not the window its message was accepted in."},"data":{"type":"array","readOnly":true,"description":"One row per failure reason with any activity in the window, ordered by `send_failed + rejected` descending, then `error_code` ascending.","items":{"$ref":"#/components/schemas/AdminAMBErrorCodeCount"}}}},"AdminAMBConversation":{"type":"object","additionalProperties":false,"description":"A conversation between a business and one customer on Apple Messages for Business, with the tenant it belongs to. Superset of what the owning workspace sees on the same record, for support inspection. Conversation records have no age limit.\n","required":["id","workspace_id","organization_id","business_id","status","origin","opaque_user_id","phone_number","group_id","intent_id","entry_point","device_capabilities","locale","unread_count","message_count","last_message_at","last_direction","assigned_to","labels","queue","closed_at","closed_reason","open_count","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AMBConversationID","description":"Conversation ID."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"The workspace this conversation belongs to."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"The organization that owns the workspace above."},"business_id":{"readOnly":true,"$ref":"#/components/schemas/AMBBusinessID","description":"Business this conversation belongs to."},"status":{"$ref":"#/components/schemas/AMBConversationStatus","readOnly":true},"origin":{"$ref":"#/components/schemas/AMBConversationOrigin","readOnly":true},"opaque_user_id":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"Apple's opaque identifier for the customer's device, present once the customer has opened the conversation from an entry point. Null on a conversation created from an invitation that has not yet been accepted."},"phone_number":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"The phone number an invitation was sent to, before the customer accepts and Apple issues `opaque_user_id`. Null on a conversation that did not start from an invitation."},"group_id":{"type":"string","readOnly":true,"minLength":1,"description":"Apple's `group` value from the inbound message that opened the conversation."},"intent_id":{"type":"string","readOnly":true,"minLength":1,"description":"Apple's `intent` value from the inbound message that opened the conversation."},"entry_point":{"type":"string","readOnly":true,"minLength":1,"description":"The entry point in the business's channel settings whose group and intent matched the inbound message that opened the conversation. Empty when no configured entry point matched.","example":"support"},"device_capabilities":{"type":"array","readOnly":true,"items":{"type":"string","minLength":1},"description":"The capability tokens the customer's device advertised on its most recent message. An empty list means the device's capabilities are unknown, not that it supports nothing.","example":["QUICK","LIST","TIME"]},"locale":{"type":"string","readOnly":true,"minLength":1,"description":"The customer's locale from the most recent inbound message, or the business's default locale before any inbound arrives. Preserved in Apple's locale format.","example":"en-US"},"unread_count":{"type":"integer","readOnly":true,"minimum":0,"description":"Number of inbound messages since this conversation was last marked read by the workspace."},"message_count":{"type":"integer","readOnly":true,"minimum":0,"description":"Number of messages in this conversation, both directions."},"last_message_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the most recent message in this conversation was sent or received."},"last_direction":{"$ref":"#/components/schemas/AMBMessageDirection","readOnly":true,"description":"Direction of the most recent message."},"assigned_to":{"oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}],"readOnly":true,"description":"The workspace user this conversation is assigned to, or null when unassigned."},"labels":{"type":"array","readOnly":true,"items":{"type":"string","minLength":1,"maxLength":64},"maxItems":20,"description":"Operator-set tags on this conversation."},"queue":{"type":"string","readOnly":true,"minLength":1,"description":"The console queue this conversation is routed to. Empty when no routing rule matched.","example":"support"},"closed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this conversation was closed. Null while it is open."},"closed_reason":{"oneOf":[{"$ref":"#/components/schemas/AMBConversationClosedReason"},{"type":"null"}],"readOnly":true,"description":"Why this conversation was closed. Null while it is open."},"open_count":{"type":"integer","readOnly":true,"minimum":1,"description":"Number of times this conversation has been opened, starting at 1 and incremented on each reopen."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this conversation was created."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this conversation last changed."}}},"AdminAMBConversationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Conversations across every workspace, newest first by last message.","items":{"$ref":"#/components/schemas/AdminAMBConversation"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminAMBMessage":{"type":"object","additionalProperties":false,"description":"One Apple Messages for Business message in the cross-tenant staff view. This is a mirror kept for paging across every workspace at once; it does not carry conversation, content, or cost fields, all of which need the full record. Read one message by id for those.\n","required":["id","workspace_id","organization_id","business_id","direction","status","content_kind","error_code","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AMBMessageID","description":"Message ID. Pass it to the single-message and list-events operations."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"The workspace this message belongs to."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"The organization that owns the workspace above."},"business_id":{"readOnly":true,"$ref":"#/components/schemas/AMBBusinessID","description":"The business the message was sent from or received by."},"direction":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBMessageDirection"}]},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBMessageStatus"}]},"content_kind":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBContentKind"}],"description":"Which of the ten content shapes this message carries."},"error_code":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/AMBStatsErrorCode"},{"type":"null"}],"description":"The failure reason, when `status` is `send_failed` or `rejected`. Null otherwise."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the message was accepted (outbound) or received (inbound)."}}},"AdminAMBMessageList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of messages across every workspace, newest first.","items":{"$ref":"#/components/schemas/AdminAMBMessage"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminAMBMessageDetail":{"type":"object","additionalProperties":false,"description":"Full Apple Messages for Business message record, with the tenant it belongs to and the fields the list view omits. Carries no message content: that is behind the audited reveal, gated on admin:amb_content. Reads the same underlying record the owning workspace's own message read does, so an event that has not yet posted (such as `sent_at` before the send completes) is absent here too.\n","required":["id","workspace_id","organization_id","business_id","conversation_id","direction","status","content_kind","error_code","wire_message_count","attachment_count","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/AMBMessageID","description":"Message ID."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"The workspace this message belongs to."},"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"The organization that owns the workspace above."},"business_id":{"readOnly":true,"$ref":"#/components/schemas/AMBBusinessID","description":"The business the message was sent from or received by."},"conversation_id":{"readOnly":true,"$ref":"#/components/schemas/AMBConversationID","description":"The conversation this message belongs to."},"provider_message_id":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"Apple's own identifier for this message, when known. Never the identifier to page or filter by; use `id`."},"direction":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBMessageDirection"}]},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBMessageStatus"}]},"content_kind":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBContentKind"}],"description":"Derived message classification for filtering and statistics, without revealing the content."},"source":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBMessageSource"}],"description":"Who sent this message. Absent on an inbound message, which has no source to report."},"in_reply_to_message_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBMessageID"}],"description":"The message this one answers. Absent when the message answers nothing."},"opaque_user_id":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"Apple's opaque identifier for the customer's device that sent or received this message. Null when not yet known."},"locale":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"The customer's locale, taken from the conversation at the time this message was sent or received. Preserved in Apple's locale format. Null when the conversation had no locale yet.","example":"en-US"},"category":{"type":"string","readOnly":true,"description":"The category this message was sent with, for grouping in statistics. Absent on an inbound message.","example":"order_update"},"group_id":{"type":"string","readOnly":true,"minLength":0,"description":"Apple's `group` value tied to this message's conversation. Absent on an outbound message with no reply to route."},"intent_id":{"type":"string","readOnly":true,"minLength":0,"description":"Apple's `intent` value tied to this message's conversation. Absent on an outbound message with no reply to route."},"entry_point":{"type":"string","readOnly":true,"minLength":0,"description":"The entry point the message's conversation opened from. Empty when none matched.","example":"support"},"tags":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/Tag"},"description":"Structured filter labels applied to this message. Absent on an inbound message."},"error_code":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/AMBStatsErrorCode"},{"type":"null"}],"description":"The failure reason, when `status` is `send_failed` or `rejected`. Null otherwise."},"error_reason":{"type":["string","null"],"readOnly":true,"minLength":1,"description":"Human-readable detail for `error_code`. Null when `error_code` is null."},"wire_message_count":{"type":"integer","readOnly":true,"minimum":0,"description":"Number of wire messages Apple's protocol split this send into.","example":1},"attachment_count":{"type":"integer","readOnly":true,"minimum":0,"description":"Number of attachments carried by this message.","example":0},"attachment_meta":{"type":["string","null"],"readOnly":true,"description":"Opaque attachment metadata for an `attachment` message. Null when `content_kind` is not `attachment`."},"cost_currency":{"oneOf":[{"$ref":"#/components/schemas/CurrencyCode"},{"type":"null"}],"readOnly":true,"description":"The currency `cost_amount` is denominated in. Null on a message with no cost recorded."},"cost_amount":{"type":["string","null"],"readOnly":true,"description":"Decimal-string cost of this message in `cost_currency`. Null on a message with no cost recorded.","example":"0.045"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the message was accepted (outbound) or received (inbound)."},"sent_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the message was handed to Apple. Null until then, and always null on an inbound message."}}},"AdminAMBContentReveal":{"type":"object","additionalProperties":false,"description":"The content of an Apple Messages for Business message, returned to authorized staff for review. Carries `direction` and `content_kind` because `content` is a union that cannot be read without them, and nothing else: the rest of the envelope is on the log row this reveal was opened from.\n","properties":{"direction":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBMessageDirection"}],"description":"Which arm of `content` applies. Read this before `content`."},"content_kind":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBContentKind"}],"description":"Derived message classification for filtering and statistics. The native family is content.type."},"content":{"readOnly":true,"description":"Native text, rich_link, or interactive content. Outbound interactions carry requests; inbound interactions carry customer replies.","anyOf":[{"$ref":"#/components/schemas/AMBMessageContent"},{"$ref":"#/components/schemas/AMBInboundContent"}]}},"required":["direction","content_kind","content"]},"AdminAMBStatsSeriesPeriod":{"type":"object","additionalProperties":false,"description":"The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to. The series is bucketed by day, hour, or month; month is not offered on the customer surface.\n","required":["from","to","grain"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window. A calendar day (YYYY-MM-DD) on the day and month grains, an RFC 3339 instant on the hour grain. Raised to the 365-day retention boundary when the request named an earlier start.","example":"2026-05-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window. A calendar day (YYYY-MM-DD) on the day and month grains, an RFC 3339 instant on the hour grain.","example":"2026-05-25"},"grain":{"$ref":"#/components/schemas/AdminStatsGrain","readOnly":true},"data_as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable.\n","example":"2026-05-25T14:03:10Z"}}},"AdminAMBStatsTotals":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Aggregate outbound counts and latency percentiles across the whole window. Each count is the number of distinct messages in that state over the whole window, merged once rather than summed from the series buckets. The percentiles are likewise computed over the whole window rather than combined from per-bucket values. Read `first_response` here or on the breakdowns; the time series does not include it.\n","required":["counts","latency"],"properties":{"counts":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBOutboundStatsCounts"}]},"latency":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBStatsLatency"}]},"first_response":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AMBStatsQuantiles"}],"description":"Time from the oldest unanswered inbound message to acceptance of the first outbound reply that claims it. Omitted entirely when no qualifying message in scope has a measurement."}}},"AdminAMBStatsResponse":{"type":"object","additionalProperties":false,"description":"Platform-wide Apple Messages for Business outbound sending and processing-latency statistics over time, within a single region. `series` is one row per bucket in chronological order; `totals` aggregates the whole window. Apple Messages for Business has no delivery receipt, so neither carries a `delivered` count: `sent` is the furthest status Bird observes.\n","required":["region","period","attribution","totals","series"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"readOnly":true,"$ref":"#/components/schemas/AdminAMBStatsSeriesPeriod","description":"The window and bucket grain the response covers (echoed from the request), plus the freshness boundary the data is current to."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time`. There is no outbound event earlier than acceptance to anchor these figures to."},"totals":{"readOnly":true,"$ref":"#/components/schemas/AdminAMBStatsTotals"},"series":{"type":"array","readOnly":true,"description":"One row per bucket (day, hour, or month, per the grain) in chronological order. Buckets with no activity are included with zero counts.","items":{"$ref":"#/components/schemas/AMBStatsPoint"}}}},"AdminAMBStatsSortMetric":{"type":"string","default":"accepted","description":"Metric to rank breakdown rows by, applied descending. Shared by the volume breakdowns whose rows carry the outbound states and the send-failure rate. `send_failure_rate` is send_failed over accepted; rows with nothing accepted sort last. `rejected` ranks by messages Bird refused before any send attempt, which are outside `accepted`.\n","enum":["accepted","sent","send_failed","rejected","send_failure_rate"]},"AdminAMBStatsByBusinessResponse":{"type":"object","additionalProperties":false,"description":"Per-business Apple Messages for Business breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200).\n","required":["region","period","attribution","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"Business rows, ranked by the `sort` metric (default `accepted`) descending. Empty when nothing was accepted in the period.","items":{"$ref":"#/components/schemas/AMBBusinessStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct businesses with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":6}}},"AdminAMBStatsByMessageKindResponse":{"type":"object","additionalProperties":false,"description":"Per-message-kind Apple Messages for Business breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200).\n","required":["region","period","attribution","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"Message kind rows, ranked by the `sort` metric (default `accepted`) descending. Empty when nothing was accepted in the period.","items":{"$ref":"#/components/schemas/AMBMessageKindStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct message kinds with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":6}}},"AdminAMBStatsByCategoryResponse":{"type":"object","additionalProperties":false,"description":"Per-category Apple Messages for Business breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200). A send that named no category lands in the empty-string row.\n","required":["region","period","attribution","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"Category rows, ranked by the `sort` metric (default `accepted`) descending. Empty when nothing was accepted in the period.","items":{"$ref":"#/components/schemas/AMBCategoryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct categories with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":6}}},"AdminAMBStatsLifecycleSortMetric":{"type":"string","default":"failures","description":"Metric to rank breakdown rows by, applied descending. Shared by the breakdowns whose rows carry outbound state counts only, with no derived rates to sort on. `failures` is `send_failed + rejected`, so a code that rides either terminal state is visible.\n","enum":["failures","accepted","sent","send_failed","rejected"]},"AdminAMBStatsByErrorCodeResponse":{"type":"object","additionalProperties":false,"description":"Per-error-code Apple Messages for Business breakdown for one customer organization or platform-wide within a single region, ranked by the `sort` metric (default `failures`, which is `send_failed + rejected`) descending and capped at the requested `limit` (default 50, max 200). Only messages carrying a failure reason appear: a `bird:` code is usually a rejection, an `apple:` code a send failure.\n","required":["region","period","attribution","data","total"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these statistics are drawn from."},"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSummaryPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"attribution":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsAttribution","description":"Always `accepted_time` for outbound statistics."},"data":{"type":"array","readOnly":true,"description":"Error code rows, ranked by the `sort` metric (default `failures`) descending. Empty when no failures or rejections occurred in the period.","items":{"$ref":"#/components/schemas/AMBErrorCodeStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct error codes with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.\n","example":6}}},"AdminAMBTopOrganization":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One organization in the top-senders ranking, with its outbound volume and send-failure rate over the requested window. Apple Messages for Business has no delivery receipt, so this carries no `delivered` count: unlike the other channels' top-organizations shape, the rate here is a failure rate, not a delivery rate.\n","required":["organization_id","name","accepted","send_failed"],"properties":{"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"The organization these figures are for."},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the organization.","example":"Acme Retail Group"},"accepted":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of messages the organization had accepted for sending in the window.","example":96200},"send_failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of the organization's messages that failed to send in the window.","example":640},"send_failure_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Share of accepted messages that failed to send, between 0 and 1, or null when no messages were accepted in the window.","example":0.0067}}},"AdminAMBTopOrganizationList":{"type":"object","additionalProperties":false,"description":"The organizations sending the most Apple Messages for Business in a single region over the requested window, ranked by accepted volume. This list is already capped at the request's `limit` before ranking, so combining several regions' lists cannot recover a true global ranking; there is no global top-organizations ranking.\n","required":["region","period","organizations"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these figures are drawn from."},"period":{"readOnly":true,"$ref":"#/components/schemas/AMBStatsSeriesPeriod","description":"The window and bucket grain the ranking covers, echoed from the request."},"organizations":{"type":"array","readOnly":true,"description":"Organizations ranked by accepted volume, highest first.","items":{"$ref":"#/components/schemas/AdminAMBTopOrganization"}}}},"AdminCreateDomainBanPlan":{"type":"object","additionalProperties":false,"required":["domain","category","reason"],"properties":{"domain":{"type":"string","minLength":1,"description":"Email domain to block, without an @ sign. Subdomains are included.","maxLength":253},"category":{"$ref":"#/components/schemas/AdminBanCategory"},"reason":{"type":"string","minLength":1,"description":"Why the selected accounts should be banned.","maxLength":2000}}},"DomainBanPlanID":{"type":"string","minLength":1,"pattern":"^dbp_[0-9a-hjkmnp-tv-z]{26}$","example":"dbp_01krdgeqcxet5s7t44vh8rt9mg"},"AdminDomainBanOwner":{"type":"object","additionalProperties":false,"required":["id","email"],"properties":{"id":{"$ref":"#/components/schemas/UserID"},"email":{"type":"string","minLength":1,"description":"Email address when the plan was created.","format":"email"}}},"AdminDomainBanOrganization":{"type":"object","additionalProperties":false,"required":["id","name","region","created_at","owners","member_count","outside_domain_member_count"],"properties":{"id":{"$ref":"#/components/schemas/OrganizationID"},"name":{"type":"string","minLength":1,"description":"Organization name."},"region":{"$ref":"#/components/schemas/Region"},"created_at":{"type":"string","minLength":1,"description":"Organization creation time.","format":"date-time"},"owners":{"type":"array","description":"Matching current owners captured by the plan.","items":{"$ref":"#/components/schemas/AdminDomainBanOwner"}},"member_count":{"type":"integer","minimum":0,"description":"Distinct organization members."},"outside_domain_member_count":{"type":"integer","minimum":0,"description":"Distinct members outside the blocked domain and its subdomains."}}},"AdminDomainBanSummary":{"type":"object","additionalProperties":false,"required":["organization_count","owner_count","affected_user_count","outside_domain_user_count","mixed_organization_count","excluded_owner_count","excluded_organization_count","oldest_organization","newest_organization"],"properties":{"organization_count":{"type":"integer","minimum":0,"description":"Organizations selected for enforcement."},"owner_count":{"type":"integer","minimum":0,"description":"Distinct matching owners selected for enforcement."},"affected_user_count":{"type":"integer","minimum":0,"description":"Distinct users selected for enforcement, including organization members."},"outside_domain_user_count":{"type":"integer","minimum":0,"description":"Distinct affected users outside the blocked domain and its subdomains."},"mixed_organization_count":{"type":"integer","minimum":0,"description":"Selected organizations with at least two distinct members outside the blocked domain and its subdomains."},"excluded_owner_count":{"type":"integer","minimum":0,"description":"Matching owners excluded from enforcement."},"excluded_organization_count":{"type":"integer","minimum":0,"description":"Matching organizations excluded from enforcement."},"oldest_organization":{"description":"Oldest selected organization, or null when no organizations are selected.","oneOf":[{"$ref":"#/components/schemas/AdminDomainBanOrganization"},{"type":"null"}]},"newest_organization":{"description":"Newest selected organization, or null when no organizations are selected.","oneOf":[{"$ref":"#/components/schemas/AdminDomainBanOrganization"},{"type":"null"}]}}},"AdminDomainBanPlan":{"type":"object","readOnly":true,"additionalProperties":false,"required":["id","domain","category","reason","created_at","created_by_user_id","identity_region","summary","organization_examples"],"properties":{"id":{"$ref":"#/components/schemas/DomainBanPlanID"},"domain":{"type":"string","minLength":1,"description":"Blocked email domain, including its subdomains."},"category":{"$ref":"#/components/schemas/AdminBanCategory"},"reason":{"type":"string","minLength":1,"description":"Reason recorded on bans applied from this plan."},"created_at":{"type":"string","minLength":1,"description":"Time the plan was created.","format":"date-time"},"created_by_user_id":{"$ref":"#/components/schemas/UserID"},"identity_region":{"$ref":"#/components/schemas/Region"},"summary":{"$ref":"#/components/schemas/AdminDomainBanSummary"},"organization_examples":{"type":"array","maxItems":20,"description":"Up to 20 selected organizations as examples. Summary totals and enforcement cover the entire plan.","items":{"$ref":"#/components/schemas/AdminDomainBanOrganization"}}}},"AdminDomainBanExecutionStatus":{"type":"string","minLength":1,"description":"Current regional enforcement status.\n\n- `not_started` means no retained execution exists for this plan in this region.\n- `running` means regional bans and deletion scheduling are being processed.\n- `completed` means all saved targets were processed or skipped; scheduled deletion retains its grace periods.\n- `failed` means enforcement stopped after an error.\n- `canceled` means enforcement stopped following a cancellation request.\n- `terminated` means enforcement was forcibly stopped without cleanup.\n- `timed_out` means enforcement exceeded its allowed execution time.\n\nStopping an execution does not roll back applied bans or scheduled deletions.\nRepeating approval returns the retained execution instead of restarting it.\n","enum":["not_started","running","completed","failed","canceled","terminated","timed_out"]},"AdminDomainBanExecution":{"type":"object","readOnly":true,"additionalProperties":false,"required":["plan_id","region","workflow_id","status","approved_by_user_id","started_at","user_count","processed_user_count","skipped_user_count","organization_count","processed_organization_count","skipped_organization_count","error"],"properties":{"plan_id":{"$ref":"#/components/schemas/DomainBanPlanID"},"region":{"$ref":"#/components/schemas/Region"},"workflow_id":{"type":"string","minLength":1,"description":"Identifier of this regional execution."},"status":{"$ref":"#/components/schemas/AdminDomainBanExecutionStatus"},"approved_by_user_id":{"oneOf":[{"$ref":"#/components/schemas/UserID"},{"type":"null"}],"description":"Approving staff user, or null before execution starts."},"started_at":{"type":["string","null"],"format":"date-time","description":"Execution start time, or null before execution starts."},"user_count":{"type":"integer","minimum":0,"description":"Users assigned to this regional execution."},"processed_user_count":{"type":"integer","minimum":0,"description":"Users whose bans and deletion scheduling have been processed."},"skipped_user_count":{"type":"integer","minimum":0,"description":"Users skipped during execution."},"organization_count":{"type":"integer","minimum":0,"description":"Organizations assigned to this regional execution."},"processed_organization_count":{"type":"integer","minimum":0,"description":"Organizations whose bans and deletion scheduling have been processed."},"skipped_organization_count":{"type":"integer","minimum":0,"description":"Organizations skipped during execution."},"error":{"type":["string","null"],"description":"Execution failure, or null when no failure is reported."}}},"AdminExecuteDomainBanPlan":{"type":"object","additionalProperties":false,"required":["confirm","region"],"properties":{"confirm":{"type":"boolean","enum":[true],"description":"Confirms approval of the saved plan and starts enforcement."},"region":{"$ref":"#/components/schemas/Region","description":"Region in which to start enforcement. Must match the region of the API receiving the request; a mismatch returns 422."}}},"EmailDomainBlocklistEntryID":{"type":"string","minLength":1,"pattern":"^edb_[0-9a-hjkmnp-tv-z]{26}$","example":"edb_01krdgeqcxet5s7t44vh8rt9mg"},"AdminEmailDomainBlocklistEntry":{"type":"object","additionalProperties":false,"required":["id","domain","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/EmailDomainBlocklistEntryID"},"domain":{"type":"string","minLength":1,"readOnly":true,"description":"The blocked domain."},"reason":{"type":"string","minLength":1,"readOnly":true,"description":"Optional note explaining why this domain is blocked."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this entry was added to the blocklist."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this entry was last updated."}}},"AdminEmailDomainBlocklistEntryList":{"type":"object","additionalProperties":false,"required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEmailDomainBlocklistEntry"},"description":"Matching blocklist entries."},"total":{"type":"integer","format":"int64","description":"Total number of entries matching the query, ignoring pagination."}}},"AdminCreateEmailDomainBlocklistEntry":{"type":"object","additionalProperties":false,"required":["domain"],"properties":{"domain":{"type":"string","minLength":1,"description":"The email domain to block (e.g. example.com). The @ symbol must not be included."},"reason":{"type":"string","description":"Optional note explaining why this domain is being blocked."}}},"IPBlockID":{"type":"string","minLength":1,"pattern":"^ipb_[0-9a-hjkmnp-tv-z]{26}$","example":"ipb_01krdgeqcxet5s7t44vh8rt9mg"},"AdminIPBlock":{"type":"object","additionalProperties":false,"required":["id","cidr","reason","created_by","created_at","expires_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/IPBlockID"},"cidr":{"type":"string","readOnly":true,"minLength":1,"description":"The blocked IP or CIDR range. A single IP is represented as a /32 (IPv4) or /128 (IPv6).","example":"203.0.113.0/24"},"reason":{"type":"string","readOnly":true,"minLength":1,"description":"Why this range was blocked."},"created_by":{"readOnly":true,"description":"Staff user who added this block.","$ref":"#/components/schemas/UserID"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this block was added."},"expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this block stops being enforced. Null blocks indefinitely."}}},"AdminIPBlockList":{"type":"object","additionalProperties":false,"required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminIPBlock"},"description":"Matching IP blocks."},"total":{"type":"integer","format":"int64","description":"Total number of IP blocks matching the query, ignoring pagination."}}},"AdminIPBlockCreate":{"type":"object","additionalProperties":false,"required":["cidr","reason"],"properties":{"cidr":{"type":"string","minLength":1,"description":"The IP or CIDR range to block (e.g. 203.0.113.5/32 or 203.0.113.0/24).","example":"203.0.113.0/24"},"reason":{"type":"string","minLength":1,"description":"Why this range is being blocked."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When this block should stop being enforced. Omit or set to null to block indefinitely."}}},"AdminMarketingSocialPlatform":{"type":"string","minLength":1,"enum":["linkedin","x","instagram","tiktok","youtube","facebook","reddit","threads"],"description":"A social media platform Bird publishes on. Closed: a write is checked against this set, and the admin app ships with the backend, so a new network reaches both at once.","example":"linkedin"},"MarketingSkillID":{"type":"string","minLength":1,"pattern":"^mskl_[0-9a-hjkmnp-tv-z]{26}$","example":"mskl_01krdgeqcxet5s7t44vh8rt9mg"},"AdminMarketingSkill":{"type":"object","additionalProperties":false,"required":["id","name","platform","prompt","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/MarketingSkillID"},"name":{"type":"string","minLength":1,"description":"What the skill produces, as staff refer to it."},"platform":{"$ref":"#/components/schemas/AdminMarketingSocialPlatform"},"description":{"type":["string","null"],"description":"When to reach for this skill. Null when nobody has written one."},"prompt":{"type":"string","minLength":1,"description":"The prompt itself, as Markdown."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the skill was added to the catalogue."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the skill was last edited."}}},"AdminMarketingSkillList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingSkill"},"description":"The matching skills."}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminMarketingSkillCreate":{"type":"object","additionalProperties":false,"required":["name","platform","prompt"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"What the skill produces, as staff refer to it."},"platform":{"$ref":"#/components/schemas/AdminMarketingSocialPlatform"},"description":{"type":["string","null"],"maxLength":1000,"description":"When to reach for this skill."},"prompt":{"type":"string","minLength":1,"maxLength":50000,"description":"The prompt itself, as Markdown."}}},"AdminMarketingSkillUpdate":{"type":"object","additionalProperties":false,"description":"Only the fields present are changed.","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"What the skill produces, as staff refer to it."},"platform":{"$ref":"#/components/schemas/AdminMarketingSocialPlatform"},"description":{"type":["string","null"],"maxLength":1000,"description":"When to reach for this skill."},"prompt":{"type":"string","minLength":1,"maxLength":50000,"description":"The prompt itself, as Markdown."}}},"AdminMarketingChannelPlatform":{"type":"string","minLength":1,"enum":["linkedin","x","instagram","tiktok","youtube","facebook","reddit","threads","bird"],"description":"A destination a campaign can hold a channel for: one of the social networks Bird publishes on, or `bird` for a marketing email sent as a broadcast from a connected Bird workspace. Closed: a write is checked against this set, and the admin app ships with the backend, so a new destination reaches both at once. It is not the same set as the platforms a connection authorizes: an advertising authorization adds reporting and no channel.","example":"linkedin"},"AdminMarketingAssetKind":{"type":"string","minLength":1,"x-extensible-enum":["image","video"],"description":"What kind of file an asset holds, derived from its stored content type rather than from anything the upload declared. `image` is re-encoded on the way in; `video` is stored byte for byte. The set grows as the surface accepts more kinds, so treat an unrecognised value as a file this client cannot preview.","example":"image"},"AdminMarketingCampaignAssetRecommendation":{"additionalProperties":false,"type":"object","required":["kind","width","height","aspect_ratio"],"properties":{"kind":{"$ref":"#/components/schemas/AdminMarketingAssetKind"},"width":{"type":"integer","minimum":1,"description":"The recommended width in pixels.","example":1080},"height":{"type":"integer","minimum":1,"description":"The recommended height in pixels.","example":1350},"aspect_ratio":{"type":"string","minLength":1,"description":"The shape these dimensions describe, for display beside them. Every recommendation states a ratio exactly, so an upload matching one of them at a higher resolution matches this ratio too.\n","example":"4:5"}}},"AdminMarketingCampaignAssetSpec":{"additionalProperties":false,"type":"object","required":["platform","recommendations"],"properties":{"platform":{"$ref":"#/components/schemas/AdminMarketingChannelPlatform"},"recommendations":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingCampaignAssetRecommendation"},"description":"What to upload for this platform, most preferred first within each kind. A platform that publishes only video carries no `image` entry, and uploading an image to it is refused.\n"}}},"AdminMarketingCampaignAssetSpecList":{"additionalProperties":false,"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingCampaignAssetSpec"},"description":"Every platform a campaign can publish on, in the order the campaign surface lists them.\n"}}},"AdminMarketingCampaignTag":{"type":"string","minLength":1,"maxLength":32,"pattern":"^[a-z0-9_-]+$","description":"One label staff group campaigns by. Lowercase letters, digits, underscore, and hyphen only, at most 32 characters. Whitespace is not accepted.","example":"ga_launch"},"AdminMarketingCampaignTagUsage":{"additionalProperties":false,"type":"object","required":["tag","count"],"properties":{"tag":{"$ref":"#/components/schemas/AdminMarketingCampaignTag"},"count":{"type":"integer","format":"int64","minimum":1,"description":"How many campaigns carry this tag, across every campaign rather than the ones a campaign search or platform filter currently selects."}}},"AdminMarketingCampaignTagList":{"additionalProperties":false,"type":"object","required":["data"],"properties":{"data":{"type":"array","maxItems":500,"items":{"$ref":"#/components/schemas/AdminMarketingCampaignTagUsage"},"description":"Tags currently on at least one campaign, in alphabetical order, each with how many campaigns carry it. Returns the first 500 distinct tags when more are in use. A tag disappears when the last campaign drops it.\n"}}},"MarketingConnectionID":{"type":"string","minLength":1,"pattern":"^mcon_[0-9a-hjkmnp-tv-z]{26}$","example":"mcon_01krdgeqcxet5s7t44vh8rt9mg"},"AdminMarketingEngagementMetricKey":{"type":"string","minLength":1,"x-extensible-enum":["views","impressions","organic_impressions","likes","reactions","comments","shares","score"],"description":"Which engagement a value counts. Each platform reports only the few most\nrelevant to it:\n\n- `views`: A play or view, on Instagram, Threads, TikTok and YouTube.\n- `impressions`: A delivery to a timeline, on X.\n- `organic_impressions`: The same, unpaid, as LinkedIn reports it separately.\n- `likes`: A like, on every platform that names one.\n- `reactions`: Facebook's reactions, which are not only likes.\n- `comments`: A comment or reply.\n- `shares`: A share, where the platform counts one apart from a repost.\n- `score`: Reddit's net score, which is not a like count.\n"},"AdminMarketingCampaignEngagementPoint":{"additionalProperties":false,"type":"object","required":["day","total"],"properties":{"day":{"type":"string","format":"date","minLength":10,"description":"The UTC date the change is attributed to."},"total":{"type":"integer","format":"int64","description":"The metric's change on this date, summed over every campaign published to the account. Negative when the platform's counter fell."}}},"AdminMarketingCampaignEngagementMetric":{"additionalProperties":false,"type":"object","required":["key","points"],"properties":{"key":{"$ref":"#/components/schemas/AdminMarketingEngagementMetricKey"},"points":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingCampaignEngagementPoint"},"description":"The reported days inside the window, oldest first. Missing days are absent."}}},"AdminMarketingCampaignEngagementSeries":{"additionalProperties":false,"type":"object","required":["connection_id","metrics"],"properties":{"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID"},"metrics":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingCampaignEngagementMetric"},"description":"The account's platform's most relevant metrics, in display order. A metric the platform does not report, or that no post collected in the window, is absent.\n"}}},"AdminMarketingCampaignEngagementList":{"additionalProperties":false,"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingCampaignEngagementSeries"},"description":"One entry per account a campaign published through that collected engagement in the window. Disconnecting an account retains what its posts already collected, so an account no longer connected still appears: this is publication history, not connection inventory. Unpaginated, because these are Bird's own accounts and the answer is bounded by how many Bird has published from rather than by a page size.\n"}}},"MarketingCampaignPublicationID":{"type":"string","minLength":1,"pattern":"^mcpub_[0-9a-hjkmnp-tv-z]{26}$","example":"mcpub_01krdgeqcxet5s7t44vh8rt9mg"},"MarketingCampaignID":{"type":"string","minLength":1,"pattern":"^mcam_[0-9a-hjkmnp-tv-z]{26}$","example":"mcam_01krdgeqcxet5s7t44vh8rt9mg"},"AdminMarketingCalendarEntry":{"type":"object","additionalProperties":false,"required":["id","campaign_id","campaign_title","platform","occurs_at"],"properties":{"id":{"$ref":"#/components/schemas/MarketingCampaignPublicationID"},"campaign_id":{"$ref":"#/components/schemas/MarketingCampaignID"},"campaign_title":{"type":"string","minLength":1,"description":"The campaign title."},"platform":{"$ref":"#/components/schemas/AdminMarketingChannelPlatform"},"occurs_at":{"type":"string","format":"date-time","minLength":1,"description":"The scheduled time, or the confirmed publication time for a published post."}}},"AdminMarketingCalendarList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingCalendarEntry"},"description":"Scheduled and published campaign entries in the requested range."}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminMarketingCampaignMetricStatus":{"type":"string","minLength":1,"maxLength":11,"enum":["ready","partial","unavailable"],"description":"- `ready` means all published platforms contribute an available count without collection issues, or all publications carry signup attribution tags.\n- `partial` means some published platforms lack this metric, have collection issues, or carry no signup attribution tags.\n- `unavailable` means no count can be reported.\n"},"AdminMarketingCampaignMetric":{"type":"object","additionalProperties":false,"required":["value","status","observed_at"],"properties":{"value":{"type":["integer","null"],"format":"int64","description":"Sum of available counters, or null when none are available. Zero is a confirmed count."},"status":{"$ref":"#/components/schemas/AdminMarketingCampaignMetricStatus"},"observed_at":{"type":"object","additionalProperties":{"type":"string","format":"date-time"},"description":"Contributing platforms mapped to their observation timestamps. For signups these identify tagged publication platforms and the time attribution was read, not the source of every signup."}}},"AdminMarketingCampaignMetrics":{"type":"object","additionalProperties":false,"required":["likes","comments","views","clicks","signups"],"properties":{"likes":{"$ref":"#/components/schemas/AdminMarketingCampaignMetric"},"comments":{"$ref":"#/components/schemas/AdminMarketingCampaignMetric"},"views":{"$ref":"#/components/schemas/AdminMarketingCampaignMetric"},"clicks":{"$ref":"#/components/schemas/AdminMarketingCampaignMetric"},"signups":{"$ref":"#/components/schemas/AdminMarketingCampaignMetric"}},"description":"Lifetime observed totals. Clicks counts destination-link clicks only; signups uses first-touch attribution from published UTM tags. Platform audiences are not deduplicated."},"AdminMarketingCampaignUTMMode":{"type":"string","minLength":1,"enum":["off","automatic","manual"],"description":"How campaign links receive UTM tags."},"AdminMarketingCampaignUTM":{"type":"object","additionalProperties":false,"required":["mode"],"description":"Tracking applied to HTTP(S) links in the destination, body, and call to action when publishing or scheduling. Authored links are unchanged. Nonblank values replace matching query parameters; blank values leave existing parameters alone.","properties":{"mode":{"$ref":"#/components/schemas/AdminMarketingCampaignUTMMode","description":"Off leaves links unchanged. Automatic uses the platform as utm_source, the campaign ID as utm_campaign, and a utm_medium that follows the channel: email for the Bird email channel, social for every network. Manual uses the supplied values. Supplied manual values are stored in every mode."},"utm_source":{"type":"string","maxLength":512,"description":"Manual utm_source value. Blank values are ignored."},"utm_medium":{"type":"string","maxLength":512,"description":"Manual utm_medium value. Blank values are ignored."},"utm_campaign":{"type":"string","maxLength":512,"description":"Manual utm_campaign value. Blank values are ignored."},"utm_term":{"type":"string","maxLength":512,"description":"Manual utm_term value. Blank values are ignored."},"utm_content":{"type":"string","maxLength":512,"description":"Manual utm_content value. Blank values are ignored."}}},"AdminMarketingCampaignPlatforms":{"type":"array","uniqueItems":true,"description":"The platforms this campaign should be published on. An empty set means it is not scheduled anywhere yet. Returned in the platform vocabulary's own order, not the order it was sent in.","items":{"$ref":"#/components/schemas/AdminMarketingChannelPlatform"}},"AdminMarketingCampaignTags":{"type":"array","uniqueItems":true,"maxItems":20,"description":"Free-form labels staff group campaigns by, such as the push a campaign belongs to. Returned in alphabetical order, not the order it was sent in.","items":{"$ref":"#/components/schemas/AdminMarketingCampaignTag"}},"MarketingCampaignAssetID":{"type":"string","minLength":1,"pattern":"^mcas_[0-9a-hjkmnp-tv-z]{26}$","example":"mcas_01krdgeqcxet5s7t44vh8rt9mg"},"AdminMarketingCampaignPublishingTarget":{"type":"object","additionalProperties":false,"required":["connection_id"],"properties":{"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID"}}},"AdminMarketingCampaignYouTube":{"type":"object","additionalProperties":false,"required":["title","made_for_kids","contains_synthetic_media"],"properties":{"title":{"type":"string","minLength":1,"maxLength":100},"made_for_kids":{"type":"boolean","description":"Whether the video is directed at children."},"contains_synthetic_media":{"type":"boolean","description":"Whether the video contains realistic altered or synthetic content."}}},"AdminMarketingCampaignPublicationStatus":{"type":"string","minLength":1,"enum":["scheduled","publishing","published","failed","needs_attention","cancelled"],"description":"`scheduled` waits for its publication time and freezes the channel until then. `publishing` is the initial status of an immediate publication and awaits a confirmed result. `published` has a confirmed platform post. `failed` can be retried after its cause is corrected. `needs_attention` has an uncertain result and must be checked before another publication can begin. `cancelled` was a schedule staff withdrew before it ran, and releases the channel."},"AdminMarketingFacebookPostFormat":{"type":"string","enum":["link"],"description":"A native Facebook link post. Absent on legacy text and photo publications."},"AdminMarketingRedditSubredditDestinationKind":{"type":"string","minLength":1,"enum":["subreddit"]},"AdminMarketingRedditSubredditDestination":{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"$ref":"#/components/schemas/AdminMarketingRedditSubredditDestinationKind"},"subreddit":{"type":"string","maxLength":21,"pattern":"^[A-Za-z0-9_]+$","description":"Subreddit name without r/. May be omitted in a draft; publishing requires a name."}}},"AdminMarketingRedditProfileDestinationKind":{"type":"string","minLength":1,"enum":["profile"]},"AdminMarketingRedditProfileDestination":{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"$ref":"#/components/schemas/AdminMarketingRedditProfileDestinationKind"}}},"AdminMarketingRedditDestination":{"description":"A subreddit draft or the connected account's own profile. Profile posts do not accept a subreddit name.","oneOf":[{"$ref":"#/components/schemas/AdminMarketingRedditSubredditDestination"},{"$ref":"#/components/schemas/AdminMarketingRedditProfileDestination"}],"discriminator":{"propertyName":"kind","mapping":{"subreddit":"#/components/schemas/AdminMarketingRedditSubredditDestination","profile":"#/components/schemas/AdminMarketingRedditProfileDestination"}}},"AdminMarketingRedditFormat":{"type":"string","enum":["text","link","images","video"]},"AdminMarketingCampaignReddit":{"type":"object","additionalProperties":false,"description":"Reddit post settings. Drafts may omit the title, destination, or format; publishing requires all three.","properties":{"title":{"type":"string","maxLength":300,"description":"Authored Reddit title. Publishing requires nonblank text."},"destination":{"$ref":"#/components/schemas/AdminMarketingRedditDestination"},"format":{"$ref":"#/components/schemas/AdminMarketingRedditFormat"},"flair_id":{"type":"string","maxLength":100},"flair_text":{"type":"string","maxLength":64},"thumbnail_asset_id":{"$ref":"#/components/schemas/MarketingCampaignAssetID","description":"JPEG or PNG asset from this channel used as the thumbnail for an MP4 video. It remains in the channel assets."}}},"AdminMarketingCampaignEmailSubject":{"type":"string","maxLength":255,"description":"The subject line of a Bird email channel's broadcast."},"AdminMarketingCampaignEmailHeading":{"type":"string","maxLength":120,"description":"The headline rendered at the top of a Bird email channel's broadcast."},"AdminMarketingCampaignEmailPreheader":{"type":"string","maxLength":255,"description":"The preview text a mail client shows beside a Bird email channel's subject."},"AdminMarketingCampaignEmail":{"type":"object","additionalProperties":false,"description":"Bird email settings: the envelope the broadcast carries and the audience in the connected workspace it goes to. Drafts may omit any of them; publishing requires a subject, a sender, and an audience.","properties":{"subject":{"$ref":"#/components/schemas/AdminMarketingCampaignEmailSubject","description":"The subject line. Publishing requires nonblank text."},"heading":{"$ref":"#/components/schemas/AdminMarketingCampaignEmailHeading","description":"The headline shown at the top of the email, above the body. Absent when none is authored, in which case the email opens on the body."},"preheader":{"$ref":"#/components/schemas/AdminMarketingCampaignEmailPreheader","description":"The preview text a mail client shows beside the subject. Absent when none is authored, in which case the client falls back to the opening of the body."},"from":{"type":"string","format":"email","maxLength":254,"description":"The address the broadcast sends from. Its domain has to be verified in the connected workspace, which is what the connection's email options report."},"from_name":{"type":"string","maxLength":64,"description":"The display name shown beside the sender address."},"reply_to":{"type":"string","format":"email","maxLength":254,"description":"Where replies go. Absent to let them return to the sender address."},"audience_id":{"$ref":"#/components/schemas/AudienceID","description":"The audience in the connected workspace the broadcast sends to. Its contacts at send time, minus suppressions, are the recipients."}}},"AdminMarketingCampaignPublicationSnapshot":{"type":"object","additionalProperties":false,"required":["asset_ids"],"properties":{"facebook_format":{"$ref":"#/components/schemas/AdminMarketingFacebookPostFormat"},"body":{"type":"string","minLength":1},"call_to_action":{"type":"string","minLength":1},"link_url":{"type":"string","format":"uri","pattern":"^https://","minLength":1},"asset_ids":{"type":"array","items":{"$ref":"#/components/schemas/MarketingCampaignAssetID"},"uniqueItems":true,"maxItems":24},"youtube":{"$ref":"#/components/schemas/AdminMarketingCampaignYouTube"},"reddit":{"$ref":"#/components/schemas/AdminMarketingCampaignReddit"},"email":{"$ref":"#/components/schemas/AdminMarketingCampaignEmail"}}},"AdminMarketingFacebookLinkPreview":{"type":"string","enum":["campaign_image","destination"],"description":"Whether Facebook receives the campaign image or uses the destination page preview."},"AdminMarketingCampaignPublication":{"readOnly":true,"unevaluatedProperties":false,"description":"The latest attempt to publish this channel and the content approved for it.","allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["status","connection_id","snapshot"],"properties":{"status":{"$ref":"#/components/schemas/AdminMarketingCampaignPublicationStatus"},"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID"},"published_at":{"type":"string","format":"date-time","description":"Absent until the platform confirms publication."},"scheduled_at":{"type":"string","format":"date-time","description":"When this publication is due to run. Absent on an immediate publication."},"external_post_id":{"type":"string","minLength":1,"description":"The platform's post identifier, when known."},"post_url":{"type":"string","format":"uri","description":"The public post address, when the platform supplies one."},"error_message":{"type":"string","minLength":1,"description":"A staff-safe explanation of the latest publication failure or uncertainty."},"snapshot":{"$ref":"#/components/schemas/AdminMarketingCampaignPublicationSnapshot"},"facebook_link_preview":{"$ref":"#/components/schemas/AdminMarketingFacebookLinkPreview"}}}]},"AdminMarketingCampaignChannel":{"type":"object","additionalProperties":false,"description":"One platform's own post: the finished files it publishes, and the text and call to action that go out beside them. Publishing combines its saved copy with the campaign link in a caption; it does not generate copy or creative files.","required":["platform","asset_ids"],"properties":{"platform":{"$ref":"#/components/schemas/AdminMarketingChannelPlatform"},"asset_ids":{"type":"array","items":{"$ref":"#/components/schemas/MarketingCampaignAssetID"},"uniqueItems":true,"maxItems":24,"description":"This platform's assets, in the order staff arranged them. The sequence belongs to the channel; assets are added and removed through the channel's own asset operations, never here."},"body":{"type":"string","minLength":1,"maxLength":3000,"description":"The text posted with the assets. Absent on a platform that carries none, never an empty string."},"call_to_action":{"type":"string","minLength":1,"maxLength":40,"description":"The label on the post's call to action, such as `Learn more`. It links to the campaign's `link_url`. Absent when the post carries none."},"publishing_target":{"$ref":"#/components/schemas/AdminMarketingCampaignPublishingTarget","description":"The connected account selected for this channel. Absent when none is selected. Omitting it from a replacement channel clears the selection."},"youtube":{"$ref":"#/components/schemas/AdminMarketingCampaignYouTube","description":"The video's title and audience disclosures for YouTube. Absent when none are saved. Omitting it from a replacement channel clears these settings."},"publication":{"$ref":"#/components/schemas/AdminMarketingCampaignPublication","readOnly":true,"description":"The latest publication attempt. Absent while the channel is a draft before its first attempt."},"reddit":{"$ref":"#/components/schemas/AdminMarketingCampaignReddit"},"email":{"$ref":"#/components/schemas/AdminMarketingCampaignEmail","description":"The subject, sender, and audience for the Bird email channel. Absent when none are saved. Omitting it from a replacement channel clears these settings."}}},"AdminMarketingCampaignChannels":{"type":"array","uniqueItems":true,"description":"The per-platform posts of this campaign. A request containing more than one entry for the same platform is rejected. A platform in `platforms` with no entry here has nothing drafted for it yet; an entry for a platform outside `platforms` is dropped. Returned in the platform vocabulary's own order, not the order it was sent in.","items":{"$ref":"#/components/schemas/AdminMarketingCampaignChannel"}},"AdminMarketingCampaignStatus":{"type":"string","minLength":1,"enum":["draft","scheduled","publishing","active","published","needs_attention","failed"],"description":"Where the campaign stands, rolled up from the latest publication of every platform in `platforms`. A platform with no channel yet, no publication yet, or a withdrawn schedule counts as a draft, so it holds the campaign short of `published`. `draft` has nothing pending anywhere. `scheduled` has at least one channel waiting for its publication time and none posted. `active` has at least one confirmed post and at least one channel still to go. `published` has a confirmed post on every platform. `publishing` has an attempt in flight. `failed` and `needs_attention` outrank the rest: they are the states that stop the campaign until staff act, and they report the worst result any channel returned."},"AdminMarketingCampaign":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","title","brief","link_url","platforms","tags","channels","utm","status"],"properties":{"metrics":{"$ref":"#/components/schemas/AdminMarketingCampaignMetrics","description":"Opt-in enrichment for campaign cards, batched across the requested list page to share publication and attribution reads. Item reads and mutation responses omit it to avoid these additional aggregate queries; the editor uses the separate date-bounded analytics endpoint."},"utm":{"$ref":"#/components/schemas/AdminMarketingCampaignUTM"},"id":{"$ref":"#/components/schemas/MarketingCampaignID"},"title":{"type":"string","minLength":1,"description":"What the campaign is about, as staff refer to it."},"brief":{"type":"string","minLength":1,"description":"The campaign brief, as Markdown."},"link_url":{"type":["string","null"],"format":"uri","description":"Where the campaign sends people. Null when it links nowhere."},"platforms":{"$ref":"#/components/schemas/AdminMarketingCampaignPlatforms"},"tags":{"$ref":"#/components/schemas/AdminMarketingCampaignTags"},"channels":{"$ref":"#/components/schemas/AdminMarketingCampaignChannels"},"status":{"$ref":"#/components/schemas/AdminMarketingCampaignStatus"}}}]},"AdminMarketingCampaignList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingCampaign"},"description":"The matching campaigns."}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminMarketingCampaignCreate":{"type":"object","additionalProperties":false,"required":["title","brief"],"properties":{"utm":{"$ref":"#/components/schemas/AdminMarketingCampaignUTM","description":"Link tracking configuration. Omit it to leave links unchanged (Off)."},"title":{"type":"string","minLength":1,"maxLength":200,"description":"What the campaign is about, as staff refer to it."},"brief":{"type":"string","minLength":1,"maxLength":50000,"description":"The campaign brief, as Markdown."},"link_url":{"type":"string","format":"uri","pattern":"^https://","minLength":1,"maxLength":512,"description":"Where the campaign sends people. Omit it for a campaign that links nowhere."},"platforms":{"$ref":"#/components/schemas/AdminMarketingCampaignPlatforms"},"tags":{"$ref":"#/components/schemas/AdminMarketingCampaignTags","description":"Labels to group this campaign by. Omit it for an untagged campaign."}}},"AdminMarketingCampaignAnalyticsStatus":{"type":"string","minLength":1,"description":"Collection status for a published post: pending waits for its first collection; ready has collected metrics; partial has usable metrics but some values or historical reports are unavailable, so check analytics permissions; reconnect_required needs renewed consent from the original publishing account; unavailable means the platform or post cannot currently provide analytics, so check the connection and post availability; error means collection failed temporarily and will retry automatically while retaining earlier observations.\n","enum":["pending","ready","partial","reconnect_required","unavailable","error"]},"AdminMarketingCampaignAnalyticsPoint":{"type":"object","additionalProperties":false,"required":["date","collected_at","metrics"],"properties":{"date":{"type":"string","minLength":1,"format":"date","description":"Observation date for snapshots, or platform reporting date for daily activity."},"collected_at":{"type":"string","minLength":1,"format":"date-time","description":"Time Bird collected these values."},"metrics":{"type":"object","description":"Available metrics keyed by name. Missing values are absent; zero is a reported zero. Values may decrease and signed values are preserved. Unless stated otherwise, units are counts. X supplies impressions, likes, comments (replies), reposts, quotes, saved (bookmarks), link_clicks, profile_clicks, engagements, organic_impressions, organic_likes, organic_comments, organic_reposts, organic_link_clicks, and organic_profile_clicks. Instagram supplies likes, comments, views, reach (unique accounts), saved, and shares. Threads supplies views, likes, comments (replies), reposts, quotes, and shares. TikTok supplies views, likes, comments, and shares. YouTube snapshots supply views, likes, and comments; daily reports also supply shares, watch_minutes (minutes watched) and average_view_seconds (mean viewing duration in seconds). LinkedIn supplies likes, comments, organic_impressions, organic_reach (unique impressions), organic_likes, organic_comments, organic_clicks, and organic_shares. Facebook supplies reactions, comments, and shares. Reddit supplies comments and score (the platform's net vote score, not likes). Organic-prefixed metrics exclude paid activity; other metrics follow the platform's combined or public reporting scope. Permissions, post format, and provider reporting windows determine which keys are present.\n","additionalProperties":{"type":"number","format":"double"}}}},"AdminMarketingCampaignAnalyticsSeries":{"type":"object","additionalProperties":false,"required":["platform","status","reporting_timezone","snapshots","daily"],"properties":{"platform":{"$ref":"#/components/schemas/AdminMarketingChannelPlatform"},"post_url":{"type":"string","format":"uri","minLength":1,"description":"Published post URL when supplied by the platform; otherwise absent."},"status":{"$ref":"#/components/schemas/AdminMarketingCampaignAnalyticsStatus"},"message":{"type":"string","minLength":1,"description":"Collection availability or recovery guidance, when needed."},"last_synced_at":{"type":"string","minLength":1,"format":"date-time","description":"Time of the last successful counter observation. Absent before the first observation."},"reporting_timezone":{"type":"string","minLength":1,"description":"IANA timezone of reported daily activity. Snapshots always use UTC."},"snapshots":{"type":"array","description":"Latest successful total-counter observation on each UTC date. Dates without an observation are absent.","items":{"$ref":"#/components/schemas/AdminMarketingCampaignAnalyticsPoint"}},"daily":{"type":"array","description":"Provider-reported daily activity. These are not total counters or differences between observed counters.","items":{"$ref":"#/components/schemas/AdminMarketingCampaignAnalyticsPoint"}}}},"AdminMarketingCampaignAttributionStatus":{"type":"string","minLength":1,"description":"Attribution availability for a campaign: off means no published post carries a utm_campaign tag, so nothing can be attributed and turning on UTM tags only affects posts published afterwards; pending means published posts carry a tag but no signup or inquiry matched it inside the requested window, which says nothing about earlier ones; tracked means matches were found in that window; unavailable means this deployment cannot read the acquisition ledger, so the absence of counts carries no information about the campaign.\n","enum":["off","pending","tracked","unavailable"]},"AdminMarketingCampaignAttributionModel":{"type":"string","minLength":1,"description":"Which visit a signup or inquiry is credited to. first credits the visit that first brought the person to Bird. last credits the visit saved as their most recent one, which falls back to the first touch when the supplied last touch was missing, invalid or earlier than first, so last can name the same visit as first; either can be direct.\n","enum":["first","last"]},"AdminMarketingCampaignAttributionCounts":{"type":"object","additionalProperties":false,"required":["signups","business_signups","organizations","inquiries"],"properties":{"signups":{"type":"integer","format":"int64","minimum":0,"description":"New accounts whose attributed visit carried the campaign tag."},"business_signups":{"type":"integer","format":"int64","minimum":0,"description":"Signups whose email address is not a personal mailbox provider."},"organizations":{"type":"integer","format":"int64","minimum":0,"description":"Attributed signups that went on to create their first workspace."},"inquiries":{"type":"integer","format":"int64","minimum":0,"description":"Contact sales submissions carrying the campaign tag."}}},"AdminMarketingCampaignAttributionPoint":{"type":"object","additionalProperties":false,"required":["date","counts"],"properties":{"date":{"type":"string","minLength":1,"format":"date","description":"UTC date the signup or inquiry was recorded."},"counts":{"$ref":"#/components/schemas/AdminMarketingCampaignAttributionCounts"}}},"AdminMarketingCampaignAttributionPlatform":{"type":"object","additionalProperties":false,"required":["platform","utm_campaign","utm_source","totals","daily"],"properties":{"platform":{"$ref":"#/components/schemas/AdminMarketingChannelPlatform"},"utm_campaign":{"type":"string","minLength":1,"description":"The utm_campaign value frozen into this platform's published link."},"utm_source":{"type":"string","minLength":0,"description":"The utm_source value frozen into this platform's published link. Empty when the link carries no source, in which case this platform reports no counts of its own."},"totals":{"$ref":"#/components/schemas/AdminMarketingCampaignAttributionCounts"},"daily":{"type":"array","description":"Counts per UTC date. Dates without a signup or inquiry are absent.","items":{"$ref":"#/components/schemas/AdminMarketingCampaignAttributionPoint"}}}},"AdminMarketingCampaignAttribution":{"type":"object","additionalProperties":false,"required":["status","model","campaign_totals","campaign_daily","platforms"],"properties":{"status":{"$ref":"#/components/schemas/AdminMarketingCampaignAttributionStatus"},"model":{"$ref":"#/components/schemas/AdminMarketingCampaignAttributionModel"},"campaign_totals":{"$ref":"#/components/schemas/AdminMarketingCampaignAttributionCounts"},"campaign_daily":{"type":"array","description":"Campaign-wide counts per UTC date, counting every source that carried a campaign tag.","items":{"$ref":"#/components/schemas/AdminMarketingCampaignAttributionPoint"}},"platforms":{"type":"array","description":"Counts for each published platform, matched on the utm_source its published link carries. These do not sum to the campaign totals: a shared link keeps the campaign tag while the visitor's source is recorded as the site that referred them.\n","items":{"$ref":"#/components/schemas/AdminMarketingCampaignAttributionPlatform"}}}},"AdminMarketingCampaignAnalytics":{"type":"object","additionalProperties":false,"required":["data","attribution"],"properties":{"data":{"type":"array","description":"Analytics for each published platform post.","items":{"$ref":"#/components/schemas/AdminMarketingCampaignAnalyticsSeries"}},"attribution":{"$ref":"#/components/schemas/AdminMarketingCampaignAttribution"}}},"AdminMarketingCampaignChannelWrite":{"type":"object","additionalProperties":false,"description":"One platform's own post as a write states it. Separate from the read schema because `asset_ids` is optional here and guaranteed there: only the caller that owns the order sends one, and omitting it leaves the stored order alone.","required":["platform"],"properties":{"platform":{"$ref":"#/components/schemas/AdminMarketingChannelPlatform"},"asset_ids":{"type":"array","items":{"$ref":"#/components/schemas/MarketingCampaignAssetID"},"uniqueItems":true,"maxItems":24,"description":"This platform's assets, in the order staff arranged them. The sequence belongs to the channel; assets are added and removed through the channel's own asset operations, never here. Omit it to leave the stored order alone, which is what a caller saving only copy does; a concurrent upload or reorder would otherwise be undone by a stale permutation."},"body":{"type":"string","minLength":1,"maxLength":3000,"description":"The text posted with the assets. A channel entry replaces the whole post, so omitting this clears any stored text rather than leaving it: send it on every write that means to keep it. Never an empty string."},"call_to_action":{"type":"string","minLength":1,"maxLength":40,"description":"The label on the post's call to action, such as `Learn more`. It links to the campaign's `link_url`. Like `body`, omitting this clears a stored label rather than leaving it."},"publishing_target":{"$ref":"#/components/schemas/AdminMarketingCampaignPublishingTarget","description":"The connected account selected for this channel. Omitting it from a replacement channel clears the selection; send it on every write that means to keep it."},"youtube":{"$ref":"#/components/schemas/AdminMarketingCampaignYouTube","description":"The video's title and audience disclosures for YouTube. Omitting it from a replacement channel clears these settings; send them on every write that means to keep them."},"reddit":{"$ref":"#/components/schemas/AdminMarketingCampaignReddit"},"email":{"$ref":"#/components/schemas/AdminMarketingCampaignEmail","description":"The subject, sender, and audience for the Bird email channel. Omitting it from a replacement channel clears these settings; send them on every write that means to keep them."}}},"AdminMarketingCampaignChannelWrites":{"type":"array","uniqueItems":true,"description":"The per-platform posts a write states. A request containing more than one entry for the same platform is rejected. A platform in `platforms` with no entry here has nothing drafted for it yet; an entry for a platform outside `platforms` is dropped. Returned in the platform vocabulary's own order, not the order it was sent in.","items":{"$ref":"#/components/schemas/AdminMarketingCampaignChannelWrite"}},"AdminMarketingCampaignUpdate":{"type":"object","additionalProperties":false,"description":"Only the fields present are changed.","properties":{"utm":{"$ref":"#/components/schemas/AdminMarketingCampaignUTM","description":"Replaces the complete link tracking configuration. Include manual values to retain them; omit utm to preserve the saved configuration."},"title":{"type":"string","minLength":1,"maxLength":200,"description":"What the campaign is about, as staff refer to it."},"brief":{"type":"string","minLength":1,"maxLength":50000,"description":"The campaign brief, as Markdown."},"link_url":{"type":["string","null"],"format":"uri","pattern":"^https://","minLength":1,"maxLength":512,"description":"Where the campaign sends people. Null clears it; omitting it leaves the stored link alone."},"platforms":{"$ref":"#/components/schemas/AdminMarketingCampaignPlatforms"},"tags":{"$ref":"#/components/schemas/AdminMarketingCampaignTags","description":"Replaces the whole tag set. Omit it to leave the stored tags alone; send an empty array to clear them."},"channels":{"$ref":"#/components/schemas/AdminMarketingCampaignChannelWrites","description":"The per-platform posts. Present replaces the whole set; omitting it keeps the posts for platforms that remain selected. A simultaneous `platforms` update deletes the posts for deselected platforms. There is no per-channel merge. Removing a channel retires the assets it held, so a platform that loses its entry starts empty if it is drafted again. A channel's `asset_ids` must list every asset already on that platform exactly once. It reorders, and never adds or removes."}}},"AdminMarketingCampaignChannelPublish":{"type":"object","additionalProperties":false,"required":["expected_updated_at"],"properties":{"expected_updated_at":{"type":"string","format":"date-time","minLength":1,"description":"The campaign's updated_at from the version reviewed for publication. A stale value returns 409."},"scheduled_at":{"type":"string","format":"date-time","description":"Publish at this time instead of now, between a minute and a year ahead. The channel is frozen until it fires; cancel the publication to edit it. Omit to publish immediately."}}},"AdminMarketingCampaignChannelPublicationCheck":{"type":"object","additionalProperties":false,"properties":{"external_post_id":{"type":"string","minLength":1,"maxLength":512,"description":"A candidate post identifier to verify when an earlier publication result was uncertain."}}},"AdminMarketingCampaignEmailPreviewRequest":{"type":"object","additionalProperties":false,"description":"The email content to render. It is the editor's own state rather than the saved channel, so an unsaved edit previews; apply UTM tags before sending, the same way the network previews do.","properties":{"subject":{"$ref":"#/components/schemas/AdminMarketingCampaignEmailSubject"},"heading":{"$ref":"#/components/schemas/AdminMarketingCampaignEmailHeading"},"preheader":{"$ref":"#/components/schemas/AdminMarketingCampaignEmailPreheader"},"body":{"type":"string","maxLength":3000,"description":"The authored body. A blank line starts a new paragraph; nothing else is interpreted."},"call_to_action":{"type":"string","maxLength":40},"link_url":{"type":"string","format":"uri","maxLength":2048,"description":"Where the call to action links, with UTM tags already applied."},"asset_id":{"$ref":"#/components/schemas/MarketingCampaignAssetID","description":"A campaign asset from this Bird email channel to render as the hero image."}}},"AdminMarketingCampaignEmailPreview":{"type":"object","additionalProperties":false,"required":["subject","html","text"],"description":"The Bird email channel as it would send.","properties":{"subject":{"type":"string","minLength":0,"description":"The subject line, as authored."},"preheader":{"type":"string","description":"The preview text, as authored. Absent when the channel saves none."},"html":{"type":"string","minLength":1,"description":"The rendered HTML body. Render it in a sandboxed frame: it is authored staff content, and the admin app is not its trust boundary."},"text":{"type":"string","minLength":0,"description":"The plain-text alternative the same render produces."}}},"AdminMarketingCampaignEmailTestRequest":{"type":"object","additionalProperties":false,"required":["connection_id","from","subject"],"description":"The email content to send as a test, and the connected Bird workspace to send it from. The content is the editor's own state rather than the saved channel, so an unsaved edit can be tested; apply UTM tags before sending, the same way the preview does. The recipient is not a field here: it is the signed-in staff user's own address.","properties":{"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID","description":"The Bird workspace connection the test sends through."},"from":{"type":"string","format":"email","minLength":3,"maxLength":254,"description":"The sender address, which has to sit on a domain the connected workspace verified. A plain address, without a display name."},"from_name":{"type":"string","maxLength":64,"description":"The display name shown beside the sender address."},"reply_to":{"type":"string","format":"email","maxLength":254,"description":"Where replies go, when the channel names a mailbox other than the sender."},"subject":{"type":"string","minLength":1,"maxLength":255,"description":"The subject line. A test send requires one, where the preview renders a draft that has none yet."},"heading":{"$ref":"#/components/schemas/AdminMarketingCampaignEmailHeading"},"preheader":{"$ref":"#/components/schemas/AdminMarketingCampaignEmailPreheader"},"body":{"type":"string","maxLength":3000,"description":"The authored body. A blank line starts a new paragraph; nothing else is interpreted."},"call_to_action":{"type":"string","maxLength":40},"link_url":{"type":"string","format":"uri","maxLength":2048,"description":"Where the call to action links, with UTM tags already applied."},"asset_id":{"$ref":"#/components/schemas/MarketingCampaignAssetID","description":"A campaign asset from this Bird email channel to render as the hero image."}}},"AdminMarketingCampaignEmailTest":{"type":"object","additionalProperties":false,"required":["recipient","message_id"],"description":"The accepted test message.","properties":{"recipient":{"type":"string","format":"email","minLength":3,"description":"The address the test went to, which is the signed-in staff user's own."},"message_id":{"type":"string","minLength":1,"description":"The connected workspace's identifier for the accepted message. Delivery is asynchronous, so this is acceptance rather than arrival."}}},"AdminMarketingCampaignAsset":{"description":"One finished creative on a marketing campaign's platform.","unevaluatedProperties":false,"allOf":[{"type":"object","required":["id","campaign_id","platform","kind","filename","content_type","size_bytes","created_at"],"properties":{"id":{"$ref":"#/components/schemas/MarketingCampaignAssetID"},"campaign_id":{"$ref":"#/components/schemas/MarketingCampaignID"},"platform":{"$ref":"#/components/schemas/AdminMarketingChannelPlatform"},"kind":{"$ref":"#/components/schemas/AdminMarketingAssetKind"},"filename":{"type":"string","minLength":1,"maxLength":255,"description":"The name the file was uploaded under, or `asset` when the upload carried no usable name."},"content_type":{"type":"string","minLength":1,"readOnly":true,"description":"The stored file's media type, determined from its contents. A declared type is ignored. An image is re-encoded on upload, so this can differ from the type the uploaded file claimed; a video is stored byte for byte.","example":"image/png"},"size_bytes":{"type":"integer","format":"int64","readOnly":true,"description":"The size of the stored file. For an image, after re-encoding."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the asset was uploaded."}}}]},"AdminMarketingCampaignAssetList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingCampaignAsset"},"description":"The platform's assets, in the order the channel arranges them. The whole set is returned, so there is no pagination."}}},"AdminMarketingCampaignAssetUpload":{"description":"The file to upload, sent as a `multipart/form-data` body.","type":"object","additionalProperties":false,"required":["file"],"properties":{"file":{"type":"string","format":"binary","minLength":1,"description":"The creative to upload: a PNG, JPEG, GIF or still WebP image up to 5 MB, or an MP4, WebM or QuickTime video up to 25 MB. The file's contents determine its type and a declared content type is ignored. Every image is re-encoded: embedded descriptive metadata is stripped, while animated GIF timing, loop count, disposal, and per-frame behavior are preserved. Animated WebP is rejected; use still WebP or an animated GIF instead. A video is stored byte for byte."}}},"CreativeID":{"type":"string","minLength":1,"pattern":"^mcrt_[0-9a-hjkmnp-tv-z]{26}$","example":"mcrt_01krdgeqcxet5s7t44vh8rt9mg"},"AdminMarketingCreativeMetadata":{"type":"object","additionalProperties":false,"required":["source_key","product_key","name","language","angle","cut","version","copy","landing_url","evidence"],"properties":{"source_key":{"type":"string","minLength":1},"product_key":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"language":{"type":"string","minLength":1},"angle":{"type":"string","minLength":1},"cut":{"type":"string","minLength":1},"version":{"type":"string","minLength":1},"copy":{"type":"string","minLength":0},"landing_url":{"type":"string","minLength":1,"format":"uri","pattern":"^https://","description":"Absolute HTTPS landing URL with a hostname and no user information."},"evidence":{"type":"string","minLength":0}}},"CreativeExportID":{"type":"string","minLength":1,"pattern":"^mexp_[0-9a-hjkmnp-tv-z]{26}$","example":"mexp_01krdgeqcxet5s7t44vh8rt9mg"},"AdminMarketingCreativeExport":{"type":"object","additionalProperties":false,"required":["id","filename","content_type","sha256","size_bytes","width","height","download_url","created_at"],"properties":{"id":{"$ref":"#/components/schemas/CreativeExportID"},"filename":{"type":"string","minLength":1},"content_type":{"type":"string","minLength":1},"sha256":{"type":"string","minLength":1},"size_bytes":{"type":"integer","format":"int64"},"width":{"type":"integer"},"height":{"type":"integer"},"duration_millis":{"type":"integer","format":"int64"},"download_url":{"type":"string","minLength":1,"format":"uri","description":"Temporary URL for the unchanged original file, valid for fifteen minutes."},"created_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminMarketingCreative":{"type":"object","additionalProperties":false,"required":["id","metadata","exports","created_at"],"properties":{"id":{"$ref":"#/components/schemas/CreativeID"},"metadata":{"$ref":"#/components/schemas/AdminMarketingCreativeMetadata"},"exports":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingCreativeExport"}},"created_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminMarketingCreativeList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingCreative"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminMarketingCreativeUpload":{"type":"object","additionalProperties":false,"required":["metadata","file"],"properties":{"metadata":{"$ref":"#/components/schemas/AdminMarketingCreativeMetadata"},"file":{"type":"string","minLength":1,"format":"binary"}}},"MarketingStillID":{"type":"string","minLength":1,"pattern":"^mstl_[0-9a-hjkmnp-tv-z]{26}$","example":"mstl_01krdgeqcxet5s7t44vh8rt9mg"},"AdminMarketingStill":{"description":"One reusable image in the marketing stills library.","unevaluatedProperties":false,"allOf":[{"type":"object","required":["id","name","filename","content_type","size_bytes"],"properties":{"id":{"$ref":"#/components/schemas/MarketingStillID"},"name":{"type":"string","minLength":1,"maxLength":120,"description":"The label staff refer to the still by. Defaults to the uploaded file's name without its extension."},"filename":{"type":"string","minLength":1,"maxLength":255,"description":"The name the file was uploaded under, or `still` when the upload carried no usable name."},"content_type":{"type":"string","minLength":1,"readOnly":true,"description":"The stored image's media type, determined from its contents. A declared type is ignored, and an image is re-encoded on upload, so this can differ from the type the uploaded file claimed.","example":"image/png"},"size_bytes":{"type":"integer","format":"int64","readOnly":true,"description":"The size of the stored image, after re-encoding."},"download_url":{"type":["string","null"],"format":"uri","readOnly":true,"description":"A signed URL for reading the image, valid for minutes rather than hours. Use it immediately and do not cache it. Signing is local, so this costs no round trip per still and is not a check that the object is still there: a still whose bytes were reclaimed carries a URL that fails.","example":"https://marketing-assets.s3.eu-west-1.amazonaws.com/marketing/stills/01kr97b2xq7m8ce5z40wxdfd12.png?X-Amz-Signature=6a9f2c4e8b1d"}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminMarketingStillList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingStill"},"description":"The matching stills."}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminMarketingStillUpload":{"description":"The image to add to the library, sent as a `multipart/form-data` body.","type":"object","additionalProperties":false,"required":["file"],"properties":{"file":{"type":"string","format":"binary","minLength":1,"description":"The image to upload: PNG, JPEG, GIF or still WebP, up to 25 MB. The file's contents determine its type and a declared content type is ignored. Every image is re-encoded: embedded descriptive metadata is stripped, while animated GIF timing, loop count, disposal, and per-frame behavior are preserved. Animated WebP is rejected; use still WebP or an animated GIF instead."},"name":{"type":"string","maxLength":120,"description":"The label to file the still under. Omit the part, or send a blank one, to take the uploaded file's name without its extension; a file with no usable name is filed as `Still`. Multipart bodies are not schema-validated, so this fallback is the contract rather than a rejection."}}},"AdminMarketingStillUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"description":"Only the fields present are changed. At least one must be present: an empty body is refused rather than silently restamping the still.","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"The label staff refer to the still by."}}},"AdminAdvertisingPlatform":{"type":"string","minLength":1,"enum":["google_ads","reddit_ads","tiktok_ads","x_ads"],"description":"Advertising platform."},"MarketingAdvertisingCampaignID":{"type":"string","minLength":1,"pattern":"^mac_[0-9a-hjkmnp-tv-z]{26}$","example":"mac_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAdvertisingTargeting":{"type":"object","additionalProperties":false,"description":"Advertising Targeting details.","required":["interest_i_ds","geography_i_ds","excluded_geography_i_ds","language_i_ds","audience_i_ds","excluded_audience_i_ds"],"properties":{"interest_i_ds":{"type":"array","items":{"type":"string","minLength":0},"description":"Interest i ds."},"geography_i_ds":{"type":"array","items":{"type":"string","minLength":0},"description":"Geography i ds."},"excluded_geography_i_ds":{"type":"array","items":{"type":"string","minLength":0},"description":"Excluded geography i ds."},"language_i_ds":{"type":"array","items":{"type":"string","minLength":0},"description":"Language i ds."},"audience_i_ds":{"type":"array","items":{"type":"string","minLength":0},"description":"Audience i ds."},"excluded_audience_i_ds":{"type":"array","items":{"type":"string","minLength":0},"description":"Excluded audience i ds."}}},"AdminAdvertisingGoogleCampaign":{"type":"object","additionalProperties":false,"description":"Advertising GoogleCampaign details.","required":["targeting"],"properties":{"bid_strategy":{"type":"string","minLength":0,"description":"Bid strategy.","x-go-type-skip-optional-pointer":true},"targeting":{"$ref":"#/components/schemas/AdminAdvertisingTargeting","description":"Targeting."},"daily_budget":{"$ref":"#/components/schemas/Money","description":"Daily budget in the selected advertising account currency. Omit while unconfigured. Publishing requires a positive amount with at most six fractional digits, no greater than 9223372036854.775807, and a currency matching the account."}}},"AdminAdvertisingXCampaign":{"type":"object","additionalProperties":false,"description":"Advertising XCampaign details.","required":[],"properties":{"funding_instrument_id":{"type":"string","minLength":0,"description":"The X funding instrument the campaign spends against. Publishing requires it; the ad lookups expose the instruments the connected account holds.","x-go-type-skip-optional-pointer":true},"daily_budget":{"$ref":"#/components/schemas/Money","description":"Daily budget in the selected advertising account currency. Omit while unconfigured. Publishing requires a positive amount with at most six fractional digits, no greater than 9223372036854.775807, and a currency matching the account."},"total_budget":{"$ref":"#/components/schemas/Money","description":"Optional total spend cap in the advertising account currency. Must be positive and no smaller than the daily budget."}}},"AdminAdvertisingCampaignVariant":{"type":"object","additionalProperties":false,"description":"Advertising CampaignVariant details.","required":["platform"],"properties":{"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform","description":"Platform."},"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID","description":"Connection selected for this platform. May be omitted in a draft; required when publishing."},"external_account_id":{"type":"string","minLength":0,"description":"Advertising account selected for this platform. May be omitted in a draft; required when publishing.","x-go-type-skip-optional-pointer":true},"google":{"$ref":"#/components/schemas/AdminAdvertisingGoogleCampaign","description":"Google."},"x":{"$ref":"#/components/schemas/AdminAdvertisingXCampaign","description":"X."}}},"MarketingAdvertisingGroupID":{"type":"string","minLength":1,"pattern":"^mag_[0-9a-hjkmnp-tv-z]{26}$","example":"mag_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAdvertisingKeyword":{"type":"object","additionalProperties":false,"description":"Advertising Keyword details.","required":[],"properties":{"text":{"type":"string","minLength":0,"description":"Text.","x-go-type-skip-optional-pointer":true},"match_type":{"type":"string","minLength":0,"description":"Match type.","x-go-type-skip-optional-pointer":true}}},"AdminAdvertisingGoogleGroup":{"type":"object","additionalProperties":false,"description":"Advertising GoogleGroup details.","required":["keywords","excluded_keywords"],"properties":{"keywords":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingKeyword"},"description":"Keywords."},"excluded_keywords":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingKeyword"},"description":"Excluded keywords."},"cpc_bid":{"$ref":"#/components/schemas/Money","description":"Maximum cost-per-click bid in the selected advertising account currency. Omit while unconfigured. Publishing requires a positive amount with at most six fractional digits, no greater than 9223372036854.775807, and a currency matching the account."}}},"AdminAdvertisingRedditGroup":{"type":"object","additionalProperties":false,"description":"Advertising RedditGroup details.","required":["community_names","excluded_community_names"],"properties":{"start_at":{"type":"string","format":"date-time","description":"Start at.","minLength":0},"end_at":{"type":"string","format":"date-time","description":"End at.","minLength":0},"bid_strategy":{"type":"string","minLength":0,"description":"Bid strategy.","x-go-type-skip-optional-pointer":true},"community_names":{"type":"array","items":{"type":"string","minLength":0},"description":"Community names."},"excluded_community_names":{"type":"array","items":{"type":"string","minLength":0},"description":"Excluded community names."},"pixel_id":{"type":"string","minLength":0,"description":"Pixel id.","x-go-type-skip-optional-pointer":true},"daily_budget":{"$ref":"#/components/schemas/Money","description":"Daily budget in the selected advertising account currency. Omit while unconfigured. Publishing requires a positive amount with at most six fractional digits, no greater than 9223372036854.775807, and a currency matching the account."},"bid":{"$ref":"#/components/schemas/Money","description":"Maximum cost-per-click bid in the selected advertising account currency. Omit while unconfigured. Publishing requires a positive amount with at most six fractional digits, no greater than 9223372036854.775807, and a currency matching the account."}}},"AdminAdvertisingTikTokGroup":{"type":"object","additionalProperties":false,"description":"Advertising TikTokGroup details.","required":["age_groups","placement_i_ds"],"properties":{"bid_strategy":{"type":"string","minLength":0,"description":"Bid strategy.","x-go-type-skip-optional-pointer":true},"pixel_id":{"type":"string","minLength":0,"description":"Pixel id.","x-go-type-skip-optional-pointer":true},"gender":{"type":"string","minLength":0,"description":"Gender.","x-go-type-skip-optional-pointer":true},"age_groups":{"type":"array","items":{"type":"string","minLength":0},"description":"Age groups."},"start_at":{"type":"string","format":"date-time","description":"Start at.","minLength":0},"end_at":{"type":"string","format":"date-time","description":"End at.","minLength":0},"placement_i_ds":{"type":"array","items":{"type":"string","minLength":0},"description":"Placement i ds."},"daily_budget":{"$ref":"#/components/schemas/Money","description":"Daily budget in the selected advertising account currency. Omit while unconfigured. Publishing requires a positive amount with at most six fractional digits, no greater than 9223372036854.775807, and a currency matching the account."},"bid":{"$ref":"#/components/schemas/Money","description":"Maximum cost-per-click bid in the selected advertising account currency. Omit while unconfigured. Publishing requires a positive amount with at most six fractional digits, no greater than 9223372036854.775807, and a currency matching the account."}}},"AdminAdvertisingXPlacement":{"type":"string","minLength":1,"enum":["ALL_ON_TWITTER","PUBLISHER_NETWORK","TWITTER_PROFILE","TWITTER_REPLIES","TWITTER_SEARCH","TWITTER_TIMELINE"],"description":"Where on X an ad may serve."},"AdminAdvertisingXGroup":{"type":"object","additionalProperties":false,"description":"Advertising XGroup details.","required":["placements"],"properties":{"objective":{"type":"string","minLength":0,"description":"What X optimizes the line item for, such as engagements or website clicks. Publishing requires it.","x-go-type-skip-optional-pointer":true},"placements":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingXPlacement"},"description":"Where on X the ad may serve. An empty set means every placement on X."},"bid_strategy":{"type":"string","minLength":0,"description":"Bid strategy.","x-go-type-skip-optional-pointer":true},"start_at":{"type":"string","format":"date-time","description":"Start at.","minLength":0},"end_at":{"type":"string","format":"date-time","description":"End at.","minLength":0},"web_event_tag_id":{"type":"string","minLength":0,"description":"Web event tag id.","x-go-type-skip-optional-pointer":true},"bid":{"$ref":"#/components/schemas/Money","description":"Maximum bid per billable action in the selected advertising account currency. Omit while unconfigured. Publishing requires a positive amount with at most six fractional digits, no greater than 9223372036854.775807, and a currency matching the account."}}},"AdminAdvertisingGroupVariant":{"type":"object","additionalProperties":false,"description":"Advertising GroupVariant details.","required":["platform","targeting"],"properties":{"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform","description":"Platform."},"targeting":{"$ref":"#/components/schemas/AdminAdvertisingTargeting","description":"Targeting."},"google":{"$ref":"#/components/schemas/AdminAdvertisingGoogleGroup","description":"Google."},"reddit":{"$ref":"#/components/schemas/AdminAdvertisingRedditGroup","description":"Reddit."},"tik_tok":{"$ref":"#/components/schemas/AdminAdvertisingTikTokGroup","description":"Tik tok."},"x":{"$ref":"#/components/schemas/AdminAdvertisingXGroup","description":"X."}}},"MarketingAdvertisingAdID":{"type":"string","minLength":1,"pattern":"^mad_[0-9a-hjkmnp-tv-z]{26}$","example":"mad_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAdvertisingGoogleAd":{"type":"object","additionalProperties":false,"description":"Advertising GoogleAd details.","required":["headlines","descriptions"],"properties":{"headlines":{"type":"array","items":{"type":"string","minLength":0},"description":"Headlines."},"descriptions":{"type":"array","items":{"type":"string","minLength":0},"description":"Descriptions."},"path1":{"type":"string","minLength":0,"description":"Path1.","x-go-type-skip-optional-pointer":true},"path2":{"type":"string","minLength":0,"description":"Path2.","x-go-type-skip-optional-pointer":true}}},"MarketingAdvertisingAssetID":{"type":"string","minLength":1,"pattern":"^maa_[0-9a-hjkmnp-tv-z]{26}$","example":"maa_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAdvertisingRedditAd":{"type":"object","additionalProperties":false,"description":"Advertising RedditAd details.","required":[],"properties":{"profile_id":{"type":"string","minLength":0,"description":"Profile id.","x-go-type-skip-optional-pointer":true},"promoted_campaign_id":{"$ref":"#/components/schemas/MarketingCampaignID","description":"Promote the Reddit post this marketing campaign already published, instead of creating a new post for the ad. It is the only property a promoted variant carries: `propertyNames` refuses `profile_id`, `format`, `headline`, `body`, `call_to_action`, `asset_id` and `thumbnail_asset_id` beside it, because the post exists and is not Bird's to rewrite. The post itself stays owned by the marketing campaign; publishing resolves its identifier then, so a campaign with no published Reddit post is reported by the publish check rather than refused here."},"format":{"type":"string","minLength":0,"description":"Format.","x-go-type-skip-optional-pointer":true},"headline":{"type":"string","minLength":0,"description":"Headline.","x-go-type-skip-optional-pointer":true},"body":{"type":"string","minLength":0,"description":"Body.","x-go-type-skip-optional-pointer":true},"call_to_action":{"type":"string","minLength":0,"description":"Call to action.","x-go-type-skip-optional-pointer":true},"asset_id":{"$ref":"#/components/schemas/MarketingAdvertisingAssetID","description":"Asset id."},"thumbnail_asset_id":{"$ref":"#/components/schemas/MarketingAdvertisingAssetID","description":"Thumbnail asset id."}},"dependentSchemas":{"promoted_campaign_id":{"propertyNames":{"enum":["promoted_campaign_id"]}}}},"AdminAdvertisingTikTokAd":{"type":"object","additionalProperties":false,"description":"Advertising TikTokAd details.","required":[],"properties":{"text":{"type":"string","minLength":0,"description":"Text.","x-go-type-skip-optional-pointer":true},"call_to_action":{"type":"string","minLength":0,"description":"Call to action.","x-go-type-skip-optional-pointer":true},"identity_id":{"type":"string","minLength":0,"description":"Identity id.","x-go-type-skip-optional-pointer":true},"identity_type":{"type":"string","minLength":0,"description":"Identity type.","x-go-type-skip-optional-pointer":true},"asset_id":{"$ref":"#/components/schemas/MarketingAdvertisingAssetID","description":"Asset id."},"thumbnail_asset_id":{"$ref":"#/components/schemas/MarketingAdvertisingAssetID","description":"Thumbnail asset id."}}},"AdminAdvertisingXAd":{"type":"object","additionalProperties":false,"description":"Advertising XAd details.","required":[],"properties":{"promoted_campaign_id":{"$ref":"#/components/schemas/MarketingCampaignID","description":"Promote the post this marketing campaign already published on X, instead of creating a new post for the ad. It is the only property a promoted variant carries: `propertyNames` refuses `text` beside it, because the post exists and is not Bird's to rewrite. The post itself stays owned by the marketing campaign; publishing resolves its identifier then, so a campaign with no published X post is reported by the publish check rather than refused here."},"text":{"type":"string","minLength":0,"maxLength":256,"x-go-type-skip-optional-pointer":true,"description":"At publish time, text must contain a non-whitespace character and at most 256 Unicode code points. The post text for an ad X publishes as a promoted-only post, which serves as an ad without appearing on the timeline or reaching followers. Bird appends the destination URL, which X shortens to 23 characters, so 256 is what remains of X's 280. An authored X ad carries no media and no website card: X uploads media through a separate host and expresses a call to action through its cards resource, neither of which this surface reaches, so accepting either field would drop it silently. Omit this on a variant that promotes an already-published post."}},"dependentSchemas":{"promoted_campaign_id":{"propertyNames":{"enum":["promoted_campaign_id"]}}}},"AdminAdvertisingAdVariant":{"type":"object","additionalProperties":false,"description":"Advertising AdVariant details.","required":["platform"],"properties":{"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform","description":"Platform."},"google":{"$ref":"#/components/schemas/AdminAdvertisingGoogleAd","description":"Google."},"reddit":{"$ref":"#/components/schemas/AdminAdvertisingRedditAd","description":"Reddit."},"tik_tok":{"$ref":"#/components/schemas/AdminAdvertisingTikTokAd","description":"Tik tok."},"x":{"$ref":"#/components/schemas/AdminAdvertisingXAd","description":"X."}}},"AdminAdvertisingAd":{"type":"object","additionalProperties":false,"description":"Advertising Ad details.","required":["name","platforms"],"properties":{"id":{"$ref":"#/components/schemas/MarketingAdvertisingAdID","description":"Id."},"name":{"type":"string","minLength":1,"description":"Name."},"destination_url":{"type":"string","minLength":0,"description":"Destination url.","x-go-type-skip-optional-pointer":true},"platforms":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingAdVariant"},"description":"Platforms."}}},"AdminAdvertisingGroup":{"type":"object","additionalProperties":false,"description":"Advertising Group details.","required":["name","platforms","ads"],"properties":{"id":{"$ref":"#/components/schemas/MarketingAdvertisingGroupID","description":"Id."},"name":{"type":"string","minLength":1,"description":"Name."},"platforms":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingGroupVariant"},"description":"Platforms."},"ads":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingAd"},"description":"Ads."}}},"AdminAdvertisingDraft":{"type":"object","additionalProperties":false,"description":"Advertising Draft details.","required":["name","platforms","groups"],"properties":{"name":{"type":"string","minLength":1,"description":"Name."},"brief":{"type":"string","minLength":0,"description":"Brief.","x-go-type-skip-optional-pointer":true},"destination_url":{"type":"string","minLength":0,"description":"Destination url.","x-go-type-skip-optional-pointer":true},"platforms":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingCampaignVariant"},"description":"Platforms."},"groups":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingGroup"},"description":"Groups."}}},"AdminAdvertisingReconciliationTarget":{"type":"object","additionalProperties":false,"description":"Advertising ReconciliationTarget details.","required":["platform"],"properties":{"key":{"type":"string","minLength":0,"description":"Key.","x-go-type-skip-optional-pointer":true},"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform","description":"Platform."},"resource_kind":{"type":"string","minLength":0,"description":"Resource kind.","x-go-type-skip-optional-pointer":true},"local_id":{"type":"string","minLength":0,"description":"Local id.","x-go-type-skip-optional-pointer":true},"state":{"type":"string","minLength":0,"description":"State.","x-go-type-skip-optional-pointer":true}}},"AdminAdvertisingReplacement":{"type":"object","additionalProperties":false,"description":"Advertising Replacement details.","required":["platform"],"properties":{"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform","description":"Platform."},"resource_kind":{"type":"string","minLength":0,"description":"Resource kind.","x-go-type-skip-optional-pointer":true},"local_id":{"type":"string","minLength":0,"description":"Local id.","x-go-type-skip-optional-pointer":true},"external_id":{"type":"string","minLength":0,"description":"External id.","x-go-type-skip-optional-pointer":true},"reason":{"type":"string","minLength":0,"description":"Reason.","x-go-type-skip-optional-pointer":true}}},"MarketingAdvertisingOperationID":{"type":"string","minLength":1,"pattern":"^mao_[0-9a-hjkmnp-tv-z]{26}$","example":"mao_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAdvertisingAction":{"type":"string","minLength":1,"enum":["publish","activate","pause","archive","reconcile","launch"],"description":"Requested provider action."},"AdminAdvertisingIssue":{"type":"object","additionalProperties":false,"description":"Advertising Issue details.","properties":{"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform","description":"Platform."},"path":{"type":"string","minLength":0,"description":"Path.","x-go-type-skip-optional-pointer":true},"code":{"type":"string","minLength":0,"description":"Code.","x-go-type-skip-optional-pointer":true},"message":{"type":"string","minLength":0,"description":"Message.","x-go-type-skip-optional-pointer":true}}},"AdminAdvertisingAccount":{"type":"object","additionalProperties":false,"description":"Advertising Account details.","required":["connection_id","platform","actions","issues"],"properties":{"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID","description":"Connection id."},"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform","description":"Platform."},"external_account_id":{"type":"string","minLength":0,"description":"External account id.","x-go-type-skip-optional-pointer":true},"name":{"type":"string","minLength":0,"description":"Name.","x-go-type-skip-optional-pointer":true},"currency_code":{"type":"string","minLength":0,"description":"Currency code.","x-go-type-skip-optional-pointer":true},"timezone":{"type":"string","minLength":0,"description":"Timezone.","x-go-type-skip-optional-pointer":true},"actions":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingAction"},"description":"Actions."},"issues":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingIssue"},"description":"Issues."}}},"AdminAdvertisingSnapshot":{"type":"object","additionalProperties":false,"description":"Advertising Snapshot details.","required":["reconciliation_targets","replacements","campaign_id","revision","draft","accounts"],"properties":{"reconciliation_targets":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingReconciliationTarget"},"description":"Reconciliation targets."},"replacements":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingReplacement"},"description":"Replacements."},"source_operation_id":{"$ref":"#/components/schemas/MarketingAdvertisingOperationID","description":"Source operation id."},"target_kind":{"type":"string","minLength":0,"description":"Target kind.","x-go-type-skip-optional-pointer":true},"target_id":{"type":"string","minLength":0,"description":"Target id.","x-go-type-skip-optional-pointer":true},"campaign_id":{"$ref":"#/components/schemas/MarketingAdvertisingCampaignID","description":"Campaign id."},"revision":{"type":"integer","format":"int64","description":"Revision."},"draft":{"$ref":"#/components/schemas/AdminAdvertisingDraft","description":"Draft."},"accounts":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingAccount"},"description":"Accounts."}}},"AdminAdvertisingAdoption":{"type":"object","additionalProperties":false,"description":"Records that a campaign was adopted from a pre-existing external campaign. When the import is not lossy, write-back stays blocked until it is verified. A lossy import is read-only: it holds no managed state, write-back is permanently unavailable, and it can never be verified.","required":["source_platform","source_external_account_id","source_external_id","verified","lossy"],"properties":{"source_platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform","description":"Platform the campaign was adopted from."},"source_external_account_id":{"type":"string","description":"External ad account the campaign was adopted from.","x-go-type-skip-optional-pointer":true,"minLength":1},"source_external_id":{"type":"string","description":"External campaign id the draft was reconstructed from.","x-go-type-skip-optional-pointer":true,"minLength":1},"verified":{"type":"boolean","description":"Whether a reconcile has confirmed the reconstructed draft matches live state. Meaningful only when lossy is false, where write-back is blocked until this becomes true. When lossy is true this is always false and can never become true, because a lossy import is read-only and verification never runs."},"verified_at":{"type":"string","format":"date-time","description":"When verification last succeeded."},"lossy":{"type":"boolean","description":"True when Bird could not fully represent the live campaign, so it is imported read-only with no managed state. Write-back (publish, activate, pause, archive) is then permanently unavailable and verified is always false; verification cannot run, so it can never be unblocked. Delete and re-import is the only recovery path."}}},"AdminAdvertisingFact":{"type":"object","additionalProperties":false,"description":"Advertising Fact details.","required":["retired","platform","observed_at"],"properties":{"retired":{"type":"boolean","description":"Retired."},"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform","description":"Platform."},"resource_kind":{"type":"string","minLength":0,"description":"Resource kind.","x-go-type-skip-optional-pointer":true},"local_id":{"type":"string","minLength":0,"description":"Local id.","x-go-type-skip-optional-pointer":true},"external_id":{"type":"string","minLength":0,"description":"External id.","x-go-type-skip-optional-pointer":true},"parent_external_id":{"type":"string","minLength":0,"description":"Parent external id.","x-go-type-skip-optional-pointer":true},"status":{"type":"string","minLength":0,"description":"Status.","x-go-type-skip-optional-pointer":true},"review_status":{"type":"string","minLength":0,"description":"Review status.","x-go-type-skip-optional-pointer":true},"desired_status":{"type":"string","minLength":0,"description":"Desired status.","x-go-type-skip-optional-pointer":true},"delivery_status":{"type":"string","minLength":0,"description":"Delivery status.","x-go-type-skip-optional-pointer":true},"draft_fingerprint":{"type":"string","minLength":0,"description":"Draft fingerprint.","x-go-type-skip-optional-pointer":true},"fingerprint":{"type":"string","minLength":0,"description":"Fingerprint.","x-go-type-skip-optional-pointer":true},"observed_at":{"type":"string","format":"date-time","description":"Observed at.","minLength":1}}},"AdminAdvertisingCampaign":{"type":"object","additionalProperties":false,"description":"Advertising Campaign details.","required":["id","revision","draft","facts","created_by_user_id","updated_by_user_id","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/MarketingAdvertisingCampaignID","description":"Id."},"revision":{"type":"integer","format":"int64","description":"Revision."},"draft":{"$ref":"#/components/schemas/AdminAdvertisingDraft","description":"Draft."},"published":{"$ref":"#/components/schemas/AdminAdvertisingSnapshot","description":"Published."},"adoption":{"$ref":"#/components/schemas/AdminAdvertisingAdoption","description":"Set when the campaign was adopted from an external campaign; write-back is gated until verified."},"facts":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingFact"},"description":"Facts."},"created_by_user_id":{"$ref":"#/components/schemas/UserID","description":"Created by user id."},"updated_by_user_id":{"$ref":"#/components/schemas/UserID","description":"Updated by user id."},"created_at":{"type":"string","format":"date-time","description":"Created at.","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","description":"Updated at.","minLength":1,"readOnly":true}}},"AdminAdvertisingCampaignList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingCampaign"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminAdvertisingImportRequest":{"type":"object","additionalProperties":false,"description":"Identifies an external campaign to preview or adopt.","required":["platform","connection_id","external_account_id","external_campaign_id"],"properties":{"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform","description":"Platform the campaign lives on."},"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID","description":"Connection holding consent for the ad account."},"external_account_id":{"type":"string","minLength":1,"description":"External ad account id.","x-go-type-skip-optional-pointer":true},"external_campaign_id":{"type":"string","minLength":1,"description":"External campaign id to adopt.","x-go-type-skip-optional-pointer":true}}},"AdminAdvertisingImportResult":{"type":"object","additionalProperties":false,"description":"Result of adopting an external campaign: the new draft campaign reconstructed from live provider state.","required":["campaign_id"],"properties":{"campaign_id":{"$ref":"#/components/schemas/MarketingAdvertisingCampaignID","description":"The adopted campaign."}}},"AdminAdvertisingImportGap":{"type":"object","additionalProperties":false,"description":"An external field the reconstruction could not represent as a Bird draft; a load-bearing gap keeps verification from passing.","required":["path","detail"],"properties":{"external_id":{"type":"string","description":"External resource the gap belongs to.","x-go-type-skip-optional-pointer":true},"path":{"type":"string","description":"Field path that could not be mapped.","x-go-type-skip-optional-pointer":true,"minLength":1},"detail":{"type":"string","description":"What was found and why it was not imported.","x-go-type-skip-optional-pointer":true,"minLength":1}}},"AdminAdvertisingImportPreview":{"type":"object","additionalProperties":false,"description":"The Bird draft reconstructed from an external campaign, for staff review before adoption.","required":["draft","unmapped"],"properties":{"draft":{"$ref":"#/components/schemas/AdminAdvertisingDraft","description":"Reconstructed draft that adoption would create."},"unmapped":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingImportGap"},"description":"External fields that could not be represented in the draft."}}},"AdminAdvertisingImportSummary":{"type":"object","additionalProperties":false,"description":"A campaign that already exists in a connected external ad account, offered for adoption.","required":["external_id","name"],"properties":{"external_id":{"type":"string","description":"External campaign id.","x-go-type-skip-optional-pointer":true,"minLength":1},"name":{"type":"string","description":"Campaign name as it reads on the platform.","x-go-type-skip-optional-pointer":true,"minLength":1},"status":{"type":"string","description":"Delivery status as reported by the platform.","x-go-type-skip-optional-pointer":true},"objective":{"type":"string","description":"Campaign objective as reported by the platform.","x-go-type-skip-optional-pointer":true},"budget_summary":{"type":"string","description":"Human-readable budget summary.","x-go-type-skip-optional-pointer":true}}},"AdminAdvertisingUpdate":{"type":"object","additionalProperties":false,"required":["revision","draft"],"properties":{"revision":{"type":"integer","format":"int64","minimum":1},"draft":{"$ref":"#/components/schemas/AdminAdvertisingDraft"}}},"AdminAdvertisingCampaignTargetKind":{"type":"string","minLength":1,"enum":["campaign"],"description":"Select the campaign hierarchy level."},"AdminAdvertisingCampaignTarget":{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"$ref":"#/components/schemas/AdminAdvertisingCampaignTargetKind"}},"description":"Target the campaign for this operation."},"AdminAdvertisingGroupTargetKind":{"type":"string","minLength":1,"enum":["group"],"description":"Select the group hierarchy level."},"AdminAdvertisingGroupTarget":{"type":"object","additionalProperties":false,"required":["kind","group_id"],"properties":{"kind":{"$ref":"#/components/schemas/AdminAdvertisingGroupTargetKind"},"group_id":{"$ref":"#/components/schemas/MarketingAdvertisingGroupID"}},"description":"Target the group for this operation."},"AdminAdvertisingAdTargetKind":{"type":"string","minLength":1,"enum":["ad"],"description":"Select the ad hierarchy level."},"AdminAdvertisingAdTarget":{"type":"object","additionalProperties":false,"required":["kind","ad_id"],"properties":{"kind":{"$ref":"#/components/schemas/AdminAdvertisingAdTargetKind"},"ad_id":{"$ref":"#/components/schemas/MarketingAdvertisingAdID"}},"description":"Target the ad for this operation."},"AdminAdvertisingTarget":{"oneOf":[{"$ref":"#/components/schemas/AdminAdvertisingCampaignTarget"},{"$ref":"#/components/schemas/AdminAdvertisingGroupTarget"},{"$ref":"#/components/schemas/AdminAdvertisingAdTarget"}],"discriminator":{"propertyName":"kind","mapping":{"campaign":"#/components/schemas/AdminAdvertisingCampaignTarget","group":"#/components/schemas/AdminAdvertisingGroupTarget","ad":"#/components/schemas/AdminAdvertisingAdTarget"}},"description":"Typed selection within the campaign hierarchy."},"AdminAdvertisingCheck":{"type":"object","additionalProperties":false,"required":["revision"],"properties":{"revision":{"type":"integer","format":"int64","minimum":1},"target":{"$ref":"#/components/schemas/AdminAdvertisingTarget","description":"Optional hierarchy target; omission selects the campaign identified by the request path."}}},"AdminAdvertisingCheckResult":{"type":"object","additionalProperties":false,"required":["snapshot","issues"],"properties":{"snapshot":{"$ref":"#/components/schemas/AdminAdvertisingSnapshot"},"issues":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingIssue"}}}},"AdminAdvertisingOperationState":{"type":"string","minLength":1,"enum":["pending","running","succeeded","failed","needs_attention","reconciling","reconciled"],"description":"Durable operation lifecycle state; needs_attention requires reconciliation before another provider mutation."},"AdminAdvertisingStepState":{"type":"string","minLength":1,"enum":["pending","started","succeeded","failed","needs_attention"],"description":"Durable step lifecycle state; needs_attention requires reconciliation before another provider mutation."},"AdminAdvertisingStepResult":{"type":"object","additionalProperties":false,"description":"Advertising StepResult details.","required":["pending","facts","needs_attention"],"properties":{"pending":{"type":"boolean","description":"Pending."},"facts":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingFact"},"description":"Facts."},"needs_attention":{"type":"boolean","description":"Needs attention."},"message":{"type":"string","minLength":0,"description":"Message.","x-go-type-skip-optional-pointer":true}}},"AdminAdvertisingStep":{"type":"object","additionalProperties":false,"description":"Advertising Step details.","required":["operation_id","platform","action","facts","state"],"properties":{"replaces_external_id":{"type":"string","minLength":0,"description":"Replaces external id.","x-go-type-skip-optional-pointer":true},"replacement_reason":{"type":"string","minLength":0,"description":"Replacement reason.","x-go-type-skip-optional-pointer":true},"key":{"type":"string","minLength":0,"description":"Key.","x-go-type-skip-optional-pointer":true},"operation_id":{"$ref":"#/components/schemas/MarketingAdvertisingOperationID","description":"Operation id."},"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform","description":"Platform."},"action":{"$ref":"#/components/schemas/AdminAdvertisingAction","description":"Action."},"resource_kind":{"type":"string","minLength":0,"description":"Resource kind.","x-go-type-skip-optional-pointer":true},"local_id":{"type":"string","minLength":0,"description":"Local id.","x-go-type-skip-optional-pointer":true},"state":{"$ref":"#/components/schemas/AdminAdvertisingStepState","description":"Durable step lifecycle state."},"facts":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingFact"},"description":"Facts."},"result":{"$ref":"#/components/schemas/AdminAdvertisingStepResult","description":"Result."}}},"AdminAdvertisingOperation":{"type":"object","additionalProperties":false,"description":"Advertising Operation details.","required":["id","campaign_id","action","snapshot","steps","actor_user_id","created_at","updated_at","state"],"properties":{"id":{"$ref":"#/components/schemas/MarketingAdvertisingOperationID","description":"Id."},"campaign_id":{"$ref":"#/components/schemas/MarketingAdvertisingCampaignID","description":"Campaign id."},"action":{"$ref":"#/components/schemas/AdminAdvertisingAction","description":"Action."},"state":{"$ref":"#/components/schemas/AdminAdvertisingOperationState","description":"Durable operation lifecycle state."},"idempotency_key":{"type":"string","minLength":0,"description":"Idempotency key.","x-go-type-skip-optional-pointer":true},"snapshot":{"$ref":"#/components/schemas/AdminAdvertisingSnapshot","description":"Snapshot."},"steps":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingStep"},"description":"Steps."},"actor_user_id":{"$ref":"#/components/schemas/UserID","description":"Actor user id."},"created_at":{"type":"string","format":"date-time","description":"Created at.","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","description":"Updated at.","minLength":1,"readOnly":true}}},"AdminAdvertisingOperationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingOperation"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminAdvertisingRequestedAction":{"type":"string","minLength":1,"enum":["publish","activate","pause","archive","reconcile"],"description":"Action accepted by the advertising operation endpoint; promotion launches use the promotion endpoint."},"AdminAdvertisingOperationCreate":{"type":"object","additionalProperties":false,"required":["revision","action","idempotency_key"],"properties":{"confirm_replacements":{"type":"boolean","description":"Acknowledge the paused replacements shown by publish-check for immutable provider fields; required when the proposed operation replaces resources."},"revision":{"type":"integer","format":"int64","minimum":1,"description":"Current campaign revision, including for child operations; a stale revision or changed provider state returns a conflict."},"action":{"$ref":"#/components/schemas/AdminAdvertisingRequestedAction","description":"Publish creates or updates resources paused; activate enables delivery separately and respects child pause intent; reconcile investigates an unresolved operation."},"idempotency_key":{"type":"string","minLength":1,"maxLength":200},"target":{"$ref":"#/components/schemas/AdminAdvertisingTarget","description":"Optional hierarchy target; omission selects the campaign identified by the request path."}}},"AdminAdvertisingPromotionObjective":{"type":"string","minLength":1,"enum":["ENGAGEMENTS","WEBSITE_CLICKS"],"description":"Whether to optimize for interactions with the tweet or visits to its website destination."},"AdminAdvertisingPromotionLaunch":{"type":"object","additionalProperties":false,"description":"Launch settings for a published X post.","required":["marketing_campaign_id","connection_id","external_account_id","funding_instrument_id","objective","total_budget","end_at","country_ids","idempotency_key"],"properties":{"marketing_campaign_id":{"$ref":"#/components/schemas/MarketingCampaignID","description":"Campaign containing the confirmed published X post to promote."},"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID","description":"X Ads connection used to access the selected advertising account."},"external_account_id":{"type":"string","minLength":1,"maxLength":200,"description":"X Ads account returned by advertising account discovery."},"funding_instrument_id":{"type":"string","minLength":1,"maxLength":200,"description":"Eligible funding instrument belonging to the selected X Ads account."},"objective":{"$ref":"#/components/schemas/AdminAdvertisingPromotionObjective"},"total_budget":{"$ref":"#/components/schemas/Money","description":"Total spending cap in the advertising account’s currency; also used as the daily ceiling."},"end_at":{"type":"string","format":"date-time","description":"Time when delivery ends; must be in the future when the promotion is first accepted.","minLength":1},"country_ids":{"type":"array","minItems":1,"maxItems":250,"uniqueItems":true,"items":{"type":"string","minLength":1,"maxLength":200},"description":"X country targeting identifiers returned by the country lookup for the selected account."},"idempotency_key":{"type":"string","minLength":1,"maxLength":200,"description":"Retry key matching the Idempotency-Key header; reuse with unchanged settings to retrieve the accepted operation."}}},"AdminAdvertisingGroupRecord":{"type":"object","additionalProperties":false,"description":"Advertising GroupRecord details.","required":["campaign_id","revision","group","created_at"],"properties":{"campaign_id":{"$ref":"#/components/schemas/MarketingAdvertisingCampaignID","description":"Campaign id."},"revision":{"type":"integer","format":"int64","description":"Revision."},"group":{"$ref":"#/components/schemas/AdminAdvertisingGroup","description":"Group."},"created_at":{"type":"string","format":"date-time","description":"Created at.","minLength":1,"readOnly":true}}},"AdminAdvertisingGroupRecordList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingGroupRecord"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminAdvertisingGroupSave":{"type":"object","additionalProperties":false,"required":["campaign_id","revision","group"],"properties":{"campaign_id":{"$ref":"#/components/schemas/MarketingAdvertisingCampaignID"},"revision":{"type":"integer","format":"int64","minimum":1},"group":{"$ref":"#/components/schemas/AdminAdvertisingGroup"}}},"AdminAdvertisingAdRecord":{"type":"object","additionalProperties":false,"description":"Advertising AdRecord details.","required":["campaign_id","group_id","revision","ad","created_at"],"properties":{"campaign_id":{"$ref":"#/components/schemas/MarketingAdvertisingCampaignID","description":"Campaign id."},"group_id":{"$ref":"#/components/schemas/MarketingAdvertisingGroupID","description":"Group id."},"revision":{"type":"integer","format":"int64","description":"Revision."},"ad":{"$ref":"#/components/schemas/AdminAdvertisingAd","description":"Ad."},"created_at":{"type":"string","format":"date-time","description":"Created at.","minLength":1,"readOnly":true}}},"AdminAdvertisingAdRecordList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAdvertisingAdRecord"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminAdvertisingAdSave":{"type":"object","additionalProperties":false,"required":["group_id","revision","ad"],"properties":{"group_id":{"$ref":"#/components/schemas/MarketingAdvertisingGroupID"},"revision":{"type":"integer","format":"int64","minimum":1},"ad":{"$ref":"#/components/schemas/AdminAdvertisingAd"}}},"AdminAdvertisingLookupLevel":{"type":"string","minLength":1,"enum":["country","region","city","other"],"description":"Normalized geographic granularity of a resolved location. Provider vocabularies differ, so each adapter maps its own target type onto these four levels; other covers a targetable place that is not a country, a region, or a city, such as a postal code or an airport."},"AdminAdvertisingLookup":{"type":"object","additionalProperties":false,"description":"Advertising Lookup details.","required":[],"properties":{"id":{"type":"string","minLength":0,"description":"Id.","x-go-type-skip-optional-pointer":true},"name":{"type":"string","minLength":0,"description":"Name.","x-go-type-skip-optional-pointer":true},"country_code":{"$ref":"#/components/schemas/CountryCode","description":"Country that contains this location. Absent when the provider reports no country for it, which happens for a metro or region it does not place.","x-go-type-skip-optional-pointer":true},"level":{"$ref":"#/components/schemas/AdminAdvertisingLookupLevel","description":"Granularity of this location.","x-go-type-skip-optional-pointer":true}}},"AdminAdvertisingAssetUpload":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"filename":{"type":"string","maxLength":200,"minLength":0,"x-go-type-skip-optional-pointer":true},"data":{"type":"string","format":"byte","maxLength":139810136,"minLength":1}}},"AdminAdvertisingAsset":{"type":"object","additionalProperties":false,"description":"Advertising Asset details.","required":["width","height","duration_millis","id","size_bytes","created_by_user_id","created_at"],"properties":{"width":{"type":"integer","format":"int64","description":"Width."},"height":{"type":"integer","format":"int64","description":"Height."},"duration_millis":{"type":"integer","format":"int64","description":"Duration millis."},"id":{"$ref":"#/components/schemas/MarketingAdvertisingAssetID","description":"Id."},"filename":{"type":"string","minLength":0,"description":"Filename.","x-go-type-skip-optional-pointer":true},"content_type":{"type":"string","minLength":0,"description":"Content type.","x-go-type-skip-optional-pointer":true},"size_bytes":{"type":"integer","format":"int64","description":"Size bytes."},"created_by_user_id":{"$ref":"#/components/schemas/UserID","description":"Created by user id."},"created_at":{"type":"string","format":"date-time","description":"Created at.","minLength":1,"readOnly":true}}},"AdminAdvertisingAssetURL":{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","format":"uri","minLength":1,"description":"Temporary signed download URL, valid for one hour after issuance; request a fresh URL after it expires."}}},"AdminMarketingAdvertisingMetricKind":{"type":"string","minLength":1,"enum":["campaign","group","ad"],"description":"The advertising resource level. Levels are reported independently."},"AdminMarketingAdvertisingMetric":{"type":"object","additionalProperties":false,"required":["platform","resource_kind","local_id","external_id","day","currency_code","reporting_timezone","impressions","clicks","spend","fetched_at"],"properties":{"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform"},"resource_kind":{"$ref":"#/components/schemas/AdminMarketingAdvertisingMetricKind"},"local_id":{"type":"string","minLength":1,"description":"Bird advertising campaign, group, or ad identifier."},"external_id":{"type":"string","minLength":1,"description":"The provider resource identifier for this metric."},"day":{"type":"string","minLength":1,"format":"date","description":"Calendar day in the account reporting timezone."},"currency_code":{"type":"string","minLength":1,"description":"Currency published by the advertising account."},"reporting_timezone":{"type":"string","minLength":1,"description":"Provider account timezone used for day boundaries."},"impressions":{"type":"integer","format":"int64"},"clicks":{"type":"integer","format":"int64"},"spend":{"type":"string","minLength":1,"description":"Exact decimal spend in the account currency."},"fetched_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminMarketingAdvertisingMetricRun":{"type":"object","additionalProperties":false,"required":["platform","pending"],"properties":{"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform"},"pending":{"type":"boolean","description":"A report refresh has been requested."},"last_success_at":{"type":"string","minLength":1,"format":"date-time"},"last_error":{"type":"string","minLength":1,"x-go-type-skip-optional-pointer":true,"description":"Safe diagnostic for the latest report failure, empty after success."}}},"AdminMarketingAdvertisingMetricTotal":{"type":"object","additionalProperties":false,"required":["platform","currency_code","impressions","clicks","spend"],"properties":{"platform":{"$ref":"#/components/schemas/AdminAdvertisingPlatform"},"currency_code":{"type":"string","minLength":1,"description":"Published advertising account currency."},"impressions":{"type":"integer","format":"int64"},"clicks":{"type":"integer","format":"int64"},"spend":{"type":"string","minLength":1,"description":"Exact sum of spend for this platform and currency."}}},"AdminMarketingAdvertisingMetricList":{"type":"object","additionalProperties":false,"required":["data","runs","totals"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdvertisingMetric"}},"runs":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdvertisingMetricRun"}},"totals":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdvertisingMetricTotal"}}}},"AdminAcquisitionKind":{"type":"string","minLength":1,"description":"The event that created this acquisition.","enum":["signup","inquiry"]},"AdminAcquisitionConsent":{"type":"string","minLength":1,"description":"Whether valid consented attribution was present at acquisition.","enum":["granted","unknown"]},"AdminAcquisitionDeliveryStatus":{"type":"string","minLength":1,"description":"CRM delivery state. Failed inquiries retry daily until expiry; not_applicable covers signups and excluded inquiries.","enum":["not_applicable","pending","retrying","delivered","failed"]},"AcquisitionID":{"type":"string","minLength":1,"pattern":"^acq_[0-9a-hjkmnp-tv-z]{26}$","example":"acq_01krdgeqcxet5s7t44vh8rt9mg"},"AdminAcquisitionTouch":{"type":"object","additionalProperties":false,"required":["source"],"properties":{"source":{"type":"string","minLength":1},"medium":{"type":"string","minLength":1},"campaign":{"type":"string","minLength":1},"campaign_id":{"type":"string","minLength":1},"adgroup_id":{"type":"string","minLength":1},"content":{"type":"string","minLength":1},"term":{"type":"string","minLength":1},"landing_path":{"type":"string","minLength":1},"locale":{"type":"string","minLength":1},"referrer_host":{"type":"string","minLength":1},"captured_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminAcquisitionSignals":{"type":"object","additionalProperties":false,"required":["verified_domain","paid_plan","sent_real","updated_at"],"properties":{"name":{"type":"string","minLength":1},"domain":{"type":"string","minLength":1},"plan":{"type":"string","minLength":1},"verified_domain":{"type":"boolean"},"paid_plan":{"type":"boolean"},"sent_real":{"type":"boolean"},"updated_at":{"type":"string","format":"date-time","minLength":1}}},"DemoConversationStatus":{"type":"string","minLength":1,"enum":["open","qualifying","question","handoff"],"description":"`open` is the initial state after acceptance. `qualifying` gathers demo requirements. `question` awaits the requester's question. `handoff` stops automation and leaves the conversation with sales."},"AdminAcquisition":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["kind","email","consent","delivery_status","id","business_email","products","first_touch","last_touch"],"properties":{"kind":{"$ref":"#/components/schemas/AdminAcquisitionKind"},"name":{"type":"string","minLength":1},"email":{"format":"email","type":"string","minLength":1},"phone_number":{"type":"string","minLength":1},"volume":{"type":"string","minLength":1},"source_detail":{"type":"string","minLength":1,"description":"Page or campaign submitted with a sales inquiry. This contact-form field is distinct from consented browser attribution."},"exclusion_reason":{"type":"string","minLength":1},"consent":{"$ref":"#/components/schemas/AdminAcquisitionConsent"},"delivery_status":{"$ref":"#/components/schemas/AdminAcquisitionDeliveryStatus"},"id":{"$ref":"#/components/schemas/AcquisitionID"},"user_id":{"$ref":"#/components/schemas/UserID"},"organization_id":{"$ref":"#/components/schemas/OrganizationID"},"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"business_email":{"description":"Email domain is outside the personal-provider list; this does not verify the company.","type":"boolean"},"products":{"type":"array","items":{"type":"string","minLength":1}},"first_touch":{"$ref":"#/components/schemas/AdminAcquisitionTouch"},"last_touch":{"$ref":"#/components/schemas/AdminAcquisitionTouch"},"organization_created_at":{"type":"string","minLength":1,"format":"date-time"},"crm_delivered_at":{"type":"string","minLength":1,"format":"date-time"},"crm_lead_id":{"type":"string","minLength":1},"crm_person_id":{"type":"string","minLength":1},"signals":{"$ref":"#/components/schemas/AdminAcquisitionSignals"},"demo_conversation_status":{"$ref":"#/components/schemas/DemoConversationStatus","description":"Conversation state on an individual acquisition read. Omitted from list responses and when no unexpired demo conversation is available."},"demo_phone_suppressed":{"type":"boolean","description":"Whether automated phone follow-up has stopped. Populated only on an individual acquisition read with an unexpired demo conversation."}}}]},"AdminAcquisitionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAcquisition"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminAcquisitionCampaign":{"type":"object","additionalProperties":false,"required":["source","signups","organizations","business_signups","inquiries","crm_leads","verified_domains","paid_plans","production_senders","observed_organizations"],"properties":{"source":{"type":"string","minLength":1},"medium":{"type":"string","minLength":1},"campaign":{"type":"string","minLength":1},"campaign_id":{"type":"string","minLength":1},"signups":{"type":"integer","format":"int64"},"organizations":{"description":"Initial organizations linked to signups in this acquisition cohort.","type":"integer","format":"int64"},"organization_ids":{"description":"Unique organizations credited to their founding signup in this cohort. Present only when include_organization_ids is true. Conflicting or incomplete founding links are withheld. Later inquiries and member associations do not receive customer acquisition credit.","type":"array","items":{"$ref":"#/components/schemas/OrganizationID"}},"unresolved_organization_ids":{"description":"Linked organizations whose founding signup cannot be uniquely established across retained acquisitions. Present only when include_organization_ids is true. These IDs can recur across cohorts and must not be assigned funding credit.","type":"array","items":{"$ref":"#/components/schemas/OrganizationID"}},"business_signups":{"type":"integer","format":"int64"},"inquiries":{"type":"integer","format":"int64"},"crm_leads":{"description":"Inquiries with confirmed CRM lead delivery.","type":"integer","format":"int64"},"verified_domains":{"description":"Organizations with a currently verified domain in the Growth snapshot.","type":"integer","format":"int64"},"paid_plans":{"description":"Organizations with a currently paid plan in the Growth snapshot.","type":"integer","format":"int64"},"production_senders":{"description":"Organizations that have sent email from their own domain in the Growth snapshot.","type":"integer","format":"int64"},"observed_organizations":{"description":"Cohort organizations with an available Growth snapshot.","type":"integer","format":"int64"},"signals_updated_at":{"description":"Oldest available Growth snapshot in this cohort.","type":"string","minLength":1,"format":"date-time"}}},"AdminAcquisitionReport":{"type":"object","additionalProperties":false,"required":["verified_domains","paid_plans","production_senders","observed_organizations","signups","organizations","business_signups","inquiries","crm_leads","attributed","excluded","campaigns","generated_at","timezone"],"properties":{"signups":{"type":"integer","format":"int64"},"organizations":{"description":"Initial organizations linked to signups in this acquisition cohort.","type":"integer","format":"int64"},"business_signups":{"type":"integer","format":"int64"},"inquiries":{"type":"integer","format":"int64"},"crm_leads":{"description":"Inquiries with confirmed CRM lead delivery.","type":"integer","format":"int64"},"attributed":{"description":"Signups and inquiries with a known source under the selected attribution model.","type":"integer","format":"int64"},"excluded":{"description":"Internal and test records matching this period and the other filters.","type":"integer","format":"int64"},"campaigns":{"type":"array","items":{"$ref":"#/components/schemas/AdminAcquisitionCampaign"}},"tracking_started_at":{"description":"Earliest retained acquisition across the ledger, independent of report filters.","type":"string","minLength":1,"format":"date-time"},"last_received_at":{"description":"Latest retained acquisition across the ledger, independent of report filters.","type":"string","minLength":1,"format":"date-time"},"generated_at":{"type":"string","minLength":1,"format":"date-time"},"timezone":{"type":"string","minLength":1},"verified_domains":{"description":"Organizations with a currently verified domain in the Growth snapshot.","type":"integer","format":"int64"},"paid_plans":{"description":"Organizations with a currently paid plan in the Growth snapshot.","type":"integer","format":"int64"},"production_senders":{"description":"Organizations that have sent email from their own domain in the Growth snapshot.","type":"integer","format":"int64"},"observed_organizations":{"description":"Cohort organizations with an available Growth snapshot.","type":"integer","format":"int64"},"signals_updated_at":{"description":"Oldest available Growth snapshot in this cohort.","type":"string","minLength":1,"format":"date-time"}}},"AdminMarketingPlatform":{"type":"string","minLength":1,"enum":["linkedin","x","instagram","tiktok","youtube","facebook","reddit","threads","google_ads","bird"],"description":"A social network, a search advertising platform, or Bird itself, that a marketing campaign or connection names. Closed: a write is checked against this set, and the admin app ships with the backend, so a new network reaches both at once.","example":"linkedin"},"AdminMarketingAdDailyMetric":{"type":"object","additionalProperties":false,"required":["connection_id","platform","external_account_id","day","external_campaign_id","campaign_name","campaign_status","currency_code","impressions","clicks","spend","conversions","conversion_value","fetched_at"],"properties":{"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID"},"platform":{"$ref":"#/components/schemas/AdminMarketingPlatform"},"external_account_id":{"type":"string","minLength":1},"day":{"type":"string","minLength":1,"format":"date"},"external_campaign_id":{"type":"string","minLength":1},"campaign_name":{"type":"string","minLength":1},"campaign_status":{"type":"string","minLength":1},"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"impressions":{"type":"integer","format":"int64"},"clicks":{"type":"integer","format":"int64"},"spend":{"type":"string","minLength":1},"conversions":{"type":"string","minLength":1},"conversion_value":{"type":"string","minLength":1},"fetched_at":{"type":"string","minLength":1,"format":"date-time"}}},"AdminMarketingAdCurrencyTotal":{"type":"object","additionalProperties":false,"required":["currency_code","impressions","clicks","spend","conversions","conversion_value"],"properties":{"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"impressions":{"type":"integer","format":"int64"},"clicks":{"type":"integer","format":"int64"},"spend":{"type":"string","minLength":1},"conversions":{"type":"string","minLength":1},"conversion_value":{"type":"string","minLength":1}}},"AdminMarketingAdReportList":{"type":"object","additionalProperties":false,"required":["data","totals"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdDailyMetric"}},"totals":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdCurrencyTotal"}}}},"PortfolioRevisionID":{"type":"string","minLength":1,"pattern":"^mpfr_[0-9a-hjkmnp-tv-z]{26}$","example":"mpfr_01krdgeqcxet5s7t44vh8rt9mg"},"AdminMarketingPortfolioProduct":{"type":"object","additionalProperties":false,"description":"A commercial product shared across advertising channels.","required":["key","name","family_key","family_name","landing_url"],"properties":{"key":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-z0-9][a-z0-9_-]*$","description":"Stable commercial product key."},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Current product name."},"family_key":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-z0-9][a-z0-9_-]*$","description":"Stable product family key."},"family_name":{"type":"string","minLength":1,"maxLength":100,"description":"Product family name."},"landing_url":{"type":"string","minLength":1,"maxLength":2048,"format":"uri","pattern":"^https://","description":"Absolute HTTPS product page on bird.com or www.bird.com, without user information or line breaks."}}},"AdminMarketingPortfolioMarket":{"type":"object","additionalProperties":false,"description":"An observed configured country pool; not actual user location.","required":["key","name","channel_type","countries"],"properties":{"key":{"type":"string","minLength":1,"maxLength":100,"description":"Configured country pool key."},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Market name."},"channel_type":{"type":"string","minLength":1,"maxLength":64,"description":"Native Google channel type for this pool."},"countries":{"type":"array","maxItems":250,"uniqueItems":true,"items":{"type":"string","minLength":0,"pattern":"^[A-Z]{2}$"}}}},"AdminMarketingPortfolioCampaign":{"type":"object","additionalProperties":false,"description":"Dated campaign identity and planned organization.","required":["campaign_id","observed_name","proposed_name","observed_status","channel_type","market_key","objective"],"properties":{"campaign_id":{"type":"string","minLength":1,"maxLength":32,"pattern":"^[0-9]+$","description":"Native Google campaign ID."},"observed_name":{"type":"string","minLength":1,"maxLength":256,"description":"Name from the dated readback."},"proposed_name":{"type":"string","minLength":0,"maxLength":256,"description":"Proposed name; not proof of a completed rename."},"observed_status":{"type":"string","minLength":1,"maxLength":64,"description":"Status from the dated readback."},"channel_type":{"type":"string","minLength":1,"maxLength":64,"description":"Native Google advertising channel type."},"market_key":{"type":"string","minLength":1,"maxLength":100,"description":"Configured country pool key."},"objective":{"type":"string","minLength":0,"maxLength":100,"description":"Commercial objective."}}},"AdminMarketingPortfolioAngle":{"type":"string","minLength":1,"maxLength":64,"enum":["product","mcp"],"description":"Product benefit or MCP angle."},"AdminMarketingPortfolioMedium":{"type":"string","minLength":1,"maxLength":64,"enum":["search_text","image","video_main","video_short","unknown"],"description":"Search text, image, main video or short video. Unknown means no supported creative format is established, including missing or mixed evidence, unmatched rows and inferred historical bindings."},"AdminMarketingPortfolioAudienceRole":{"type":"string","minLength":1,"maxLength":64,"enum":["prospecting","retargeting","unknown"],"description":"Prospecting, retargeting or unknown."},"AdminMarketingPortfolioBinding":{"type":"object","additionalProperties":false,"description":"A dated classification bound to stable provider IDs.","required":["campaign_id","ad_group_id","product_key","market_key","language","intent","angle","medium","audience_role","observed_name","proposed_name","observed_status","creative_key","evidence","observed_urls","observed_at"],"properties":{"campaign_id":{"type":"string","minLength":1,"maxLength":32,"pattern":"^[0-9]+$","description":"Native Google campaign ID."},"ad_group_id":{"type":"string","minLength":1,"maxLength":32,"pattern":"^[0-9]+$","description":"Native Google ad group ID."},"product_key":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-z0-9][a-z0-9_-]*$","description":"Stable product key, or portfolio, brand or mcp for an explicit nonproduct bucket."},"market_key":{"type":"string","minLength":1,"maxLength":100,"description":"Configured country pool key."},"language":{"type":"string","minLength":1,"maxLength":32,"description":"Creative language; independent of country."},"intent":{"type":"string","minLength":0,"maxLength":64,"description":"Commercial search intent."},"angle":{"$ref":"#/components/schemas/AdminMarketingPortfolioAngle"},"medium":{"$ref":"#/components/schemas/AdminMarketingPortfolioMedium"},"audience_role":{"$ref":"#/components/schemas/AdminMarketingPortfolioAudienceRole"},"observed_name":{"type":"string","minLength":1,"maxLength":256,"description":"Group name from the dated readback."},"proposed_name":{"type":"string","minLength":0,"maxLength":256,"description":"Proposed group name."},"observed_status":{"type":"string","minLength":1,"maxLength":64,"description":"Group status from the dated readback."},"creative_key":{"type":"string","minLength":0,"maxLength":256,"description":"Linked authored story key; empty when unknown."},"evidence":{"type":"string","minLength":0,"maxLength":2048,"description":"Classification provenance."},"observed_urls":{"type":"array","maxItems":50,"items":{"type":"string","minLength":0,"maxLength":2048}},"observed_at":{"type":"string","minLength":1,"format":"date-time","description":"Time the provider entity was observed."}}},"AdminMarketingPortfolio":{"type":"object","additionalProperties":false,"description":"A complete versioned product catalogue and account organization snapshot.","required":["source_version","account_id","observed_at","effective_from","products","markets","campaigns","bindings"],"properties":{"source_version":{"type":"string","minLength":1,"maxLength":100,"description":"Immutable source revision identifier."},"account_id":{"type":"string","minLength":1,"maxLength":32,"pattern":"^[0-9]+$","description":"Google customer ID that must match the selected connection."},"observed_at":{"type":"string","minLength":1,"format":"date-time","description":"Source evidence timestamp."},"effective_from":{"type":"string","minLength":1,"format":"date-time","description":"Start of classification validity. Later imports cannot rewrite earlier periods."},"products":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/components/schemas/AdminMarketingPortfolioProduct"}},"markets":{"type":"array","maxItems":250,"items":{"$ref":"#/components/schemas/AdminMarketingPortfolioMarket"}},"campaigns":{"type":"array","maxItems":1000,"items":{"$ref":"#/components/schemas/AdminMarketingPortfolioCampaign"}},"bindings":{"type":"array","maxItems":10000,"items":{"$ref":"#/components/schemas/AdminMarketingPortfolioBinding"}}}},"AdminMarketingPortfolioRevision":{"type":"object","additionalProperties":false,"description":"An accepted organization revision; does not claim Google edits were applied.","required":["id","created_at","portfolio"],"properties":{"id":{"$ref":"#/components/schemas/PortfolioRevisionID"},"created_at":{"type":"string","minLength":1,"format":"date-time","description":"Time Bird accepted this revision."},"portfolio":{"$ref":"#/components/schemas/AdminMarketingPortfolio"}}},"AdminMarketingAdWindow":{"type":"integer","enum":[1,7,30,90],"default":30},"AdminMarketingAdMetricFields":{"type":"object","required":["key","label","impressions","clicks","spend","conversions","conversion_value"],"properties":{"key":{"type":"string","minLength":1,"description":"The stable identifier for the bucket within this breakdown."},"label":{"type":"string","minLength":1},"impressions":{"type":"integer","format":"int64"},"clicks":{"type":"integer","format":"int64"},"spend":{"type":"string","minLength":1,"description":"Spend in the report currency, as an exact decimal amount."},"conversions":{"type":"string","minLength":1,"description":"Google-reported conversions under the account attribution settings; not verified customer outcomes."},"conversion_value":{"type":"string","minLength":1,"description":"Google-reported conversion value in the report currency."},"ctr":{"type":"string","minLength":1,"description":"Clicks divided by impressions; absent when there are no impressions."},"cpc":{"type":"string","minLength":1,"description":"Spend divided by clicks in the report currency; absent when there are no clicks."}}},"AdminMarketingAdMetricBucket":{"allOf":[{"$ref":"#/components/schemas/AdminMarketingAdMetricFields"}],"unevaluatedProperties":false},"AdminMarketingAdClassificationBasis":{"type":"string","minLength":1,"maxLength":64,"enum":["retrospective_url_inference"],"description":"Product classification inferred from a later URL observation, not verified historical destination evidence."},"AdminMarketingAdInferredProduct":{"allOf":[{"$ref":"#/components/schemas/AdminMarketingAdMetricFields"},{"type":"object","required":["classification_basis"],"properties":{"classification_basis":{"$ref":"#/components/schemas/AdminMarketingAdClassificationBasis"}}}],"unevaluatedProperties":false},"AdminMarketingAdSearchBucket":{"allOf":[{"$ref":"#/components/schemas/AdminMarketingAdMetricFields"},{"type":"object","properties":{"match_type":{"type":"string"},"quality_score":{"type":"integer","minimum":1,"maximum":10},"impression_share":{"type":"string","description":"Latest reported day's share. Values below 0.1 are censored by Google."},"lost_rank_share":{"type":"string","description":"Latest reported day's lost share due to rank. Values above 0.9 are censored by Google."}}}],"unevaluatedProperties":false},"AdminMarketingAdAssetReference":{"type":"object","additionalProperties":false,"required":["resource_name","role"],"properties":{"resource_name":{"type":"string","minLength":1},"role":{"type":"string","minLength":1,"description":"Google attachment role, such as landscape, square, portrait, vertical, video or logo."}}},"AdminMarketingAdCreative":{"type":"object","additionalProperties":false,"required":["type","name","headlines","descriptions","assets","configured_channels","channel_strategy"],"properties":{"type":{"type":"string","minLength":1},"name":{"type":"string","minLength":0},"headlines":{"type":"array","items":{"type":"string","minLength":0}},"descriptions":{"type":"array","items":{"type":"string","minLength":0}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdAssetReference"}},"configured_channels":{"type":"array","description":"Explicitly selected Google channels. An empty list does not mean that all channels are disabled; inspect channel_strategy.","items":{"type":"string","minLength":0}},"channel_strategy":{"type":"string","minLength":0},"optimized_targeting":{"type":"boolean","description":"Whether Google expands beyond the configured audience, when reported."}}},"AdminMarketingAdLanding":{"type":"object","additionalProperties":false,"required":["campaign_id","campaign_name","ad_group_id","ad_group_name","ad_id","ad_status","landing_product","final_urls"],"properties":{"campaign_id":{"type":"string","minLength":1},"campaign_name":{"type":"string","minLength":1},"ad_group_id":{"type":"string","minLength":1},"ad_group_name":{"type":"string","minLength":1},"ad_id":{"type":"string","minLength":1},"ad_status":{"type":"string","minLength":1},"approval_status":{"type":"string","minLength":1,"description":"Google policy approval status, when reported. An enabled ad may still be awaiting review."},"landing_product":{"type":"string","minLength":1},"final_urls":{"type":"array","items":{"type":"string","format":"uri"}},"campaign_status":{"type":"string"},"ad_group_status":{"type":"string"},"creative":{"$ref":"#/components/schemas/AdminMarketingAdCreative"}}},"AdminMarketingAdReportKind":{"type":"string","minLength":1,"x-extensible-enum":["configuration","ads","assets","ad_delivery","delivery","history","ad_groups","countries","keywords","search_terms","hourly","locations"]},"AdminMarketingAdPerformanceSource":{"type":"object","additionalProperties":false,"required":["kind","last_attempt_at","next_refresh_at","refreshing"],"properties":{"kind":{"$ref":"#/components/schemas/AdminMarketingAdReportKind"},"last_attempt_at":{"type":"string","minLength":1,"format":"date-time"},"last_success_at":{"type":"string","minLength":1,"format":"date-time"},"next_refresh_at":{"type":"string","minLength":1,"description":"Earliest next automatic or requested collection, subject to provider backoff and the shared request budget.","format":"date-time"},"refreshing":{"type":"boolean"},"last_error":{"type":"string","minLength":1},"from":{"type":"string","minLength":1,"format":"date"},"to":{"type":"string","minLength":1,"format":"date"}}},"AdminMarketingAdCampaignRef":{"type":"object","additionalProperties":false,"required":["id","name","status"],"properties":{"id":{"type":"string","minLength":1,"description":"Native Google campaign ID."},"name":{"type":"string","minLength":1},"status":{"type":"string","minLength":1},"channel_type":{"type":"string","minLength":1,"description":"Native Google advertising channel type, such as SEARCH, DISPLAY or DEMAND_GEN. Omitted when the source report did not include the type."}}},"AdminMarketingAdProviderAsset":{"type":"object","additionalProperties":false,"required":["resource_name","id","name","type"],"properties":{"resource_name":{"type":"string","minLength":1},"id":{"type":"string","minLength":1},"name":{"type":"string","minLength":0},"type":{"type":"string","minLength":1},"image_url":{"type":"string","minLength":1,"format":"uri"},"width":{"type":"integer","format":"int64"},"height":{"type":"integer","format":"int64"},"size_bytes":{"type":"integer","format":"int64"},"youtube_id":{"type":"string","minLength":1},"youtube_title":{"type":"string","minLength":1}}},"AdminMarketingAdPerformance":{"type":"object","additionalProperties":false,"required":["assets","ad_delivery","ad_networks","grain","unavailable","product_channels","campaign_types","campaign_options","hourly","request_budget_used","request_budget_limit","daily_budget","shared_budget","summary","daily","campaigns","products","countries","ad_groups","search_terms","keywords","search_term_count","landing_pages","sources"],"properties":{"currency_code":{"$ref":"#/components/schemas/CurrencyCode"},"time_zone":{"type":"string","minLength":1},"from":{"type":"string","minLength":1,"format":"date"},"to":{"type":"string","minLength":1,"format":"date"},"daily_budget":{"type":"string","minLength":1,"description":"Sum of enabled campaigns' observed daily budgets. Shared budgets are counted once."},"shared_budget":{"type":"boolean"},"summary":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"},"daily":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"}},"campaigns":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"}},"inferred_products":{"type":"array","description":"The subset of product totals assigned retrospectively from later URL observations. These amounts are already included in products, use the report currency, and do not establish verified day-by-day classifications. Absent when no inferred assignments contribute.","items":{"$ref":"#/components/schemas/AdminMarketingAdInferredProduct"}},"products":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"}},"countries":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"}},"ad_groups":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"}},"search_terms":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"}},"keywords":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdSearchBucket"}},"search_term_count":{"type":"integer","description":"Number of reported terms before limiting search_terms to the 100 highest-spend terms. Google withholds some search terms."},"landing_pages":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdLanding"}},"sources":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdPerformanceSource"}},"campaign_types":{"type":"array","description":"Delivery grouped by native Google campaign type. Demand Gen can include Display and YouTube placements.","items":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"}},"campaign_options":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdCampaignRef"}},"hourly":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"}},"request_budget_used":{"type":"integer","format":"int64","description":"Requests reserved by this collector during the previous 24 hours. Other project users are not included."},"request_budget_limit":{"type":"integer","description":"Maximum collector requests per rolling 24 hours, shared across advertising accounts and viewers."},"grain":{"type":"string","minLength":1,"description":"Fact grain used for headline totals and daily trends, such as campaign_day, ad_group_day or ad_group_country_day."},"unavailable":{"type":"array","description":"Breakdown keys that cannot be reported for the selected filters. Their values must not be displayed as zero.","items":{"type":"string","minLength":0}},"product_channels":{"type":"array","description":"Mutually exclusive product, medium and audience-role buckets from ad-group facts. Keys use product/medium/audience-role.","items":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"}},"assets":{"type":"array","description":"Google assets referenced by ads within the selected filters.","items":{"$ref":"#/components/schemas/AdminMarketingAdProviderAsset"}},"ad_delivery":{"type":"array","description":"Ad-level delivery, keyed by campaign/ad-group/ad. These totals must not be attributed to every attached asset.","items":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"}},"ad_networks":{"type":"array","description":"Observed ad delivery by Google network, keyed by campaign/ad-group/ad/network. Configured channels are reported separately on each ad.","items":{"$ref":"#/components/schemas/AdminMarketingAdMetricBucket"}}}},"AdminMarketingAdReportRun":{"type":"object","additionalProperties":false,"required":["connection_id","consecutive_failures"],"properties":{"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID"},"last_pulled_day":{"type":"string","minLength":1,"format":"date"},"last_run_at":{"type":"string","minLength":1,"format":"date-time"},"last_error":{"type":"string","minLength":1},"consecutive_failures":{"type":"integer"}}},"AdminMarketingAdReportRunList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingAdReportRun"}}}},"AdminMarketingSelectableAccount":{"type":"object","additionalProperties":false,"required":["external_account_id","name","currency_code","parent_id"],"properties":{"external_account_id":{"type":"string","minLength":1,"description":"The provider account identifier, such as an advertising account ID, a Facebook Page ID, or a LinkedIn organization URN."},"name":{"type":"string","minLength":1,"description":"The account display name, or its identifier when the platform returns no name."},"currency_code":{"oneOf":[{"$ref":"#/components/schemas/CurrencyCode"},{"type":"null"}],"description":"The account currency published by the platform."},"parent_id":{"type":["string","null"],"minLength":1,"description":"The manager account identifier, when present."}}},"AdminMarketingConnectionProvider":{"type":"string","minLength":1,"enum":["linkedin","x","instagram","tiktok","youtube","facebook","reddit","threads","reddit_ads","google_ads","tiktok_ads","meta_ads","microsoft_ads","x_ads","bird","google_analytics"],"description":"A social, advertising, analytics, or Bird platform account authorized through a marketing connection.","example":"reddit_ads"},"AdminMarketingConnectionStatus":{"type":"string","minLength":1,"enum":["account_selection_required","connected","reauth_required","revoked"],"description":"What the stored credential is worth. `account_selection_required` means consent succeeded but no account is selected; staff can list and select cached accounts until the one-hour selection deadline. It cannot publish or report before selection. `connected` means the token is valid or renewable, which is the precondition for publishing rather than a promise that anything publishes yet. `reauth_required` means it expired or its renewal failed for good. `revoked` means the platform withdrew the grant. Only a fresh connect flow moves the last two back to `connected`.\n","example":"connected"},"AdminMarketingConnection":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","required":["id","platform","account_currency_code","account_selection_expires_at","external_account_id","account_handle","account_name","account_avatar_url","status","granted_scopes","access_token_expires_at","has_refresh_token","refresh_token_expires_at","last_refreshed_at","last_refresh_error","connected_by_user_id"],"properties":{"id":{"$ref":"#/components/schemas/MarketingConnectionID"},"platform":{"$ref":"#/components/schemas/AdminMarketingConnectionProvider"},"external_account_id":{"type":["string","null"],"minLength":1,"description":"The account's identifier at the platform. Null while status is account_selection_required."},"account_currency_code":{"oneOf":[{"$ref":"#/components/schemas/CurrencyCode"},{"type":"null"}],"description":"The currency published by the selected account. Null before account selection or when the provider supplies no currency."},"account_selection_expires_at":{"type":["string","null"],"format":"date-time","description":"The deadline for choosing an account while status is account_selection_required. Null after selection or when the provider requires no selection."},"account_handle":{"type":["string","null"],"description":"The account's handle at the platform. Null when it publishes none."},"account_name":{"type":["string","null"],"description":"The account's display name. Null when the platform returns none."},"account_avatar_url":{"type":["string","null"],"format":"uri","description":"The account's avatar. Null when the platform returns none."},"status":{"$ref":"#/components/schemas/AdminMarketingConnectionStatus"},"granted_scopes":{"type":"array","items":{"type":"string","minLength":1},"description":"The scopes the platform actually granted, which can be narrower than the ones Bird asked for.\n"},"access_token_expires_at":{"type":["string","null"],"format":"date-time","description":"When the stored access token stops working. Null when the platform issues one that does not expire.\n"},"has_refresh_token":{"type":"boolean","description":"Whether Bird holds something it can renew the access token with. False means the only way back to a working connection is a fresh connect flow.\n"},"refresh_token_expires_at":{"type":["string","null"],"format":"date-time","description":"When the renewal credential itself stops working, when it expires at all."},"last_refreshed_at":{"type":["string","null"],"format":"date-time","description":"When Bird last renewed the access token. Null until the first renewal."},"last_refresh_error":{"type":["string","null"],"description":"Why the last renewal failed. Null when the last one succeeded."},"connected_by_user_id":{"$ref":"#/components/schemas/UserID"}}}]},"AdminMarketingConnectionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingConnection"},"description":"The matching connections."}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminMarketingRedditDestinationKind":{"type":"string","minLength":1,"enum":["subreddit","profile"]},"AdminMarketingRedditFlair":{"type":"object","additionalProperties":false,"required":["id","text_editable"],"properties":{"id":{"type":"string","minLength":1},"text":{"type":"string","minLength":1},"text_editable":{"type":"boolean"}}},"AdminMarketingRedditDestinationInfo":{"type":"object","additionalProperties":false,"required":["destination","resolved_subreddit","available","allow_text","allow_link","allow_images","allow_galleries","allow_video","require_flair","flairs"],"properties":{"destination":{"$ref":"#/components/schemas/AdminMarketingRedditDestination"},"resolved_subreddit":{"type":"string","minLength":1},"available":{"type":"boolean","description":"Whether this connected account can submit to the destination. Reddit may enforce additional rules at submission time."},"allow_text":{"type":"boolean"},"allow_link":{"type":"boolean"},"allow_images":{"type":"boolean"},"allow_galleries":{"type":"boolean"},"allow_video":{"type":"boolean"},"require_flair":{"type":"boolean"},"title_min_length":{"type":"integer"},"title_max_length":{"type":"integer"},"body_restriction_policy":{"type":"string","minLength":1},"guidelines":{"type":"string","minLength":1},"flairs":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingRedditFlair"}}}},"AdminMarketingBirdAudience":{"type":"object","additionalProperties":false,"required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/AudienceID"},"name":{"type":"string","minLength":1,"description":"The audience's name in the connected workspace."}}},"AdminMarketingBirdEmailDestinations":{"type":"object","additionalProperties":false,"required":["audiences","sending_domains"],"description":"What a Bird email channel can be pointed at in one connected workspace. An empty list is an answer, not a failure: a workspace with no verified domain cannot send, and the admin app says so rather than offering a sender that the publish would refuse.","properties":{"audiences":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingBirdAudience"},"description":"Every audience in the connected workspace."},"sending_domains":{"type":"array","items":{"type":"string","minLength":1,"description":"A domain the connected workspace has verified for sending.","example":"news.bird.com"},"description":"Every domain the connected workspace may send from, meaning its `sending` capability is verified rather than only its ownership. A channel's sender address has to sit on one of them."}}},"AdminMarketingConnectionOAuthStart":{"additionalProperties":false,"type":"object","required":["platform","return_to"],"properties":{"platform":{"$ref":"#/components/schemas/AdminMarketingConnectionProvider"},"return_to":{"type":"string","format":"uri","minLength":1,"maxLength":512,"description":"Where the platform's callback should land the browser once the exchange finishes. Must be one of Bird's own configured origins.\n"},"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID"}}},"AdminMarketingConnectionOAuthStartResult":{"additionalProperties":false,"type":"object","required":["authorize_url","state","expires_at"],"properties":{"authorize_url":{"type":"string","format":"uri","minLength":1,"description":"Send the browser here to ask the account holder for consent."},"state":{"type":"string","minLength":1,"description":"The single-use value tying the callback back to this attempt. Bird stores only its hash, so this is the one copy.\n"},"expires_at":{"type":"string","format":"date-time","minLength":1,"description":"After this the attempt is refused and the flow has to start again."}}},"AdminMarketingPlatformKind":{"type":"string","minLength":1,"enum":["social","search","advertising","platform","analytics"],"description":"The connection category: social for organic social accounts, search for Google Ads, advertising for other Ads APIs such as Meta Ads, Reddit Ads, TikTok Ads and X Ads, analytics for a measurement property such as a Google Analytics property, and platform for a Bird workspace authorized through Bird's own authorization server. This category does not grant publishing or reporting capabilities; consult the platform's publishing_configured and supports_audience_metrics flags for supported social operations.","example":"search"},"AdminMarketingConnectionPlatform":{"additionalProperties":false,"type":"object","required":["platform","kind","configured","supports_refresh","supports_audience_metrics","scopes"],"properties":{"kind":{"$ref":"#/components/schemas/AdminMarketingPlatformKind"},"platform":{"$ref":"#/components/schemas/AdminMarketingConnectionProvider"},"configured":{"type":"boolean","description":"Whether this environment can connect the platform at all, which takes both the OAuth client credentials and an adapter for that platform. False means the connect flow cannot start, whatever else is true. It does not say which of the two is missing: deploying credentials for a platform whose adapter is unwritten leaves this false.\n"},"publishing_configured":{"type":"boolean","readOnly":true,"description":"Whether this region has the provider and dependencies needed to publish saved campaign channels. Account authorization and content validation still apply. Missing or false means publishing is unavailable.\n"},"supports_refresh":{"type":"boolean","description":"Whether Bird can renew a token for this platform without the account holder consenting again. False means a connection here needs reconnecting by hand each time its token lapses.\n"},"supports_audience_metrics":{"type":"boolean","description":"Whether Bird can read a follower count for this platform. False covers both the platform whose API exposes none and the platform Bird holds no adapter for at all, so a connection here has no audience history.\n"},"scopes":{"type":"array","items":{"type":"string","minLength":1},"description":"The scopes Bird asks the platform for."}}},"AdminMarketingConnectionPlatformList":{"additionalProperties":false,"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingConnectionPlatform"},"description":"Every platform Bird knows, whether or not this environment can connect it."}}},"AdminMarketingConnectionGrowthPoint":{"type":"object","additionalProperties":false,"required":["day","gained","lost"],"properties":{"day":{"type":"string","format":"date","minLength":10,"description":"Reporting date in the series reporting timezone."},"gained":{"type":"integer","format":"int64","description":"Followers or subscribers gained on this reporting day. Negative when the series reports net change and the account lost followers that day."},"lost":{"type":["integer","null"],"format":"int64","minimum":0,"description":"Followers or subscribers lost on this reporting day, or null when the platform does not report losses."}}},"AdminMarketingGrowthStatus":{"type":"string","minLength":1,"description":"Progress of importing the account's historical growth. `importing` means history is waiting to import or an import is in progress. `synced` means the latest import or refresh completed; coverage may still be partial. `reconnect_required` means the account must reconnect before imports can continue. `failed` means the last import failed and will be retried automatically. `unavailable` means the provider refused the metric or returned an unsupported response for this account. `unsupported` means no historical growth adapter is configured for this platform.\n","enum":["importing","synced","reconnect_required","failed","unavailable","unsupported"]},"AdminMarketingConnectionGrowthSeries":{"additionalProperties":false,"type":"object","required":["connection_id","platform","status","account_handle","account_name","account_avatar_url","points","partial_coverage","reports_net_change","import_status","reporting_timezone","earliest_available_day","last_synced_at"],"properties":{"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID"},"platform":{"$ref":"#/components/schemas/AdminMarketingConnectionProvider"},"status":{"$ref":"#/components/schemas/AdminMarketingConnectionStatus"},"account_handle":{"type":["string","null"],"description":"The account's handle at the platform. Null when it publishes none."},"account_name":{"type":["string","null"],"description":"The account's display name. Null when the platform returns none."},"account_avatar_url":{"type":["string","null"],"format":"uri","description":"The account's avatar. Null when the platform returns none."},"points":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingConnectionGrowthPoint"},"description":"Reported daily growth, oldest first. Missing days are absent, never assumed to be zero."},"import_status":{"$ref":"#/components/schemas/AdminMarketingGrowthStatus"},"reporting_timezone":{"type":"string","minLength":0,"description":"IANA reporting timezone. Empty for unsupported platforms."},"earliest_available_day":{"type":["string","null"],"format":"date","description":"Earliest date requested from the platform. Null before import initialization."},"last_synced_at":{"type":["string","null"],"format":"date-time","description":"Last completed import or refresh. Null before the first completed import."},"partial_coverage":{"type":"boolean","description":"True when the selected reporting window contains missing days or its import is incomplete."},"reports_net_change":{"type":"boolean","description":"True when the platform folds losses into each day's reported figure, so gained is a net change and lost is always null."}}},"AdminMarketingConnectionGrowthList":{"additionalProperties":false,"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingConnectionGrowthSeries"},"description":"Every connection, whether or not it has any history yet. Unpaginated: these are Bird's own social accounts, so the answer is bounded by how many Bird has connected rather than by a page size.\n"}}},"AdminMarketingConnectionAudiencePoint":{"additionalProperties":false,"type":"object","required":["day","follower_count"],"properties":{"day":{"type":"string","format":"date","minLength":1,"description":"The UTC day the count was recorded against.","example":"2026-09-08"},"follower_count":{"type":"integer","format":"int64","minimum":0,"description":"The account's total following as the platform reported it that day: a level, not that day's change. Two consecutive days differ by the net change between them.\n"}}},"AdminMarketingConnectionAudienceSeries":{"additionalProperties":false,"type":"object","required":["connection_id","platform","status","account_handle","account_name","account_avatar_url","points"],"properties":{"connection_id":{"$ref":"#/components/schemas/MarketingConnectionID"},"platform":{"$ref":"#/components/schemas/AdminMarketingConnectionProvider"},"status":{"$ref":"#/components/schemas/AdminMarketingConnectionStatus"},"account_handle":{"type":["string","null"],"description":"The account's handle at the platform. Null when it publishes none."},"account_name":{"type":["string","null"],"description":"The account's display name. Null when the platform returns none."},"account_avatar_url":{"type":["string","null"],"format":"uri","description":"The account's avatar. Null when the platform returns none."},"points":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingConnectionAudiencePoint"},"description":"The recorded days inside the window, oldest first. A day Bird could not read is absent rather than zero, so a gap here is a gap in the record and not a collapse in the following. Empty until the first sync records one.\n"}}},"AdminMarketingConnectionAudienceList":{"additionalProperties":false,"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMarketingConnectionAudienceSeries"},"description":"Every connection, whether or not it has any history yet. Unpaginated: these are Bird's own social accounts, so the answer is bounded by how many Bird has connected rather than by a page size.\n"}}},"AdminCountryPolicyUpsert":{"type":"object","additionalProperties":false,"properties":{"high_risk_destination":{"type":"boolean","description":"Whether Bird treats this country as a high-risk messaging destination."},"data_residency_required":{"type":"boolean","description":"Whether local data-residency requirements apply."},"supported_platforms":{"type":"array","items":{"type":"string"},"description":"Messaging platforms available in this country."},"dial_code":{"type":["string","null"],"description":"International dialling prefix (without the leading +)."},"mobile_country_codes":{"type":"array","items":{"type":"string"},"description":"ITU mobile country codes (MCC) assigned to this country."},"portability_supported":{"type":"boolean","description":"Whether number portability is supported."},"region":{"$ref":"#/components/schemas/DestinationRegion"},"internal_notes":{"type":["string","null"],"description":"Internal staff notes. Not returned by the public API."}}},"AdminSMSKeywordOperation":{"type":"string","minLength":1,"x-extensible-enum":["stop","start","help","info","confirm","custom"],"description":"What the catalogue row's keywords do.\n\n- `stop` opens a suppression episode on the (sender, subscriber) pair, then sends the\n  acknowledgement reply.\n- `start` ends that episode, then sends the acknowledgement reply.\n- `help` replies and nothing else.\n- `info` replies and nothing else, exactly as `help` does. Declared, and used by no\n  catalogue row until INFO is moved off `help` in the order set out below.\n- `confirm` is reserved for double opt-in and ships a null reply until that behaviour lands.\n  Naming it here is what reserves JOIN, YES and SUBSCRIBE against a workspace campaign rule.\n- `custom` replies with the workspace's own text; the catalogue holds no `custom` rows.\n\n`help` and `info` behave identically and are still two operations, because a row pairs its\nkeywords with ONE reply: a country that must answer INFO differently from HELP needs a second\nrow, and this is the only key it can differ on.\n\nA keyword binds to one operation everywhere, in every country, so the two directions are\nsymmetric: a row is refused with E12073 whenever any other row already binds one of its\nkeywords to a different operation. Moving INFO from `help` to `info` therefore means dropping\nthe keyword from every `help` row that carries it, in every country, before the first `info`\nrow can be created.\n","example":"stop"},"SMSSystemKeywordRuleID":{"type":"string","minLength":1,"pattern":"^sks_[0-9a-hjkmnp-tv-z]{26}$","example":"sks_01krdgeqcxet5s7t44vh8rt9mg"},"AdminSMSKeywordRule":{"type":"object","additionalProperties":false,"required":["id","country","operation","keywords","mandatory","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/SMSSystemKeywordRuleID"},"country":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code the row applies in. Stored uppercase.","example":"NL"},"language":{"type":["string","null"],"minLength":2,"description":"Which language set this row is, in countries Bird ships keywords in more than one. Two rows differing only by language are a declared split, and a workspace override then has to name which side it replaces. Null in single-set countries.\n","example":"fr"},"operation":{"$ref":"#/components/schemas/AdminSMSKeywordOperation"},"keywords":{"type":"array","minItems":1,"description":"The full keyword set for this row, stored normalised (case-folded, diacritics folded). A workspace override for the same country and language inherits this set and may add to it, so adding a keyword here reaches every existing override.\n","items":{"type":"string","minLength":1},"example":["stop","stoppen","afmelden"]},"reply":{"type":["string","null"],"minLength":1,"description":"The auto-reply Bird sends when one of these keywords matches and the workspace has set no override.","example":"Je bent afgemeld. Je ontvangt geen berichten meer van dit nummer."},"reply_suffix":{"type":["string","null"],"minLength":1,"description":"The compliant tail a workspace override is PREPENDED to rather than replacing, so a customer editing the reply cannot remove the rates disclosure or the opt-out instruction. Only help rows carry one; a stop reply must not acquire boilerplate.\n","example":"Msg&data rates may apply. Reply STOP to opt out."},"mandatory":{"type":"boolean","description":"Binds these keywords to this operation immutably: a workspace may override the reply and can never change what the keyword does. Not a precedence flag, since resolution is customer-first throughout.\n","example":true},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"example":"2026-08-12T09:00:00Z"},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"example":"2026-08-12T09:00:00Z"}}},"AdminSMSKeywordRuleList":{"type":"object","additionalProperties":false,"required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminSMSKeywordRule"},"description":"Matching catalogue rows, ordered by country, then operation, then language."},"total":{"type":"integer","format":"int64","description":"Total number of catalogue rows matching the filters, ignoring pagination."}}},"AdminSMSKeywordRuleUpsert":{"type":"object","additionalProperties":false,"required":["country","operation","keywords"],"description":"Creates or replaces one catalogue row, keyed by (country, language, operation). That triple is the identity: two rows sharing it would derive identical keyword sets for a workspace override with no deterministic winner, so this upserts rather than duplicating.\n","properties":{"country":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code. Case-insensitive; stored uppercase.","example":"NL"},"language":{"type":["string","null"],"minLength":2,"description":"Omit in a single-set country. Supply it to declare a language split, which then REQUIRES a workspace override for that country to name which side it replaces. Adding a second language to a country that had one is a behaviour change for existing overrides, not just new data.\n","example":"fr"},"operation":{"$ref":"#/components/schemas/AdminSMSKeywordOperation"},"keywords":{"type":"array","minItems":1,"description":"The full keyword set for this row. Stored normalised, and normalised on the way in, so matching is equality rather than a transform per message. A keyword already bound to a different operation in any country is rejected: the binding is global.\n","items":{"type":"string","minLength":1},"example":["stop","stoppen","afmelden"]},"reply":{"type":["string","null"],"minLength":1,"description":"The auto-reply sent when a workspace has set no override. Null sends nothing.","example":"Je bent afgemeld. Je ontvangt geen berichten meer van dit nummer."},"reply_suffix":{"type":["string","null"],"minLength":1,"description":"The tail a workspace override is prepended to and cannot edit away. Set it on help rows only; a stop or start reply the customer wrote must not acquire Bird boilerplate.\n","example":"Msg&data rates may apply. Reply STOP to opt out."},"mandatory":{"type":"boolean","description":"Set on the keywords a regulator requires to keep their meaning, so a workspace can change the reply but never what the keyword does. Defaults to false.\n","example":true}}},"AdminPlatformPolicyUpsert":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","minLength":1,"enum":["available","restricted","coming_soon","unavailable"],"description":"Availability of this platform in the country."},"regulatory_body":{"type":["string","null"],"description":"Name of the primary regulatory body."},"consent_model":{"type":["string","null"],"enum":["opt_in","opt_out","double_opt_in",null],"description":"Opt-in model required by regulation. Null when none is recorded."},"opt_in_required":{"type":["boolean","null"],"description":"Whether explicit opt-in is required."},"double_opt_in_required":{"type":["boolean","null"],"description":"Whether double opt-in is required."},"opt_out_required":{"type":["boolean","null"],"description":"Whether opt-out requests must be honoured."},"requires_registration":{"type":["boolean","null"],"description":"Whether sender registration is required."},"requires_template_approval":{"type":["boolean","null"],"description":"Whether message templates require regulatory pre-approval."},"is_one_way_supported":{"type":["boolean","null"],"description":"Whether one-way sending is supported."},"is_two_way_supported":{"type":["boolean","null"],"description":"Whether two-way (reply-capable) messaging is supported."},"max_throughput_per_second":{"type":["integer","null"],"description":"Maximum messages per second allowed by regulation."},"restricted_categories":{"type":"array","items":{"type":"string"},"description":"Message categories that are restricted or prohibited."},"required_elements":{"type":"array","items":{"type":"string"},"description":"Required message elements (e.g. opt-out instructions)."},"max_message_length":{"type":["integer","null"],"description":"Maximum message length in characters."},"url_registration_required":{"type":["boolean","null"],"description":"Whether URLs must be registered before use."},"url_shorteners_allowed":{"type":["boolean","null"],"description":"Whether URL shorteners are permitted."},"generic_url_shorteners_allowed":{"type":["boolean","null"],"description":"Whether generic public URL shorteners (e.g. bit.ly) are permitted."},"has_quiet_hours":{"type":["boolean","null"],"description":"Whether quiet-hour restrictions apply."},"quiet_hours_is_regulatory":{"type":["boolean","null"],"description":"Whether quiet hours are regulatory (true) or advisory (false)."},"exempt_message_categories":{"type":"array","items":{"type":"string"},"description":"Categories exempt from quiet-hour restrictions."},"regulations_description":{"type":["string","null"],"description":"Summary of applicable regulations."},"restrictions_description":{"type":["string","null"],"description":"Summary of content or operational restrictions."},"internal_notes":{"type":["string","null"],"description":"Internal staff notes. Not returned by the public API."}}},"AdminSenderTypePolicy":{"type":"object","additionalProperties":false,"description":"The complete sender type policy for one country, platform, and sender type. Unlike the messaging context, this carries the staff-only fields as well, so an edit form can round-trip every value the upsert writes.\n","required":["country_code","platform","sender_type","display_name","status","direction","registration_mode","sender_id_behavior","sender_id_replacement","third_party_approval_required","default_routing_priority","registration_types","supported_message_categories","allowed_source_countries","restrictions","created_at","updated_at"],"properties":{"country_code":{"type":"string","minLength":2,"readOnly":true,"description":"Country this policy applies to."},"platform":{"type":"string","minLength":1,"readOnly":true,"description":"Messaging platform this sender type belongs to."},"sender_type":{"type":"string","minLength":1,"readOnly":true,"enum":["long_code","short_code","alphanumeric","toll_free"],"description":"Sender identity type this policy applies to."},"display_name":{"type":"string","minLength":1,"description":"Human-readable label for this sender type."},"status":{"type":"string","minLength":1,"enum":["available","restricted","coming_soon","unavailable"],"description":"Availability status. Only `available` carries unregistered traffic."},"direction":{"type":"string","minLength":1,"enum":["one_way","two_way"],"description":"Whether this sender type supports replies."},"throughput_mps":{"type":["integer","null"],"description":"Maximum send rate in messages per second."},"throughput_mpd":{"type":["integer","null"],"description":"Maximum messages per day."},"registration_mode":{"$ref":"#/components/schemas/SMSRegistrationMode","description":"Whether registration is not required, optional, or required before sending."},"sender_id_behavior":{"$ref":"#/components/schemas/SMSSenderIDBehavior","description":"Expected sender ID behavior. For optional registration, this applies before approval; approved registrations preserve the sender ID. For required registration, this behavior still applies after approval.\n"},"sender_id_replacement":{"description":"The replacement used when sender IDs are overwritten, or null otherwise.","oneOf":[{"$ref":"#/components/schemas/SMSSenderIDReplacement"},{"type":"null"}]},"program":{"type":["string","null"],"enum":["10dlc","tfn","alpha","numeric","ctia_short_code",null],"description":"External registration programme, or null if the sender type needs none."},"registration_types":{"type":"array","items":{"type":"string"},"description":"Registration categories accepted under the programme."},"registration_timeline_min":{"type":["integer","null"],"description":"Minimum expected registration time in business days."},"registration_timeline_max":{"type":["integer","null"],"description":"Maximum expected registration time in business days."},"deregistration_required":{"type":["boolean","null"],"description":"Whether active deregistration is required when discontinuing use."},"third_party_approval_required":{"type":"boolean","description":"Whether a party outside the carrier (a regulator, or the mobile operators) approves the registration, which makes its timeline unpredictable.\n"},"supported_message_categories":{"type":"array","items":{"type":"string"},"description":"Message categories this sender type can be used for. Empty means all."},"allowed_source_countries":{"type":"array","items":{"$ref":"#/components/schemas/CountryCode"},"description":"The countries a sender of this type may send FROM to reach this destination. Empty means unrestricted. A sender outside the set is not offered this destination's requirements and is refused by the send path, rather than being told to register for a programme it cannot hold.\n"},"restrictions":{"type":"array","items":{"type":"string"},"description":"Applicable content or operational restrictions."},"default_routing_priority":{"type":"integer","description":"Routing priority relative to other sender types (lower = higher priority)."},"internal_notes":{"type":["string","null"],"description":"Internal staff notes. Not carried by the customer-facing messaging context."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the policy was first recorded."},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the policy was last changed."}}},"AdminSenderTypePolicyUpsert":{"type":"object","additionalProperties":false,"required":["display_name","status","direction"],"properties":{"display_name":{"type":"string","minLength":1,"description":"Human-readable label for this sender type."},"status":{"type":"string","minLength":1,"enum":["available","restricted","coming_soon","unavailable"],"description":"Availability status."},"direction":{"type":"string","minLength":1,"enum":["one_way","two_way"],"description":"Whether this sender type supports replies."},"throughput_mps":{"type":["integer","null"],"description":"Maximum send rate in messages per second."},"throughput_mpd":{"type":["integer","null"],"description":"Maximum messages per day."},"registration_mode":{"$ref":"#/components/schemas/SMSRegistrationMode","description":"Whether registration is not required, optional, or required before sending. Optional registration requires sender_id_behavior overwritten and program alpha or numeric. Required registration requires a non-null program. Invalid combinations return 422. Omission preserves the stored mode on update and defaults to none on creation.\n"},"sender_id_behavior":{"$ref":"#/components/schemas/SMSSenderIDBehavior","description":"Expected sender ID behavior. For optional registration, this applies before approval; approved registrations preserve the sender ID. For required registration, this behavior still applies after approval. Omission preserves the stored behavior on update and defaults to unknown on creation.\n"},"sender_id_replacement":{"description":"Omission preserves the stored replacement on update and defaults to null on creation. The resulting policy requires a replacement when sender_id_behavior is overwritten and must have a null replacement otherwise. Null clears the replacement only when the resulting behavior is not overwritten. Invalid combinations return 422.\n","oneOf":[{"$ref":"#/components/schemas/SMSSenderIDReplacement"},{"type":"null"}]},"program":{"type":["string","null"],"enum":["10dlc","tfn","alpha","numeric","ctia_short_code",null],"description":"External registration programme, if applicable."},"registration_types":{"type":"array","items":{"type":"string"},"description":"Registration categories accepted under the programme."},"registration_timeline_min":{"type":["integer","null"],"description":"Minimum expected registration time in business days."},"registration_timeline_max":{"type":["integer","null"],"description":"Maximum expected registration time in business days."},"deregistration_required":{"type":["boolean","null"],"description":"Whether active deregistration is required when discontinuing use."},"third_party_approval_required":{"type":"boolean","description":"Whether a party outside the carrier (a regulator, or the mobile operators) approves the registration. Omitted leaves the stored value alone. Defaults to false.\n"},"supported_message_categories":{"type":"array","items":{"type":"string"},"description":"Message categories this sender type can be used for."},"allowed_source_countries":{"type":"array","items":{"$ref":"#/components/schemas/CountryCode"},"description":"The countries a sender of this type may send FROM to reach this destination. Empty means unrestricted, so leaving it empty keeps the corridor open to senders from anywhere. Set it where the destination's registration programme admits only senders from a known set of countries: a sender outside the set is then left out of that destination's requirements and refused by the send path, instead of being told to register for a programme it cannot hold.\n"},"restrictions":{"type":"array","items":{"type":"string"},"description":"Applicable content or operational restrictions."},"default_routing_priority":{"type":"integer","description":"Routing priority relative to other sender types (lower = higher priority)."},"internal_notes":{"type":["string","null"],"description":"Internal staff notes. Not returned by the public API."}}},"BillingProductList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingProductSummary"}}}},"BillingEntitlementRateCard":{"type":"object","additionalProperties":false,"required":["id","name"],"properties":{"id":{"$ref":"#/components/schemas/RateCardID"},"name":{"type":"string","minLength":1}}},"BillingPlanEntitlement":{"type":"object","additionalProperties":false,"required":["product"],"properties":{"product":{"$ref":"#/components/schemas/BillingProductSummary"},"included_units":{"type":["integer","null"],"format":"int64","description":"Number of free units included per billing period. `null` if not applicable (for example, `rate_card` products).","example":50000},"rate_card":{"type":["object","null"],"allOf":[{"$ref":"#/components/schemas/BillingEntitlementRateCard"}]}}},"BillingPlanQuota":{"type":"object","additionalProperties":false,"required":["key","value"],"properties":{"key":{"type":"string","minLength":1,"description":"Quota key identifying the resource being limited.","example":"email_domains_max"},"value":{"type":"integer","format":"int64","description":"Maximum allowed units for this quota on the plan. Zero means the feature is not available on this plan.","example":1000}}},"BillingPlan":{"type":"object","additionalProperties":false,"required":["id","slug","name","tier","weight","billing_interval","entitlements","quotas"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ProductID"},"slug":{"type":"string","minLength":1,"example":"plan-growth"},"name":{"type":"string","minLength":1,"example":"Growth"},"tier":{"type":"string","minLength":1,"description":"The plan family this belongs to, such as `growth` or `pro`, independent of billing interval.","example":"growth"},"weight":{"type":"integer","format":"int32","readOnly":true,"description":"Sort key for plan ordering; higher is more premium.","example":200},"base_price":{"type":["object","null"],"description":"List price. `null` for sales-negotiated plans.","allOf":[{"$ref":"#/components/schemas/Money"}]},"billing_interval":{"type":"string","minLength":1,"enum":["month","year"],"description":"The recurring interval this plan bills on.","example":"month"},"entitlements":{"type":"array","description":"What's included in this plan for each product, such as free usage or rate-card pricing.","items":{"$ref":"#/components/schemas/BillingPlanEntitlement"}},"quotas":{"type":"array","description":"Resource limits enforced for this plan. Use these to determine what a plan allows when no entitlement exists for the resource.","items":{"$ref":"#/components/schemas/BillingPlanQuota"}}}},"BillingPlanList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingPlan"}}}},"BillingPriceContext":{"type":"object","additionalProperties":false,"description":"What else you need to know before relying on this price, such as whether the destination accepts only a sender you have registered. Absent when nothing further applies.\n","properties":{"registration_mode":{"allOf":[{"$ref":"#/components/schemas/SMSRegistrationMode"}],"description":"Registration policy for this destination and sender type. Required registration must be approved before sending. Optional registration allows sending while approval is pending. The rate applies in either case; the country messaging context describes sender ID behavior.\n"}}},"BillingPrice":{"type":"object","additionalProperties":false,"required":["dimensions","unit_price"],"properties":{"dimensions":{"type":"object","additionalProperties":{"type":"string"},"description":"The attributes that decide which price applies, such as destination country and sender type. Which attributes a product uses depends on how it is priced, so match on the whole set rather than on a fixed shape.\n","example":{"country_code":"US","sender_type":"long_code"}},"unit_price":{"$ref":"#/components/schemas/Money"},"increment":{"type":["string","null"],"description":"How a time-based product rounds usage before pricing it. The format is `{first}/{subsequent}` seconds. For example, `60/60` bills a full minute up front and then whole minutes. A `null` value means the product is priced per unit rather than per second.\n","example":"60/60"},"context":{"$ref":"#/components/schemas/BillingPriceContext"}}},"BillingPriceList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingPrice"}}}},"BillingSubscriptionBase":{"type":"object","description":"Fields shared by every subscription, whether it is a plan or a unit subscription.","required":["id","product","status","billing_interval_count","cancel_at_period_end","billing_anchor","current_period_start","current_period_end","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SubscriptionID"},"product":{"$ref":"#/components/schemas/BillingProductSummary"},"unit_price":{"type":["object","null"],"readOnly":true,"description":"Resolved price per unit. For plan subscriptions, the agreed price. For unit subscriptions, resolved from rate cards.","allOf":[{"$ref":"#/components/schemas/Money"}]},"billing_interval":{"type":["string","null"],"enum":["month","year"],"readOnly":true,"description":"The unit `billing_interval_count` counts, and the unit this subscription's `unit_price` is quoted in. A unit subscription takes it from its product's billing configuration, so it is `month` for one priced from a monthly rate card and `year` for one priced from an annual rate.","example":"month"},"billing_interval_count":{"type":"integer","minimum":1,"readOnly":true,"description":"Number of `billing_interval`s one period covers, so 6 on a monthly subscription bills every six months. `unit_price` is the price for ONE interval, so the amount charged per period is `unit_price` × `quantity` × this value.","example":1},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["scheduled","incomplete","trialing","active","past_due","paused","non_renewing","canceled"],"description":"Where the subscription is in its lifecycle.\n\n- `scheduled`: it is queued to start at a future date, such as a downgrade\n  booked for the next billing period.\n- `incomplete`: it has not finished its initial setup.\n- `trialing`: it is in a trial period.\n- `active`: it bills and renews normally.\n- `past_due`: its latest renewal charge failed; overage is disabled until\n  it either recovers to `active` or is canceled.\n- `paused`: its billing is paused.\n- `non_renewing`: it is scheduled to end at the close of the current period\n  but stays fully usable until then, and can still be resumed.\n- `canceled`: terminal. The subscription no longer bills or renews.\n","example":"active"},"cancel_at_period_end":{"type":"boolean","readOnly":true,"description":"When `true`, the subscription is scheduled to end at the close of the current billing period."},"billing_anchor":{"type":"string","minLength":1,"format":"date","readOnly":true,"description":"The day of the month (and, for a yearly interval, the month) every period boundary aligns to. Stays fixed across most plan changes; an upgrade from a free plan re-anchors it to the upgrade date.","example":"2026-01-15"},"current_period_start":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Start of the subscription's current billing cycle."},"current_period_end":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"End of the subscription's current billing cycle, recomputed against `billing_anchor` at each renewal."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"BillingPlanSubscription":{"description":"A plan subscription with plan-specific detail.","allOf":[{"$ref":"#/components/schemas/BillingSubscriptionBase"},{"type":"object","properties":{"tier":{"type":"string","minLength":1,"readOnly":true,"description":"The plan family this subscription is on, such as `growth` or `pro`, independent of billing interval.","example":"pro"},"trial_end":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the trial period ends. Null when the subscription has no trial."}}}]},"CreatePlanSubscription":{"type":"object","additionalProperties":false,"required":["product_id"],"properties":{"product_id":{"description":"ID of the plan product to subscribe to.","$ref":"#/components/schemas/ProductID"}}},"ChangePlan":{"type":"object","additionalProperties":false,"required":["product_id"],"properties":{"product_id":{"description":"ID of the plan product to change to.","$ref":"#/components/schemas/ProductID"}}},"BillingPlanChangeResult":{"type":"object","additionalProperties":false,"required":["subscription","upgrade_status"],"properties":{"subscription":{"$ref":"#/components/schemas/BillingPlanSubscription"},"upgrade_status":{"type":"string","minLength":1,"readOnly":true,"description":"Outcome of the plan change.\n\n- `upgraded`: the change applied immediately.\n- `scheduled`: identifies a downgrade that takes effect at the period end.\n- `pending_topup`: the wallet was short and a top-up against your saved card\n  is processing.\n- `requires_topup`: the wallet was short and no saved card is available. A\n  top-up is required to complete that upgrade.\n","enum":["upgraded","scheduled","pending_topup","requires_topup"],"example":"upgraded"},"pending_operation_id":{"type":["string","null"],"readOnly":true,"description":"Identifier of the pending upgrade operation. Present for `pending_topup` and `requires_topup`.","example":"wpo_01krdgeqcxet5s7t44vh8rt9mg"},"shortfall_amount":{"type":["object","null"],"readOnly":true,"description":"Amount still needed to fund the upgrade. Present for `pending_topup` and `requires_topup`.","allOf":[{"$ref":"#/components/schemas/Money"}]},"client_secret":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Provider client secret for completing a payment challenge in the browser, when one is required."},"charged_gross":{"readOnly":true,"description":"Total amount charged today (net + tax). Present only for synchronous upgrades (`upgraded` status) where a non-zero charge was applied.","oneOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]}}},"BillingPendingOperation":{"type":"object","additionalProperties":false,"required":["id","operation_type","status","quoted_amount","shortfall_amount","currency_code","expires_at","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WalletPendingOperationID"},"operation_type":{"type":"string","minLength":1,"readOnly":true,"description":"The kind of wallet-funded operation awaiting funds.","enum":["plan_upgrade","dedicated_ip_create"],"example":"plan_upgrade"},"status":{"type":"string","minLength":1,"readOnly":true,"description":"Lifecycle state of the operation.\n\n- `pending_funds`: it is parked until the wallet balance covers\n  `shortfall_amount`.\n- `executing`: it is being applied now that funds are available.\n- `succeeded`: it was applied.\n- `failed`: it ran but could not be applied.\n- `expired`: its funding window closed while it was still waiting for funds.\n- `canceled`: it was called off before it ran.\n","enum":["pending_funds","executing","succeeded","failed","expired","canceled"],"example":"pending_funds"},"quoted_amount":{"type":"string","minLength":1,"readOnly":true,"description":"Gross amount quoted when the operation was created, in currency units. This is the cap on the eventual charge.","example":"2.90"},"quoted_net_amount":{"type":["string","null"],"readOnly":true,"description":"Pre-tax (net) amount behind the quoted amount, in currency units. Present for a plan upgrade so the funding top-up can show a net plus tax breakdown.","example":"2.40"},"quoted_tax_amount":{"type":["string","null"],"readOnly":true,"description":"Tax amount behind the quoted amount (quoted amount minus net), in currency units. Present for a plan upgrade.","example":"0.50"},"quoted_tax_rate":{"type":["string","null"],"readOnly":true,"description":"Tax rate applied to the net amount, as a decimal fraction (for example `0.21` for 21%). Present for a plan upgrade.","example":"0.21"},"shortfall_amount":{"type":"string","minLength":1,"readOnly":true,"description":"Amount still needed to fund this operation, in currency units. Top up this much and the operation completes on its own. It is not `quoted_amount` minus `balance`: funds already committed to usage in flight cannot pay for this, so use this figure rather than deriving one. Treat it as the amount required rather than the exact final charge, which can come out lower if the price changed while the operation was waiting. `0.00` once the wallet covers it.","example":"55.00"},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code of the operation.","example":"USD"},"failure_reason":{"type":["string","null"],"readOnly":true,"description":"Why the operation failed. Present only when status is failed."},"expires_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the operation expires if it is still awaiting funds."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"BillingPendingOperationList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"The organization's wallet-funded operations, newest first.","items":{"$ref":"#/components/schemas/BillingPendingOperation"}}}},"BillingSubscription":{"description":"A plan or unit subscription with its resolved price.","allOf":[{"$ref":"#/components/schemas/BillingSubscriptionBase"},{"type":"object","required":["quantity"],"properties":{"workspace_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/WorkspaceID"},{"type":"null"}],"description":"The workspace this subscription is scoped to. Null for an organization-wide plan subscription."},"ref_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"UUID of the specific resource this subscription provisions, such as a dedicated IP. Null for a plan subscription."},"quantity":{"type":"integer","readOnly":true,"description":"Quantity subscribed to. Always `1` for a plan subscription; can be more for a unit subscription that covers several of the same resource.","example":1},"dimensions":{"type":"object","additionalProperties":{"type":"string"},"readOnly":true,"description":"Pricing dimensions for this subscription."}}}]},"BillingSubscriptionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingSubscription"}}}},"UsageBreakdownType":{"type":"string","x-extensible-enum":["daily","country","domain"],"description":"A usage breakdown dimension.\n\n- `daily`: per-day buckets.\n- `country`: per-country breakdown (rate-card products only).\n- `domain`: per-sending-domain breakdown (email products only).\n\nNew values may be added over time; unrecognized values are silently ignored.\n","example":"daily"},"BillingPricingModel":{"type":"string","minLength":1,"enum":["plan","metered","rate_card","unit_subscription","custom","counter"],"description":"How this product is priced.\n\n- `plan`: it comes with the organization's subscribed plan, with usage tracked\n  against that plan's included allowance.\n- `metered`: it is billed per unit beyond any included allowance.\n- `rate_card`: the per-unit price varies by attributes of the usage, such as\n  destination, instead of being flat.\n- `unit_subscription`: it is a standalone add-on billed as its own recurring\n  subscription, independent of the plan.\n- `custom`: a pricing arrangement that does not fit the other models.\n- `counter`: usage is recorded without enforcing a spending cap or blocking\n  requests.\n","example":"rate_card"},"BillingInterval":{"type":"string","minLength":1,"enum":["month","year"],"description":"The recurring interval one subscription charge covers.","example":"month"},"BillingResolvedPrice":{"type":"object","additionalProperties":false,"required":["unit_price","tax_rate","tax_amount","gross_price","billing_unit_size","dimensions","name","pricing_model"],"properties":{"unit_price":{"$ref":"#/components/schemas/Money","description":"The net unit price, before tax."},"tax_rate":{"type":"string","minLength":1,"description":"The tax rate applied to this price for your organization, as a decimal (for example `0.21` for 21%). `0` if your organization is tax-exempt or no tax applies.","example":"0.21"},"tax_amount":{"$ref":"#/components/schemas/Money","description":"The tax charged on one unit, computed from `unit_price` and `tax_rate`."},"gross_price":{"$ref":"#/components/schemas/Money","description":"The total your organization is charged per unit, that is, `unit_price` plus `tax_amount`."},"billing_unit_size":{"type":"integer","format":"int64","minimum":1,"description":"Number of units that form one chargeable block. Overage usage is ceiling-rounded to the nearest multiple of this value before the unit price is applied. A value of 1 charges per individual unit.","example":1000},"increment":{"type":["string","null"],"description":"The billing rounding window for time-based products, expressed as `{initial_seconds}/{continuation_seconds}`. For example, `60/60` means each unit covers 60 seconds. `null` for per-unit products.","example":"60/60"},"dimensions":{"type":"object","additionalProperties":{"type":"string"},"description":"Pricing dimensions that determined this price (for example, `country_code`, `sender_type`).","example":{"country_code":"US"}},"name":{"type":"string","minLength":1,"description":"Display name of the product this price was resolved for.","example":"10DLC Brand Registration"},"external_description":{"readOnly":true,"type":["string","null"],"description":"Customer-facing description of the product this price was resolved for, when set."},"pricing_model":{"$ref":"#/components/schemas/BillingPricingModel"},"billing_interval":{"$ref":"#/components/schemas/BillingInterval","readOnly":true,"description":"The unit `billing_interval_count` counts. Only present when `pricing_model` is `plan` or `unit_subscription`; absent for a one-time charge."},"billing_interval_count":{"type":"integer","minimum":1,"readOnly":true,"description":"Number of `billing_interval`s one charge covers. Only present when `pricing_model` is `plan` or `unit_subscription`; absent for a one-time charge.","example":1}}},"BillingWalletCollectionStatus":{"type":"string","minLength":1,"description":"Automatic collection state:\n- `none`: No retained collection run.\n- `in_progress`: Collection is active, though payment may still require customer action.\n- `exhausted`: Automatic retries have stopped and customer action is needed.","enum":["none","in_progress","exhausted"]},"BillingWallet":{"type":"object","readOnly":true,"additionalProperties":false,"required":["id","currency_code","balance","settled_balance","renewal_reserved","renewal_required","collection_status","collection_renewal","collection_required","collection_usage_buffer","collection_deficit","credit_limit","provisional","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/WalletID"},"currency_code":{"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code.","example":"USD"},"balance":{"type":"string","minLength":1,"description":"Balance you can spend, in currency units. Usage is charged as it happens, so this drops within seconds of sending and can be lower than the balance the wallet transactions add up to, which only reflects charges that have settled. Funds held for upcoming renewals are also excluded. Credit headroom is reported separately in `credit_limit`, and postpaid accounts can keep spending past a zero balance up to that limit.","example":"118.00"},"settled_balance":{"type":"string","minLength":1,"description":"Settled wallet balance in currency units, before active reservations are deducted.","example":"150.00"},"renewal_reserved":{"type":"string","minLength":1,"description":"Amount currently held for prepared subscription renewals and scheduled plan changes, in currency units.","example":"25.00"},"renewal_required":{"type":"string","minLength":1,"description":"Total amount required for prepared subscription renewals and scheduled plan changes, including tax, in currency units.","example":"30.00"},"collection_status":{"$ref":"#/components/schemas/BillingWalletCollectionStatus"},"collection_renewal":{"type":"string","minLength":1,"description":"Part of collection_required needed for unfunded renewal fees, in exact currency units.","example":"30.004"},"collection_required":{"type":"string","minLength":1,"description":"Total wallet funding still required by the current automatic collection run, in exact currency units.","example":"42.00"},"collection_usage_buffer":{"type":"string","minLength":1,"description":"Part of collection_required needed for the configured auto-topup amount, in currency units.","example":"10.00"},"collection_deficit":{"type":"string","minLength":1,"description":"Part of collection_required needed to cover the wallet shortfall after reservations and approved credit, in currency units.","example":"2.00"},"credit_limit":{"type":"string","minLength":1,"description":"Credit limit in currency units. `0.00` for prepaid wallets.","example":"0.00"},"provisional":{"type":"boolean","description":"When `true`, the wallet currency has not yet been committed and can be changed once. When `false`, the currency is permanent."},"created_at":{"type":"string","minLength":1,"format":"date-time"},"updated_at":{"type":"string","minLength":1,"format":"date-time"}}},"BillingWalletList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingWallet"}}}},"BillingWalletUpdate":{"type":"object","additionalProperties":false,"required":["currency_code"],"properties":{"currency_code":{"$ref":"#/components/schemas/CurrencyCode","description":"The currency to commit for this wallet. Can only be set while the wallet is provisional.","example":"EUR"}}},"PaymentMethodType":{"type":"string","minLength":1,"x-extensible-enum":["card","applepay","googlepay","ideal","bancontact","wero","trustly","bizum","alipaycn","alipayhk","wechatpay","bank_transfer"],"description":"Payment method for a wallet top-up. `card` completes without leaving the page. `applepay` and `googlepay` complete on the page too, but only where the payer's browser offers the wallet and the serving domain is registered with the provider. Every other method takes the payer elsewhere: most redirect to a bank or wallet page, `wechatpay` returns a QR code to scan. Which methods an organization can use depends on the wallet currency, on where the payer and the business are, and on whether the method has been released, so read the offered list from the wallet's payment-methods endpoint rather than assuming a value listed here applies. Only `card` can be saved and charged later. Treat unrecognized values as payment methods added later rather than errors.\n","example":"card"},"BillingPaymentMethodOffer":{"type":"object","additionalProperties":false,"required":["type","completion","prominence"],"properties":{"type":{"$ref":"#/components/schemas/PaymentMethodType","readOnly":true},"completion":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["dropin","redirect","qrcode"],"description":"How the payer finishes. `dropin` renders the provider's card component in place, `redirect` sends the browser to the provider, and `qrcode` shows a code to scan. Treat unrecognized values as shapes added later.\n","example":"redirect"},"prominence":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["default","more"],"description":"Where to place this method in the picker. `default` methods are shown immediately: the always-offered set, plus anything matching where the payer or the business is. `more` methods still work for the wallet's currency, but nothing about this payer suggested them, so show them behind a disclosure such as \"see more\" rather than up front. The split reflects the payer and the business, not what the method costs. Treat unrecognized values as groupings added later.\n","example":"default"},"matched":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["ip","profile"],"description":"Why this method is offered: `ip` means the payer appears to be in a country that supports it, `profile` means the organization's business address is. Absent for a method offered everywhere its currency allows, such as `card`. A method both signals match reports `ip`.\n","example":"ip"}}},"BillingPaymentMethodList":{"type":"object","additionalProperties":false,"required":["items"],"properties":{"items":{"type":"array","readOnly":true,"description":"Methods this wallet can be topped up with, in the order to present them: card first, then methods matching where the payer is, then methods matching the business address, then bank transfer.\n","items":{"$ref":"#/components/schemas/BillingPaymentMethodOffer"}}}},"BillingPaymentIntentStatus":{"type":"string","minLength":1,"enum":["pending","requires_action","processing","succeeded","blocked","declined","failed","canceled"],"description":"Where this payment is in its lifecycle.\n\n- `pending`: there is no attempt outcome yet.\n- `requires_action`: it needs an additional step, such as authentication,\n  before it can proceed.\n- `processing`: the payment method was captured and the provider has yet to\n  settle it.\n- `succeeded`: it settled and credited the wallet.\n- `blocked`: it failed the provider's risk screen; the intent stays open, so\n  retrying with another payment method can still succeed.\n- `declined`: the last attempt failed, whether the issuer declined it or\n  authentication did not complete; the payment can still be retried,\n  usually with a different card.\n- `failed`: the provider rejected the charge request or an error ended the payment; terminal.\n- `canceled`: it was abandoned before completion.\n","example":"pending"},"BillingWalletPaymentIntentStatus":{"type":"object","additionalProperties":false,"description":"The owner's view of an in-flight or settled payment. Deliberately carries no client secret: unlike BillingPaymentIntent, this is a status read rather than a creation response, and a caller polling it has no business receiving one. qr_code is absent for the same reason a WeChat Pay QR is never persisted: it expires within hours of being issued, and a stale code handed back on a later read would look valid but do nothing.","required":["id","status"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PaymentIntentID"},"status":{"$ref":"#/components/schemas/BillingPaymentIntentStatus","readOnly":true},"payment_method_type":{"$ref":"#/components/schemas/PaymentMethodType","readOnly":true,"description":"The payment method this intent was created for. Absent for an intent created before this field was persisted."},"redirect_url":{"type":"string","format":"uri","readOnly":true,"description":"URL where the payer completes a redirect payment, such as a bank transfer's instruction page. Absent for a method that completes without one, such as card, and for an intent created before this field was persisted."}}},"WalletTopup":{"type":"object","additionalProperties":false,"required":["amount"],"properties":{"amount":{"$ref":"#/components/schemas/Money","description":"Top-up amount."},"payment_method_type":{"$ref":"#/components/schemas/PaymentMethodType","default":"card","description":"Payment method to use for this top-up. Defaults to `card`. Any other value must appear in the wallet's offered payment methods, which depend on the wallet currency and on where the payer and business are."}}},"BillingPaymentIntent":{"type":"object","additionalProperties":false,"required":["id","provider","client_secret","external_intent_id","status"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PaymentIntentID"},"provider":{"type":"string","minLength":1,"readOnly":true,"description":"Payment provider name.","example":"airwallex"},"client_secret":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Client secret for the provider's frontend component."},"external_intent_id":{"type":"string","minLength":1,"readOnly":true,"description":"Provider's payment intent ID, passed to the frontend SDK."},"status":{"$ref":"#/components/schemas/BillingPaymentIntentStatus","readOnly":true},"payment_method_type":{"$ref":"#/components/schemas/PaymentMethodType","readOnly":true,"description":"The payment method this intent was created for."},"redirect_url":{"type":"string","format":"uri","readOnly":true,"description":"URL where the payer completes a redirect payment. The client must navigate the browser there. Absent for `card`, which completes in place, and for `wechatpay`, which returns `qr_code` instead."},"qr_code":{"type":"string","minLength":1,"readOnly":true,"description":"QR payload the payer scans with their wallet app, returned for `wechatpay`. Render it as a QR code image; it is a `weixin:` URI and not a web address, so navigating to it does nothing. Absent for every other method."}}},"BillingWalletTransaction":{"type":"object","additionalProperties":false,"required":["id","type","amount","currency_code","balance_after","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/WalletTransactionID"},"type":{"readOnly":true,"$ref":"#/components/schemas/WalletTransactionType"},"amount":{"type":"string","minLength":1,"readOnly":true,"description":"Gross amount in currency units. Positive for `top_up` and refund, negative for charge. Adjustments may be positive or negative (never zero).\n","example":"50.00"},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code of the wallet this transaction belongs to.","example":"USD"},"net_amount":{"type":["string","null"],"readOnly":true,"description":"Net amount in currency units (amount minus tax). Present for charge and adjustment transactions; `null` for `top_up` and refund.\n"},"tax_amount":{"type":["string","null"],"readOnly":true,"description":"Tax amount in currency units. Present for charge and adjustment transactions; `null` for `top_up` and refund.\n"},"balance_after":{"type":"string","minLength":1,"readOnly":true,"description":"Ledger balance immediately after this transaction, in currency units. This is a settled figure and does not have to match the wallet's current `balance`, which also reflects usage charged since the last transaction settled.","example":"125.00"},"description":{"type":["string","null"],"readOnly":true,"description":"Human-readable description of the transaction.","example":"Manual top-up"},"billing_period":{"type":["string","null"],"format":"date","readOnly":true,"description":"First day of the billing month this transaction belongs to.","example":"2026-05-01"},"channels":{"type":"array","readOnly":true,"description":"Product channel slugs (for example, `sms`, `email`, `verify`) that contributed to this charge. Only present when `type` is `charge`; always absent for `top_up`, refund, and adjustment transactions. When present, may be empty if the charge cannot be attributed to a specific channel.\n","items":{"$ref":"#/components/schemas/ChannelSlug"}},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"BillingWalletTransactionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingWalletTransaction"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"BillingCurrency":{"type":"object","additionalProperties":false,"required":["code","name","symbol","precision","is_default"],"properties":{"code":{"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 three-letter currency code.","example":"EUR"},"name":{"type":"string","minLength":1,"description":"Full display name of the currency.","example":"US Dollar"},"symbol":{"type":"string","minLength":1,"description":"Currency symbol.","example":"$"},"precision":{"type":"integer","description":"Number of decimal places for display.","example":2},"is_default":{"type":"boolean","description":"Whether this is the platform default currency."}}},"BillingCurrencyList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingCurrency"}}}},"BillingInvoice":{"type":"object","additionalProperties":false,"description":"A billing invoice issued to the organization.","required":["id","invoice_number","status","type","currency_code","billing_month","subtotal_net","tax_amount","total_gross","amount_paid","issued_at","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/InvoiceID"},"invoice_number":{"type":"string","minLength":1,"readOnly":true,"description":"Per-business-entity, per-year sequential number (for example, `2600001`)."},"status":{"readOnly":true,"$ref":"#/components/schemas/BillingInvoiceStatus"},"type":{"readOnly":true,"$ref":"#/components/schemas/BillingInvoiceType"},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode"},"billing_month":{"type":"string","minLength":1,"format":"date","readOnly":true,"description":"First moment of the billed calendar month (UTC)."},"subtotal_net":{"type":"string","minLength":1,"readOnly":true,"description":"Decimal amount as a string, in major currency units."},"tax_amount":{"type":"string","minLength":1,"readOnly":true,"description":"Total tax charged, as a decimal string in `currency_code` units."},"total_gross":{"type":"string","minLength":1,"readOnly":true,"description":"The invoiced amount (`subtotal_net` plus `tax_amount`), as a decimal string in `currency_code` units."},"amount_paid":{"type":"string","minLength":1,"readOnly":true,"description":"How much of `total_gross` has been settled so far, as a decimal string in `currency_code` units."},"due_date":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When payment is due. Null for a prepaid organization, which owes nothing by a due date."},"issued_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"paid_at":{"type":["string","null"],"format":"date-time","readOnly":true},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"tax_in_issuer_currency":{"readOnly":true,"description":"Total tax restated in the currency of the entity that issued this invoice, when that differs from `currency_code`. Converted at the exchange rate in force on the issue date. `null` when no restatement applies.","oneOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]}}},"BillingInvoiceList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingInvoice"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"BillingInvoiceDetail":{"type":"object","description":"An invoice with its line items.","allOf":[{"$ref":"#/components/schemas/BillingInvoice"},{"type":"object","required":["line_items"],"properties":{"line_items":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/BillingInvoiceLineItem"}}}}]},"BillingPaymentConsent":{"type":"object","additionalProperties":false,"required":["id","provider","status","is_default","created_at","updated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PaymentConsentID"},"provider":{"type":"string","minLength":1,"readOnly":true,"description":"Payment provider name.","example":"airwallex"},"status":{"type":"string","minLength":1,"readOnly":true,"enum":["pending","verified","disabled","unusable"],"description":"Verification state of this saved payment method.\n\n- `pending`: the card has not finished the provider's verification step yet\n  and cannot be charged.\n- `verified`: it passed verification and can be charged, including for\n  automatic top-ups.\n- `disabled`: it was removed and can no longer be charged.\n- `unusable`: the card network refused a charge and told us not to retry\n  it, so we have stopped charging this card. It has not been removed at\n  the provider. Add a working card to resume automatic charges.\n","example":"verified"},"card_brand":{"type":["string","null"],"readOnly":true,"description":"The card network, such as `visa` or `mastercard`, as reported by the provider. Null while the consent is still `pending`.","example":"visa"},"card_last_four":{"type":["string","null"],"readOnly":true,"description":"Last four digits of the card number.","example":"4242"},"is_default":{"type":"boolean","readOnly":true,"description":"Whether this is the organization's default payment consent."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"BillingPaymentConsentList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingPaymentConsent"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"BillingPaymentConsentUpdate":{"type":"object","additionalProperties":false,"properties":{"is_default":{"type":"boolean","description":"Set to `true` to make this the organization's default payment consent."}}},"BillingPaymentConsentSetup":{"type":"object","additionalProperties":false,"required":["id","provider","client_secret","customer_id"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PaymentConsentID"},"provider":{"type":"string","minLength":1,"readOnly":true,"description":"Payment provider name.","example":"airwallex"},"client_secret":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Consent-scoped client secret for the provider's frontend component. Valid for 60 minutes."},"customer_id":{"type":"string","minLength":1,"readOnly":true,"description":"Provider's customer ID, passed to the frontend SDK."}}},"BillingPaymentReceipt":{"type":"object","additionalProperties":false,"description":"A receipt for a wallet top-up payment.","required":["id","wallet_transaction_id","amount","currency_code","auto_topup","issued_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/PaymentReceiptID"},"wallet_transaction_id":{"readOnly":true,"$ref":"#/components/schemas/WalletTransactionID","description":"ID of the wallet transaction that funded this top-up."},"amount":{"type":"string","minLength":1,"readOnly":true,"description":"Decimal amount as a string, in major currency units."},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode"},"payment_method_type":{"type":["string","null"],"readOnly":true,"description":"How the top-up was paid, such as `card`. Null when the provider did not report a payment method, including for a receipt recovered after a missed payment notification."},"payment_method_summary":{"type":["string","null"],"readOnly":true,"description":"Human-readable payment method summary (for example, `Visa ending in 4242`)."},"provider_payment_id":{"type":["string","null"],"readOnly":true,"description":"External provider reference for reconciliation (for example, Airwallex payment ID)."},"auto_topup":{"type":"boolean","readOnly":true,"description":"Whether this receipt was generated from an automatic top-up."},"issued_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"BillingPaymentReceiptList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingPaymentReceipt"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"BillingProformaDocument":{"type":"object","additionalProperties":false,"description":"A prepayment request for a wallet top-up. Not a tax invoice.","required":["id","document_number","description","amount","issued_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/ProformaDocumentID"},"document_number":{"type":"string","minLength":1,"readOnly":true,"description":"Sequential reference to quote as the bank-transfer payment reference (for example, `PRO-2600001`)."},"description":{"type":"string","minLength":1,"readOnly":true,"description":"What the prepayment is for."},"amount":{"readOnly":true,"description":"Amount requested by this document.","allOf":[{"$ref":"#/components/schemas/Money"}]},"issued_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true}}},"BillingProformaDocumentList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BillingProformaDocument"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"BillingProformaDocumentCreate":{"type":"object","additionalProperties":false,"description":"Request to create a pro forma document for a wallet prepayment.","required":["amount"],"properties":{"amount":{"$ref":"#/components/schemas/Money","description":"Prepayment amount."}}},"BillingTaxProfile":{"type":"object","additionalProperties":false,"description":"The organization's tax profile. Returns default values\n(`tax_country`=`null`, `tax_type`=`none`, `tax_rate`=`0`, `tax_number_status`=`none`)\nif no profile has been configured.\n","required":["configured","tax_type","tax_rate","tax_number_status","tax_exempt"],"properties":{"configured":{"type":"boolean","readOnly":true,"description":"Whether the organization has set up its tax details. `false` until a tax registration number is entered, or the organization confirms it has none.\n"},"tax_country":{"type":["string","null"],"minLength":2,"maxLength":2,"readOnly":true,"description":"ISO-3166 alpha-2 (uppercase). `null` if no profile is configured."},"tax_type":{"type":"string","minLength":1,"readOnly":true,"enum":["none","vat","sales_tax","gst","vat_reverse_charge","exempt"],"description":"The tax treatment applied to invoices.\n\n- `none`: we charge no tax because no rate applies to the organization's\n  country.\n- `vat`: we charge standard-rate VAT.\n- `sales_tax`: US sales tax, resolved by jurisdiction and product category.\n- `gst`: goods and services tax.\n- `vat_reverse_charge`: we charge no VAT and the customer self-assesses it\n  instead, which requires a validated tax number.\n- `exempt`: we charge no tax at all.\n"},"tax_rate":{"type":"string","minLength":1,"readOnly":true,"description":"Decimal-as-string (for example, `0.21` for 21%)."},"tax_number":{"type":["string","null"],"readOnly":true,"description":"The organization's tax registration number, if one is on file."},"tax_number_status":{"type":"string","minLength":1,"readOnly":true,"enum":["none","pending","valid","invalid","admin_override"],"description":"Validation state of `tax_number`.\n\n- `none`: no number is on file.\n- `pending`: one was submitted and is awaiting validation.\n- `valid`: it was confirmed and can support `vat_reverse_charge`.\n- `invalid`: it failed validation; see `tax_number_validation_msg` for why.\n- `admin_override`: the tax treatment was pinned manually, bypassing normal\n  validation.\n"},"tax_number_validated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When `tax_number_status` was last set to `valid` or `invalid`. Null if never validated."},"tax_number_validation_msg":{"type":["string","null"],"readOnly":true,"description":"Detail on the tax number's validation state. Null once the number is `valid`; otherwise a rejection reason or an informational note about validation in progress.\n"},"tax_exempt":{"type":"boolean","readOnly":true,"description":"Whether tax is waived regardless of country or registration status. When `true`, it overrides `tax_type` to `exempt` and `tax_rate` to `0`. Set manually rather than through automatic tax resolution."},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true}}},"BillingOverageStatus":{"type":"string","minLength":1,"description":"The organization's effective overage posture:\n\n- `billed`: usage past the plan's included volume is billed per unit.\n- `capped_by_preference`: the organization turned overage off, so sending stops\n  at the included volume.\n- `capped_no_rate_card`: the plan bills no overage at all.\n- `capped_past_due`: a failed renewal payment is holding overage off.\n- `capped_unfunded`: sending stops at the included volume because wallet funding\n  is insufficient or cannot be confirmed. Add funds if the wallet is short;\n  overage resumes once sufficient funding is confirmed.\n- `capped_ceiling`: overage is billed, up to a configured multiple of the\n  included volume.\n- `uncapped_no_plan`: no active plan applies, so nothing is capped.\n","enum":["billed","capped_by_preference","capped_no_rate_card","capped_past_due","capped_unfunded","capped_ceiling","uncapped_no_plan"],"example":"billed"},"BillingNotificationSettings":{"type":"object","additionalProperties":false,"description":"An organization's wallet notification settings: automatic top-up configuration\nand the low-balance alert threshold. Returns default values\n(`auto_topup_enabled`=`false`, amounts `null`) when no settings have been configured.\n","required":["auto_topup_enabled","currency_code","overage_enabled","overage_status"],"properties":{"low_balance_threshold":{"type":["string","null"],"readOnly":true,"description":"Balance at or below which a low-balance alert email is sent, as a decimal string in currency\nunits (for example, `100.00`). Compared against the settled balance the wallet transactions add up\nto, so an alert follows the charges that have settled rather than usage still in flight.\n`null` when the alert is off.\n"},"auto_topup_enabled":{"type":"boolean","readOnly":true,"description":"Whether the wallet is automatically topped up when its balance falls below\nthe trigger threshold.\n"},"auto_topup_threshold":{"type":["string","null"],"readOnly":true,"description":"Balance that triggers an automatic top-up, as a decimal string in currency units. Compared\nagainst the settled balance, the same as `low_balance_threshold`. `null` when automatic top-up\nis off.\n"},"auto_topup_amount":{"type":["string","null"],"readOnly":true,"description":"Amount added to the wallet on each automatic top-up, as a decimal string in\ncurrency units. `null` when automatic top-up is off.\n"},"consent_id":{"readOnly":true,"description":"Saved card charged for automatic top-ups. `null` uses the organization's\ndefault card.\n","oneOf":[{"$ref":"#/components/schemas/PaymentConsentID"},{"type":"null"}]},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code the amounts are expressed in."},"overage_enabled":{"type":"boolean","readOnly":true,"description":"Whether usage past the plan's included volume is billed as overage. `false` means\nsending stops at the included volume. Defaults to `true`.\n"},"overage_status":{"readOnly":true,"$ref":"#/components/schemas/BillingOverageStatus","description":"Why overage is or is not being billed right now, accounting for the plan and any\nfailed renewal as well as this setting.\n"},"overage_included_units":{"type":["integer","null"],"format":"int64","readOnly":true,"description":"The included volume sending stops at when overage is off. `null` without an active\nplan or when the plan's grant is unlimited.\n"}}},"BillingNotificationSettingsUpdate":{"type":"object","additionalProperties":false,"description":"Fields to update on the organization's wallet notification settings. Omit a\nfield to leave it unchanged; send `null` to clear it. When `auto_topup_enabled` is\n`true`, `auto_topup_threshold` and `auto_topup_amount` must be set (in this request or\nfrom a previous one).\n","properties":{"low_balance_threshold":{"type":["string","null"],"description":"Wallet balance at or below which a low-balance alert email is sent, as a\ndecimal string in currency units. Send `null` to turn the alert off.\n"},"auto_topup_enabled":{"type":"boolean","description":"Enable or disable automatic top-up."},"auto_topup_threshold":{"type":["string","null"],"description":"Wallet balance that triggers an automatic top-up, as a decimal string in\ncurrency units.\n"},"auto_topup_amount":{"type":["string","null"],"description":"Amount added to the wallet on each automatic top-up, as a decimal string in\ncurrency units.\n"},"consent_id":{"description":"Saved card to charge for automatic top-ups. Send `null` to use the\norganization's default card.\n","oneOf":[{"$ref":"#/components/schemas/PaymentConsentID"},{"type":"null"}]},"overage_enabled":{"type":"boolean","description":"Set to `false` to stop at the plan's included volume instead of being billed for\noverage. Set to `true` to allow overage again.\n"}}},"PlatformCurrency":{"type":"object","additionalProperties":false,"required":["code","name","minor_units","available_for_billing"],"properties":{"code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 three-letter currency code.","example":"USD"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Currency display name.","example":"US Dollar"},"minor_units":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of decimal places used by this currency.","example":2},"available_for_billing":{"type":"boolean","readOnly":true,"description":"Whether we accept this currency for billing and invoicing. Only a subset of currencies can be used when configuring billing for an organization.\n","example":true}}},"PlatformCurrencyList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/PlatformCurrency"},"description":"All supported currencies."}}},"PlatformCountry":{"type":"object","additionalProperties":false,"required":["code","name"],"properties":{"code":{"type":"string","minLength":2,"maxLength":2,"readOnly":true,"description":"ISO 3166-1 alpha-2 country code.","example":"NL"},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Country display name.","example":"Netherlands"}}},"PlatformCountryList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/PlatformCountry"},"description":"All supported countries."}}},"DemoRequest":{"type":"object","additionalProperties":false,"required":["name","email"],"properties":{"name":{"type":"string","minLength":1,"description":"Full name of the person requesting a demo."},"email":{"type":"string","format":"email","minLength":1,"description":"Work email address."},"phone_number":{"type":["string","null"],"description":"Phone number in E.164 format."},"products":{"type":"array","items":{"type":"string","minLength":1},"description":"Products the requester is interested in."},"volume":{"type":["string","null"],"description":"Expected monthly message volume range."},"source_detail":{"type":["string","null"],"description":"Page or campaign the form was submitted from."}}},"DemoContactValidationRequest":{"type":"object","additionalProperties":false,"required":["email","phone_number"],"properties":{"email":{"type":"string","minLength":1,"maxLength":254,"description":"Work email address to check."},"phone_number":{"type":"string","minLength":1,"maxLength":32,"description":"International phone number to check."}}},"DemoContactStatus":{"type":"string","minLength":1,"enum":["valid","invalid","unconfirmed"],"description":"Whether the contact is suitable, unsuitable, or could not be confirmed."},"DemoContactCheck":{"type":"object","readOnly":true,"additionalProperties":false,"required":["status"],"properties":{"status":{"$ref":"#/components/schemas/DemoContactStatus"},"reason":{"type":"string","description":"Reason for the validation result."},"suggestion":{"type":"string","description":"Suggested correction for the requester to review."}}},"DemoContactValidation":{"type":"object","readOnly":true,"additionalProperties":false,"required":["email","phone","can_submit"],"properties":{"email":{"$ref":"#/components/schemas/DemoContactCheck"},"phone":{"$ref":"#/components/schemas/DemoContactCheck"},"can_submit":{"type":"boolean","description":"Whether neither contact has been identified as unsuitable. Unconfirmed contacts can be submitted for follow-up."}}},"DemoConversationRequest":{"type":"object","additionalProperties":false,"required":["name","email","phone_number","messaging_consent","consent_version"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Full name of the person requesting a demo."},"email":{"type":"string","minLength":1,"maxLength":254,"description":"Work email address for the confirmation and replies."},"phone_number":{"type":"string","minLength":1,"maxLength":32,"description":"Phone number in E.164 format."},"products":{"type":"array","maxItems":20,"items":{"type":"string","minLength":1,"maxLength":80},"description":"Products the requester is interested in."},"volume":{"type":"string","maxLength":100,"description":"Expected monthly message volume range."},"source_detail":{"type":"string","maxLength":2048,"description":"Page or campaign the form was submitted from."},"messaging_consent":{"type":"boolean","description":"Whether the requester agreed to WhatsApp and SMS follow-up about this inquiry."},"consent_version":{"type":"string","minLength":1,"maxLength":80,"description":"Version of the messaging disclosure shown to the requester. Use demo-sales-v1."}}},"DemoConversationChannel":{"type":"string","minLength":1,"enum":["email","whatsapp","sms","web"],"description":"Channel carrying the conversation message."},"DemoConversationDirection":{"type":"string","minLength":1,"enum":["inbound","outbound"],"description":"Whether the requester or Bird sent the conversation message."},"DemoConversationMessageState":{"type":"string","minLength":1,"enum":["queued","ambiguous","accepted","delivered","failed","suppressed","received"],"description":"Current delivery or transcript state:\n- `queued`: waiting for a send attempt or channel readiness.\n- `ambiguous`: provider acceptance could not be confirmed; reconciliation is required before another attempt.\n- `accepted`: the provider accepted the message, but delivery is not yet confirmed.\n- `delivered`: the provider confirmed delivery.\n- `failed`: the provider reported a terminal failure.\n- `suppressed`: the message will not be sent because automation or phone messaging was stopped.\n- `received`: an inbound reply or authoritative staff message recorded in the conversation.\n"},"DemoConversationMessage":{"type":"object","readOnly":true,"additionalProperties":false,"required":["key","channel","direction","body","state","created_at"],"properties":{"key":{"type":"string","minLength":1,"description":"Stable message key within this inquiry."},"channel":{"$ref":"#/components/schemas/DemoConversationChannel"},"direction":{"$ref":"#/components/schemas/DemoConversationDirection"},"body":{"type":"string","minLength":0,"description":"Text of a web reply. Email, WhatsApp and SMS entries carry delivery state with an empty body; entering a contact does not grant access to that contact's replies."},"state":{"$ref":"#/components/schemas/DemoConversationMessageState"},"created_at":{"type":"string","minLength":1,"format":"date-time","description":"When the message was recorded."}}},"DemoConversation":{"type":"object","readOnly":true,"additionalProperties":false,"required":["id","status","validation","messaging_consent","phone_suppressed","messages","expires_at"],"properties":{"id":{"$ref":"#/components/schemas/AcquisitionID"},"status":{"$ref":"#/components/schemas/DemoConversationStatus","description":"Current state of the sales conversation."},"validation":{"$ref":"#/components/schemas/DemoContactValidation"},"messaging_consent":{"type":"boolean","description":"Whether phone messaging was requested for this inquiry."},"phone_suppressed":{"type":"boolean","description":"Whether further automated phone messages are stopped."},"messages":{"type":"array","items":{"$ref":"#/components/schemas/DemoConversationMessage"},"description":"Web replies and outbound channel delivery summaries. Replies received by email, WhatsApp or SMS remain private to those channels and the sales team."},"expires_at":{"type":"string","minLength":1,"format":"date-time","description":"When access to this inquiry expires."}}},"DemoConversationAcceptance":{"type":"object","readOnly":true,"additionalProperties":false,"required":["token","expires_at","conversation"],"properties":{"token":{"type":"string","minLength":1,"x-sensitive":true,"description":"Private capability to read and reply to this inquiry. Keep it server-side."},"expires_at":{"type":"string","minLength":1,"format":"date-time","description":"When the private capability expires."},"conversation":{"$ref":"#/components/schemas/DemoConversation"}}},"DemoConversationReplyRequest":{"type":"object","additionalProperties":false,"required":["body","client_key"],"properties":{"body":{"type":"string","minLength":1,"maxLength":4000,"description":"Reply to the sales conversation."},"client_key":{"type":"string","minLength":1,"maxLength":128,"description":"Stable key for this reply. Retry with the same key and body to avoid duplicates."}}},"SIPTrunkID":{"type":"string","minLength":1,"pattern":"^spt_[0-9a-hjkmnp-tv-z]{26}$","example":"spt_01krdgeqcxet5s7t44vh8rt9mg"},"SIPTrunkACLID":{"type":"string","minLength":1,"pattern":"^sta_[0-9a-hjkmnp-tv-z]{26}$","example":"sta_01krdgeqcxet5s7t44vh8rt9mg"},"VoiceTrunkIPACL":{"type":"object","additionalProperties":false,"required":["id","trunk_id","cidr","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SIPTrunkACLID","description":"Unique identifier for this IP ACL entry."},"trunk_id":{"readOnly":true,"$ref":"#/components/schemas/SIPTrunkID"},"cidr":{"type":"string","minLength":7,"readOnly":true,"description":"IPv4 or IPv6 CIDR block that is allowed to send SIP traffic to this trunk.","example":"203.0.113.0/24"},"description":{"type":["string","null"],"minLength":1,"description":"Optional human-readable label for this ACL entry.","example":"Office network"},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"VoiceSIPDigestAlgorithm":{"type":"string","enum":["SHA-256","MD5"],"description":"A hash algorithm for SIP Digest authentication, spelled as it appears in the `algorithm=` parameter on the wire. `SHA-256` is the stronger option and is offered first; `MD5` is the algorithm most PBX and ITSP equipment implements.\n","example":"SHA-256"},"VoiceTrunkCore":{"type":"object","description":"A SIP trunk's identity and access-control settings.","required":["id","workspace_id","display_name","domain","outbound_enabled","inbound_enabled","media_bypass","ip_acls","allowed_api_key_ids","ineligible_api_key_ids","digest_algorithms","session_credentials_enabled"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/SIPTrunkID","description":"Unique identifier for this SIP trunk."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"display_name":{"type":"string","minLength":1,"description":"A human-readable label for this SIP trunk. Mutable, and distinct from the generated wire domain.","example":"Production PBX trunk"},"domain":{"type":"string","minLength":1,"readOnly":true,"description":"Full SIP address for this trunk, generated as `{trunk-id}.trunk.{region}.sip.bird.com`. This is the trunk's identity, so configure your PBX or SIP client to send calls to this address. It is derived from the trunk id and cannot be chosen or changed.\n","example":"01kxp5bb9qf878642atrf0xy5r.trunk.eu1.sip.bird.com"},"outbound_enabled":{"type":"boolean","readOnly":true,"description":"Whether this trunk may place calls: your PBX connects to us to dial out. Off on a new trunk. While it is off the trunk refuses every call attempt no matter what its allow lists say, and the connection and authentication settings below have no effect. Set `outbound_enabled` through the trunk update operation.\n"},"inbound_enabled":{"type":"boolean","readOnly":true,"description":"Whether this trunk may receive calls: we dial the addresses you declared, for the numbers this trunk answers. Off on a new trunk. Turning it off releases every number the trunk answers, so those numbers become available to other trunks and are not restored by turning it back on. Set `inbound_enabled` through the trunk update operation.\n"},"media_bypass":{"type":"boolean","readOnly":true,"description":"Whether we take ourselves out of the audio path for calls we forward to this trunk: your equipment and the originating carrier exchange audio directly, and only the call signalling passes through us. Off by default. It applies to inbound calls alone (calls this trunk places are always carried through us, whatever this says). While it is on we cannot record those calls, report their audio quality, or end one because its audio stopped. Your equipment must be reachable for audio from the public internet. Set `media_bypass` through the trunk update operation.\n"},"ip_acls":{"type":"array","readOnly":true,"description":"The trunk's IP allow list. IP filtering is active whenever this has at least one entry: only those CIDR ranges may send SIP traffic. An empty list means no IP restriction. Replace the whole `ip_acls` list through the trunk update operation.\n","items":{"$ref":"#/components/schemas/VoiceTrunkIPACL"}},"allowed_api_key_ids":{"type":"array","readOnly":true,"description":"The API keys allowed to authenticate this trunk over SIP Digest. A key must hold `voice` at write level to authenticate, and `ineligible_api_key_ids` names the entries that currently cannot. API-key authentication is active whenever this list has at least one entry: only those keys may authenticate. An empty list means no API-key authentication (mirror of the IP allow list). A trunk with an empty IP allow list and no allowed keys accepts nothing (deny-by-default). Replace the whole `allowed_api_key_ids` list through the trunk update operation.\n","items":{"$ref":"#/components/schemas/APIKeyID"}},"ineligible_api_key_ids":{"type":"array","readOnly":true,"description":"The entries in `allowed_api_key_ids` that cannot authenticate this trunk right now, because the key lost the voice permission or has expired. We keep the entry rather than dropping it: a key that lost the permission authenticates again as soon as you add it back, with the secret it already has and no change to this trunk. Empty when every allowed key can authenticate.\n","items":{"$ref":"#/components/schemas/APIKeyID"}},"digest_algorithms":{"type":"array","readOnly":true,"minItems":1,"description":"The Digest hash algorithms this trunk offers, in the order they are offered. We send one challenge line per algorithm and your PBX answers with the first it supports, so the order decides what most equipment picks. Always populated: a trunk with no explicit setting reports the default, `[\"SHA-256\", \"MD5\"]`. A trunk answering with an algorithm that is not on this list is rejected, so narrowing the list also narrows what the trunk accepts. Replace `digest_algorithms` through the trunk update operation.\n","items":{"$ref":"#/components/schemas/VoiceSIPDigestAlgorithm"},"example":["SHA-256","MD5"]},"session_credentials_enabled":{"type":"boolean","readOnly":true,"description":"Whether a session credential may be used to connect to this trunk from a web browser, the CLI or MCP, alongside whatever the allow lists admit. Off by default. It grants nothing on its own: a call still has to present a credential issued to this workspace, and each one expires within minutes. Set `session_credentials_enabled` through the trunk update operation.\n"}}},"VoiceTrunkRouting":{"type":"object","description":"Whether the trunk's workspace can currently route outbound calls.","required":["routing_configured"],"properties":{"routing_configured":{"type":"boolean","readOnly":true,"description":"Whether outbound routing is configured for this trunk's workspace: `true` once the workspace's dial plan has at least one rule backed by an active interconnect, so calls placed through this trunk can be routed. A new trunk reports `false` until routing is configured. This is workspace-level state, so every trunk in a workspace reports the same value.\n","example":false}}},"VoiceTrunk":{"allOf":[{"$ref":"#/components/schemas/VoiceTrunkCore"},{"$ref":"#/components/schemas/VoiceTrunkRouting"},{"$ref":"#/components/schemas/Timestamps"}]},"VoiceTrunkList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceTrunk"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"VoiceTrunkCreate":{"type":"object","additionalProperties":false,"required":["display_name"],"properties":{"display_name":{"type":"string","minLength":1,"description":"A human-readable label for this SIP trunk. Mutable, and distinct from the generated wire domain.","example":"Production PBX trunk"},"outbound_enabled":{"type":"boolean","default":false,"description":"Whether the new trunk may place calls. Omit it to create a trunk that does neither direction yet, and enable the ones you want once you know what the trunk is for. The settings below configure outbound, so send this as `true` alongside them.\n","example":true},"inbound_enabled":{"type":"boolean","default":false,"description":"Whether the new trunk may receive calls. Omit it to create a trunk that does neither direction yet. A trunk receives no calls until it also has at least one gateway and at least one number, both added after create.\n","example":true},"media_bypass":{"type":"boolean","default":false,"description":"Whether we take ourselves out of the audio path for calls we forward to this trunk. Omit it to create the trunk with this off, which is what suits equipment behind NAT and any account that wants call recording. It is an inbound setting, so `true` is accepted only alongside `inbound_enabled: true`; `false` is always accepted. It can be changed later.\n","example":true},"digest_algorithms":{"type":"array","minItems":1,"description":"The Digest hash algorithms to offer, in the order they should be offered. Omit this to use the default of `[\"SHA-256\", \"MD5\"]`, which suits most equipment. Send `[\"MD5\"]` for a PBX that only implements MD5 and rejects or ignores a challenge offering SHA-256 first. This can be changed later without re-issuing credentials.\n","items":{"$ref":"#/components/schemas/VoiceSIPDigestAlgorithm"},"example":["MD5"]},"session_credentials_enabled":{"type":"boolean","default":false,"description":"Whether a session credential may be used to connect to this trunk from a web browser, the CLI or MCP. Omit it to create the trunk with this off, which is what a trunk reached only by a PBX wants. It can be changed later.\n","example":true}}},"VoiceTrunkIPACLCreate":{"type":"object","additionalProperties":false,"required":["cidr"],"properties":{"cidr":{"type":"string","minLength":7,"description":"IPv4 or IPv6 CIDR block to allow. Use /32 for a single IPv4 address or /128 for a single IPv6 address.","example":"203.0.113.0/24"},"description":{"type":"string","minLength":1,"description":"Optional human-readable label for this ACL entry.","example":"Office network"}}},"VoiceTrunkUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"description":"A change to the trunk's directions, name, access control, and Digest algorithm\noffer. Every field is optional. An omitted field is left unchanged. The\n`ip_acls`, `allowed_api_key_ids`, and `digest_algorithms` fields each replace\ntheir whole list when present. Send an empty array to clear one. This lets you\ncommit the trunk's entire access configuration in one update.\n\nA direction has to be enabled before its settings can be set, but one update\ncan do both: enable a direction and send its configuration together.\n","properties":{"display_name":{"type":"string","minLength":1,"description":"A human-readable label for this SIP trunk. Mutable, and distinct from the generated wire domain.","example":"Production PBX trunk"},"outbound_enabled":{"type":"boolean","description":"Whether this trunk may place calls. Turning it off stops the trunk admitting call attempts at the next call setup and leaves its connection and authentication settings stored, so turning it back on restores a working trunk. Omit the field to leave it unchanged.\n","example":true},"inbound_enabled":{"type":"boolean","description":"Whether this trunk may receive calls. Turning it off releases every number this trunk answers: those numbers become available to other trunks, and turning inbound back on does not reclaim them. The gateways are kept. Omit the field to leave it unchanged.\n","example":true},"media_bypass":{"type":"boolean","description":"Whether we take ourselves out of the audio path for calls we forward to this trunk. Turning it on takes effect at the next call setup and leaves calls already up untouched. It is an inbound setting, so the trunk must have `inbound_enabled` on; one update can do both. While it is on we cannot record those calls, report their audio quality, or end one because its audio stopped, and your equipment must be reachable for audio from the public internet. Turning it off puts us back in the path at the next call setup. Omit the field to leave it unchanged.\n","example":true},"ip_acls":{"type":"array","description":"Replaces the trunk's entire IP allow list. When present, the allow list is set to exactly these CIDR blocks: ranges not listed are removed and new ones are added. Send an empty array to clear the list, turning IP filtering off. Omit the field to leave the allow list unchanged.\n","items":{"$ref":"#/components/schemas/VoiceTrunkIPACLCreate"}},"allowed_api_key_ids":{"type":"array","description":"Replaces the trunk's entire set of allowed API keys. When present, exactly these keys may authenticate the trunk over SIP Digest. Each key you ADD must belong to this workspace and hold `voice` at write level; a key that does not is refused and the whole update is rolled back. A key already on the list that has since lost the permission or expired does not block the update, so you can keep editing the trunk while you put its permission back. A non-empty list turns API-key authentication on; send an empty array to turn it off. Omit the field to leave the allowed keys unchanged.\n","items":{"$ref":"#/components/schemas/APIKeyID"}},"digest_algorithms":{"type":"array","description":"Replaces the Digest hash algorithms this trunk offers, in the order they should be offered. Send `[\"MD5\"]` for a PBX that only implements MD5 and rejects or ignores a challenge offering SHA-256 first. Send an empty array to return to the default of `[\"SHA-256\", \"MD5\"]`. The offer is never empty, because a trunk that offered nothing could not be authenticated at all. Narrowing the list also narrows what the trunk accepts: an answer using an algorithm no longer offered is rejected. Takes effect on the next call setup; no credential is re-issued. Omit the field to leave the offer unchanged.\n","items":{"$ref":"#/components/schemas/VoiceSIPDigestAlgorithm"},"example":["MD5"]},"session_credentials_enabled":{"type":"boolean","description":"Whether a session credential may be used to connect to this trunk from a web browser, the CLI or MCP. Off by default; turning it on does not change what the allow lists admit, and turning it off stops those connections at the next call setup without re-issuing anything. Omit the field to leave it unchanged.\n","example":true}}},"VoiceSessionCredential":{"type":"object","description":"A short-lived SIP digest credential for a calling client. The `password` is returned once and cannot be recovered. Create a new credential if you lose it.\n","additionalProperties":false,"required":["username","password","realm","expires_at"],"properties":{"username":{"type":"string","minLength":1,"maxLength":64,"description":"SIP digest username. Always `bird`. The credential identifies the workspace through `realm`. The username does not identify the workspace.\n","example":"bird"},"password":{"type":"string","minLength":1,"maxLength":128,"x-sensitive":true,"description":"SIP digest password, returned once. Treat it as a bearer secret: until it expires it can place calls billed to this workspace.\n","example":"8Kx2mQ7pR4tYvB9nL3sW6dF1gH5jC0aZ"},"realm":{"type":"string","minLength":1,"maxLength":253,"description":"SIP digest realm to authenticate against. Workspace-scoped, so a credential minted for one workspace cannot authenticate against another.\n","example":"01ARZ3NDEKTSV4RRFFQ69G5FAV.sip.bird.com"},"expires_at":{"type":"string","format":"date-time","minLength":1,"description":"When the credential stops authenticating. Short by design. Renew while a call is up rather than requesting a long-lived credential.\n","example":"2026-07-30T12:05:00Z"},"handshake_token":{"type":"string","minLength":1,"maxLength":4096,"x-sensitive":true,"description":"Short-lived token required when upgrading the WebSocket connection. The token authorizes the connection only; each call still authenticates with `password`.\n","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODU0NDMwMzV9.fp1xWxROEmgCafwiJ-ZHbZg9cIdYC-wLGcH-5gIVbco"}}},"VoiceTrunkGatewayID":{"type":"string","minLength":1,"pattern":"^vtg_[0-9a-hjkmnp-tv-z]{26}$","example":"vtg_01krdgeqcxet5s7t44vh8rt9mg"},"VoiceTrunkGateway":{"description":"One address an inbound call to this trunk is forwarded to, and how that peer wants the call's two numbers spelled. A trunk can have several, tried in priority order until one answers.\n","allOf":[{"type":"object","required":["id","trunk_id","sip_uri","priority","origination_format","destination_format"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceTrunkGatewayID","description":"Unique identifier for this gateway."},"trunk_id":{"readOnly":true,"$ref":"#/components/schemas/SIPTrunkID"},"sip_uri":{"type":"string","minLength":1,"maxLength":512,"description":"SIP URI an inbound call to this trunk is forwarded to. The host only: which number is dialed at that host comes from `destination_format`, because it changes with every call.\n","example":"sip:pbx.example.com:5060"},"priority":{"type":"integer","minimum":0,"description":"The order gateways are tried in, lowest first. Gateways sharing a priority take an equal share of calls, and any of them may be tried first on a given call.\n","example":0},"origination_format":{"type":"string","minLength":1,"maxLength":64,"description":"How the calling number is spelled to this gateway, as a template whose\n`{number}` stands for the number without its leading `+`. It is stated\nin the `P-Asserted-Identity` header of the delivered call.\n\nA gateway that has not asked for anything else reports `+{number}`,\nwhich is E.164. A format with no `{number}` states that same identity on\nevery call, whoever called.\n","example":"+{number}"},"destination_format":{"type":"string","minLength":1,"maxLength":64,"description":"How this gateway formats the dialed number. In the template,\n`{number}` represents the number without its leading `+`. The result\nis placed before the `sip_uri` host. For example, `1234#{number}`\nformats `+31201234567` as\n`sip:1234#31201234567@pbx.example.com:5060`.\n\nA gateway that has not asked for anything else reports `+{number}`,\nwhich is E.164. A format with no `{number}` is dialed as it stands, so\nevery number the trunk answers reaches that one number.\n","example":"+{number}"}}},{"$ref":"#/components/schemas/Timestamps"}]},"VoiceTrunkGatewayList":{"type":"object","additionalProperties":false,"required":["data"],"description":"The trunk's gateways. Not paginated: a trunk holds a small, hand-managed set of dial targets, and the order across the whole set is what decides hunt order, so a partial page would misrepresent it.","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceTrunkGateway"},"description":"The trunk's gateways, in priority order."}}},"VoiceTrunkGatewayCreate":{"type":"object","additionalProperties":false,"required":["sip_uri","priority"],"properties":{"sip_uri":{"type":"string","minLength":1,"maxLength":512,"description":"SIP URI an inbound call to this trunk should be forwarded to. Give the host only, with an optional port: which number is dialed there comes from `destination_format`, so a URI carrying a user part is rejected.\n","example":"sip:pbx.example.com:5060"},"priority":{"type":"integer","minimum":0,"maximum":2147483647,"description":"The order gateways are tried in, lowest first. Give two gateways the same priority to share calls between them evenly.\n","example":0},"origination_format":{"type":"string","maxLength":64,"description":"How this gateway wants the calling number spelled. Write a template whose\n`{number}` stands for the number without its leading `+`; the result is\nstated in the `P-Asserted-Identity` header of the delivered call.\n\nOmit it for E.164, which is `+{number}`. The template may add digits,\nletters and the characters `-_.!~*'()&=+$,;?/%#` around `{number}`, which\nmay appear at most once, and anything else in braces is rejected so a\nmisspelled placeholder cannot reach a call.\n\nA format with no `{number}` at all states the same identity on every call,\nwhich is what a peer that only accepts one authorized number wants. The\ncall then carries nothing about who really called.\n","example":"+{number}"},"destination_format":{"type":"string","maxLength":64,"description":"How this gateway formats the dialed number. In the template, `{number}`\nrepresents the number without its leading `+`. The result is placed before\nthe `sip_uri` host. For example, `1234#{number}` formats `+31201234567` as\n`sip:1234#31201234567@pbx.example.com:5060`.\n\nOmit it for E.164, which is `+{number}`. A format with no `{number}` at all\nsends every number this trunk answers to one fixed number, so\n`777000447973` reaches `sip:777000447973@pbx.example.com:5060` whatever was\ndialed. The same rules as `origination_format` apply to what the template\nmay contain.\n","example":"1234#{number}"}}},"VoiceTrunkGatewayUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"description":"A change to the gateway's address, its place in the order, or how it wants numbers spelled. Every field is optional; an omitted field is left unchanged.\n","properties":{"sip_uri":{"type":"string","minLength":1,"maxLength":512,"description":"SIP URI an inbound call to this trunk should be forwarded to. Give the host only, with an optional port: which number is dialed there comes from `destination_format`, so a URI carrying a user part is rejected.\n","example":"sip:pbx.example.com:5060"},"priority":{"type":"integer","minimum":0,"maximum":2147483647,"description":"The order gateways are tried in, lowest first. Give two gateways the same priority to share calls between them evenly.\n","example":0},"origination_format":{"type":"string","maxLength":64,"description":"How this gateway wants the calling number spelled, as a template whose\n`{number}` stands for the number without its leading `+`. The result is\nstated in the `P-Asserted-Identity` header of the delivered call.\n\nSend an empty string to go back to E.164, which is `+{number}`. The template\nmay add digits, letters and the characters `-_.!~*'()&=+$,;?/%#` around\n`{number}`, which may appear at most once, and anything else in braces is\nrejected so a misspelled placeholder cannot reach a call.\n\nA format with no `{number}` at all states the same identity on every call,\nwhich is what a peer that only accepts one authorized number wants. The\ncall then carries nothing about who really called.\n","example":"+{number}"},"destination_format":{"type":"string","maxLength":64,"description":"How this gateway formats the dialed number. In the template, `{number}`\nrepresents the number without its leading `+`. The result is placed before\nthe `sip_uri` host. For example, `1234#{number}` formats `+31201234567` as\n`sip:1234#31201234567@pbx.example.com:5060`.\n\nSend an empty string to go back to E.164, which is `+{number}`. A format\nwith no `{number}` at all sends every number this trunk answers to one fixed\nnumber, so `777000447973` reaches `sip:777000447973@pbx.example.com:5060`\nwhatever was dialed. The same rules as `origination_format` apply to what\nthe template may contain.\n","example":"1234#{number}"}}},"VoiceNumberID":{"type":"string","minLength":1,"pattern":"^vnu_[0-9a-hjkmnp-tv-z]{26}$","example":"vnu_01krdgeqcxet5s7t44vh8rt9mg"},"VoiceNumberProviderType":{"type":"string","minLength":1,"enum":["allocation","verified_number"],"description":"Where a number came from. `allocation` is a number we allocated to your workspace, and the only kind whose calls reach us. `verified_number` is a number from another carrier that you registered and proved you control, so it can be presented on a call you place.\n","example":"allocation"},"VoiceNumberProviderAllocation":{"type":"object","additionalProperties":false,"required":["type","number_id"],"properties":{"type":{"$ref":"#/components/schemas/VoiceNumberProviderType","description":"A number we allocated to your workspace."},"number_id":{"oneOf":[{"$ref":"#/components/schemas/AllocatedNumberID"},{"type":"null"}],"readOnly":true,"description":"Identifier of this number's allocation, to pass to the numbers operations. Null when the allocation behind this number cannot be resolved.\n"}}},"VoiceCallerIDStatus":{"type":"string","minLength":1,"x-extensible-enum":["pending","verified","failed"],"description":"Verification state of the caller ID.\n\n- `pending`: the number is registered but ownership has not yet been proven.\n- `verified`: the workspace completed the verification call, so the number can\n  be presented as the outbound caller ID.\n- `failed`: terminal because the verification attempt limit was exhausted.\n  Delete the caller ID and create it again to retry.\n\nOpen enum: additional states may be added over time, so treat an unrecognized\nvalue as a future state rather than an error.\n","example":"pending"},"VoiceNumberProviderVerifiedNumber":{"type":"object","additionalProperties":false,"required":["type","status","verified_at"],"properties":{"type":{"$ref":"#/components/schemas/VoiceNumberProviderType","description":"A number from another carrier that you registered here. That carrier decides where calls to it go; we only present it on calls you place.\n"},"status":{"$ref":"#/components/schemas/VoiceCallerIDStatus","readOnly":true,"description":"How far proving control of this number has got."},"verified_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When control of this number was last proven. Null until it is."}}},"VoiceNumberProvider":{"description":"Where this number came from, and the facts that belong to that answer. The type selects the shape. `allocation` is a number we allocated to your workspace, and it carries that allocation's identifier. `verified_number` is a number from another carrier, and it carries how far proving control of it has got.\n","oneOf":[{"$ref":"#/components/schemas/VoiceNumberProviderAllocation"},{"$ref":"#/components/schemas/VoiceNumberProviderVerifiedNumber"}],"discriminator":{"propertyName":"type","mapping":{"allocation":"#/components/schemas/VoiceNumberProviderAllocation","verified_number":"#/components/schemas/VoiceNumberProviderVerifiedNumber"}}},"VoiceNumberDirections":{"type":"object","additionalProperties":false,"required":["inbound","outbound"],"properties":{"inbound":{"type":"boolean","readOnly":true,"description":"Whether calls to this number arrive here. False for a number from another carrier, whose calls that carrier routes, and for one allocated to you that cannot carry calls.\n"},"outbound":{"type":"boolean","readOnly":true,"description":"Whether this number can be presented on a call you place. Buying a number does not grant this on its own: proving control of it does.\n"}}},"VoiceInboundConfigurationError":{"type":"string","minLength":1,"enum":["unsupported_route_type"]},"VoiceCallRouteType":{"type":"string","minLength":1,"enum":["reject","trunk","forward"],"description":"Which answer a number carries.\n\n- `reject`: refuses the call. This is where every number starts.\n- `trunk`: delivers the call to one of your SIP trunks.\n- `forward`: places a call to one of your verified caller IDs and connects the two.\n\nIt selects the answer's own shape, so a new way to answer a call arrives as a\nnew value alongside a new set of fields.\n","example":"reject"},"VoiceCallRouteReject":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"$ref":"#/components/schemas/VoiceCallRouteType","const":"reject","description":"Refuses the call. Every number starts here, and setting it again is how you stop a number answering without giving it up.\n"}}},"VoiceCallRouteTrunk":{"type":"object","additionalProperties":false,"required":["type","trunk_id"],"properties":{"type":{"$ref":"#/components/schemas/VoiceCallRouteType","const":"trunk","description":"Delivers the call to one of your SIP trunks."},"trunk_id":{"$ref":"#/components/schemas/SIPTrunkID","description":"The SIP trunk that answers calls to this number. It must be one of yours and must have inbound calling enabled. Turning that trunk's inbound calling off, or deleting it, puts this number back on \"reject\".\n"}}},"VoiceInboundForwardAs":{"type":"string","minLength":1,"enum":["dialed_number","calling_number"],"description":"Which of a forwarded call's two numbers it shows as the caller.\n\n\"dialed_number\" is the number the caller dialled, which is one of yours.\nCarriers treat it as fully yours, so it is the least likely to be altered or\nscreened. Whoever answers sees which of your numbers was called, not who called\nit. It needs your workspace approved to place calls from numbers you bought from\nus; where it is not, this value is refused and the call shows the calling\nnumber.\n\n\"calling_number\" is the caller's own number, so the phone rings as though they\nhad dialled it directly and the call can be returned from the call log. Because\nthe number is not one you own, some carriers (most often in the US and parts of\nEurope) mark such calls as unverified, replace the number, or screen them.\n","example":"dialed_number"},"VoiceCallRouteForward":{"type":"object","additionalProperties":false,"required":["type","forward_to","forward_as"],"properties":{"type":{"$ref":"#/components/schemas/VoiceCallRouteType","const":"forward","description":"Places a call to another of your numbers and connects the two."},"forward_to":{"type":"string","minLength":1,"description":"The number calls are forwarded to, in E.164 format. It has to be one of your verified caller IDs. That is checked when you set it and again on every call it forwards, so a caller ID you later remove stops forwarding rather than carrying on.\n","example":"+14155551234"},"forward_as":{"$ref":"#/components/schemas/VoiceInboundForwardAs","description":"Which of the forwarded call's two numbers it shows as the caller. State it on every write: there is no default to fall back on, and a number configured before this option existed reads back the value its calls carry rather than nothing.\n\nOn read it is always that value, which can differ from what was last written. Where your workspace is not approved to place calls from numbers you bought from us, a forward shows the calling number whatever it was set to, and this field says so.\n"}}},"VoiceSequenceSlug":{"type":"string","description":"Slugs starting with bird_ are reserved for builtin sequences. You can reference these sequences but cannot create, edit, rename, or delete them.","minLength":1,"maxLength":63,"pattern":"^[a-z0-9](?:[a-z0-9_-]{0,61}[a-z0-9])?$","example":"main-menu"},"VoiceCallRouteSequence":{"type":"object","additionalProperties":false,"required":["type","sequence"],"properties":{"type":{"$ref":"#/components/schemas/VoiceCallRouteType","const":"sequence"},"sequence":{"$ref":"#/components/schemas/VoiceSequenceSlug"}}},"VoiceCallRoute":{"description":"What happens to a call arriving for this number. The type selects the shape: \"reject\" refuses the call, \"trunk\" delivers it to one of your SIP trunks, and \"forward\" places a call to one of your verified caller IDs and connects the two. An unconfigured number uses \"reject\".\n","oneOf":[{"$ref":"#/components/schemas/VoiceCallRouteReject"},{"$ref":"#/components/schemas/VoiceCallRouteTrunk"},{"$ref":"#/components/schemas/VoiceCallRouteForward"}],"discriminator":{"propertyName":"type","mapping":{"reject":"#/components/schemas/VoiceCallRouteReject","trunk":"#/components/schemas/VoiceCallRouteTrunk","forward":"#/components/schemas/VoiceCallRouteForward"}}},"VoiceInboundConfiguration":{"type":"object","additionalProperties":false,"required":["route"],"properties":{"route":{"oneOf":[{"$ref":"#/components/schemas/VoiceCallRoute"},{"type":"null"}],"description":"Null when the stored route type is unsupported; inspect configuration_error before changing it."},"configuration_error":{"$ref":"#/components/schemas/VoiceInboundConfigurationError","readOnly":true},"forward_as_options":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/VoiceInboundForwardAs"},"description":"Which numbers a forward from this number may be set to show as the caller. A value outside this list is refused on write.\n\n\"dialed_number\" is present only where your workspace is approved to place calls from numbers you bought from us.\n","example":["calling_number"]}},"if":{"properties":{"route":{"type":"null"}},"required":["route"]},"then":{"properties":{"configuration_error":{"$ref":"#/components/schemas/VoiceInboundConfigurationError"}},"required":["configuration_error"]},"else":{"properties":{"configuration_error":{"not":{}}}}},"VoiceNumber":{"type":"object","additionalProperties":false,"required":["id","phone_number","country_code","name","provider","directions","inbound_configuration","created_at"],"properties":{"id":{"$ref":"#/components/schemas/VoiceNumberID","readOnly":true,"description":"Identifier of this number, to pass to the operations that read and change it. A number you registered as a caller ID carries the same identifier there, with the caller-ID prefix.\n"},"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"The phone number in E.164 format.","example":"+14155551234"},"country_code":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"readOnly":true,"description":"Country the number belongs to. Null when the number is not geographic or its country cannot be determined.\n"},"name":{"type":["string","null"],"minLength":1,"maxLength":100,"readOnly":true,"description":"Your own label for this number, to tell several apart. Null when it has none. Only you see it, so it never affects what a caller sees.\n","example":"Support line"},"provider":{"$ref":"#/components/schemas/VoiceNumberProvider","readOnly":true},"directions":{"$ref":"#/components/schemas/VoiceNumberDirections","readOnly":true,"description":"Which directions this number can carry. Both follow from the number itself, so neither is yours to change. On a SIP trunk each direction is a setting you turn on; here it is a fact about the number.\n"},"inbound_configuration":{"$ref":"#/components/schemas/VoiceInboundConfiguration","readOnly":true,"description":"What happens to a call arriving for this number."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this number became usable for voice: when it was allocated to you, or when you first registered it, whichever this number is.\n"}}},"VoiceNumberList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceNumber"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"VoiceInboundConfigurationPut":{"type":"object","additionalProperties":false,"required":["route"],"properties":{"route":{"$ref":"#/components/schemas/VoiceCallRoute"}}},"VoiceNumberUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"name":{"type":["string","null"],"minLength":1,"maxLength":100,"description":"Your own label for this number. Send null to remove the one it has. Omit the field to leave it alone.\n","example":"Support line"},"inbound_configuration":{"$ref":"#/components/schemas/VoiceInboundConfigurationPut","description":"What should happen to calls arriving for this number. The route replaces\nwhatever was set before, because a number has exactly one answer at a time,\nand type \"reject\" is how you stop it answering. Omit the field to leave the\nanswer alone.\n\nOnly a number that can receive calls carries a route, so it is refused on\none whose directions do not include inbound.\n"}}},"VoiceCallerIDID":{"type":"string","minLength":1,"pattern":"^vci_[0-9a-hjkmnp-tv-z]{26}$","example":"vci_01krdgeqcxet5s7t44vh8rt9mg"},"VoiceCallerID":{"allOf":[{"type":"object","required":["id","workspace_id","phone_number","name","status","verified_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallerIDID","description":"Unique identifier for this caller ID."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"The phone number in E.164 format registered as a caller ID.","example":"+14155551234"},"name":{"type":["string","null"],"minLength":1,"maxLength":100,"readOnly":true,"description":"Your label for this caller ID, to tell several registered numbers apart. `null` when the caller ID has no label. It is yours to choose and appears nowhere on a call, so changing it never affects what the person you are calling sees. Set it with the caller ID update operation.\n","example":"Support line"},"status":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallerIDStatus"},"verified_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the caller ID was verified. `null` while the caller ID is still pending verification."}}},{"$ref":"#/components/schemas/Timestamps"}]},"VoiceCallerIDList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceCallerID"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"VoiceCallerIDCreate":{"type":"object","additionalProperties":false,"required":["phone_number"],"properties":{"phone_number":{"type":"string","minLength":1,"description":"The phone number to register as a caller ID, in E.164 format (a leading `+` followed by the country code and national number). Must be unique within the workspace. Creating the caller ID starts verification: a verification call is placed to this number.\n","example":"+14155551234"},"name":{"type":"string","minLength":1,"maxLength":100,"description":"Your label for this caller ID, to tell several registered numbers apart. Omit it to register the number without one and add it later. It is yours to choose and appears nowhere on a call, so it never affects what the person you are calling sees.\n","example":"Support line"}}},"VoiceCallerIDUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"description":"A change to the caller ID's label. Every field is optional; an omitted field is left unchanged.\n","properties":{"name":{"type":["string","null"],"minLength":1,"maxLength":100,"description":"Your new label for this caller ID. Send `null` to clear it and go back to identifying the caller ID by its number alone. It is yours to choose and appears nowhere on a call, so renaming never affects what the person you are calling sees, and it leaves the number and its verification untouched.\n","example":"Support line"}}},"VoiceCallerIDVerifyRequest":{"type":"object","additionalProperties":false,"required":["code"],"properties":{"code":{"type":"string","pattern":"^\\d{6}$","minLength":6,"maxLength":6,"x-sensitive":true,"description":"The 6-digit verification code read out by the verification call.","example":"123456"}}},"AdminVoiceRoutingExpressionValidation":{"type":"object","additionalProperties":false,"required":["valid","message"],"properties":{"valid":{"type":"boolean","description":"Whether the expression would be accepted on a rule.","example":false},"message":{"type":["string","null"],"minLength":1,"description":"Why it was rejected, in the same words the create and update endpoints return. Null when the expression is valid.","example":"ERROR: <input>:1:14: undeclared reference to 'ZONE_EAA'"}}},"AdminVoiceCallFlowProbeCall":{"type":"object","additionalProperties":false,"required":["destination_number"],"properties":{"destination_number":{"type":"string","minLength":1,"description":"The dialed number, in E.164 form with or without the leading plus.","example":"+31612345678"},"caller_id":{"type":"string","minLength":1,"description":"The caller ID the call would present. Omit for a call with none.","example":"+31201234567"},"caller_id_verified":{"type":"boolean","default":true,"description":"Whether to treat the caller ID as presentable by this workspace. False models a spoofed or unheld one, which leaves every `src` fact empty and puts the call in the least favourable zone."},"caller_id_provider":{"allOf":[{"$ref":"#/components/schemas/VoiceNumberProviderType"}],"description":"How the right to present the caller ID arose. The live path reads this off the number's own row; the probe looks nothing up, so state it or `src.provider` is empty. Ignored when `caller_id_verified` is false.","example":"allocation"},"caller_id_supplier":{"allOf":[{"$ref":"#/components/schemas/AdminNumbersSupplierSlug"}],"description":"Slug of the supplier that sold the caller ID, as `src.supplier` would carry it. Ignored when `caller_id_verified` is false, and ignored unless `caller_id_provider` is `allocation`: only a number we allocated has a supplier, so a verified number carrying one is a call that cannot happen.","example":"didww"},"caller_id_outbound_routing":{"allOf":[{"$ref":"#/components/schemas/AdminNumberOutboundRouting"}],"description":"Where a call presenting this caller ID must leave by, as `src.outbound_routing` would carry it. Omit to model a number nobody has classified. Ignored on the same terms as `caller_id_supplier`: an unpresentable caller ID, or a provider that is not `allocation`."}}},"AdminVoiceRoutingRuleAction":{"type":"string","minLength":1,"enum":["route","block"],"description":"What a matching call does. `route` sends it to the rule's interconnects; `block` refuses it (a destination ban). Matching is strict either way: a matched blocking rule never falls through to a later rule.","example":"route"},"AdminVoiceRoutingRuleExpression":{"type":"string","minLength":1,"description":"Expression the call must satisfy for this rule to match, written in [CEL](https://github.com/google/cel-spec). It must produce true or false; `true` matches every call.\n\nThe call's facts come in three namespaces. `src` is where the call comes from, `dst` where it is going, and `call` the relation between the two. `src.number`, `src.country` and `src.type` describe the caller ID and are ALL EMPTY unless it was verified, so a spoofed caller ID can never reach a rule. `dst.number`, `dst.country` and `dst.type` describe the dialed number, `call.breakout_code` is the rating key resolved for it, and `call.zone` is the calling zone the two countries put the call in.\n\nNumbers carry NO leading plus: a Dutch mobile is `31612345678`, so a prefix reads `dst.number.startsWith(\"316\")`.\n\nValues are written as declared identifiers, never as quoted strings: `ZONE_DOMESTIC`, `ZONE_EEA` and `ZONE_INTERNATIONAL` for `call.zone`; `TYPE_MOBILE`, `TYPE_FIXED` and `TYPE_TOLL_FREE` for the line types; and every ISO 3166-1 alpha-2 country code, so a country reads `dst.country == DE`. Match a set with `in`, as in `src.country in [US, CA]`. A misspelled identifier is refused when the rule is saved, where a misspelled string would be accepted and then match nothing.\n\n`call.breakout_code` is the destination's rating key, a hyphen-delimited code such as `NL-MOB-KPN` or `NL-PREM`. It can be compared, prefix-matched by whole levels (`call.breakout_code.startsWith(\"NL-MOB-\")`) or tested for a whole segment (`call.breakout_code.hasSegment(\"PREM\")`, which also takes a list), but never taken apart with `contains` or a regex, because those bind a rule to a position inside a code the deck is free to change. A rule that matches on the code refuses the call when the deck cannot be read, rather than treating an unreadable deck as no match.\n\nThe expression is compiled and type-checked on write, so an unknown fact, an unknown identifier, a result that is not a boolean, and an expression too expensive to evaluate on every call are all refused.\n","example":"dst.country == DE && dst.type == TYPE_MOBILE"},"AdminVoiceCallFlowProbeRule":{"type":"object","additionalProperties":false,"required":["expression"],"description":"One rule as it stands in the editor, saved or not. The probe evaluates the set in priority order and stops at the first match, so what it reports is what the plan would do if it were saved as it is.","properties":{"priority":{"type":"integer","default":0,"description":"Lower values are evaluated first. Ties are broken by position in the list.","example":10},"action":{"$ref":"#/components/schemas/AdminVoiceRoutingRuleAction","default":"route","description":"What a match does. Reported back as the call's final action when this rule is the one that matches."},"expression":{"$ref":"#/components/schemas/AdminVoiceRoutingRuleExpression"},"label":{"type":"string","minLength":1,"maxLength":128,"description":"Echoed on the verdict so a caller can name the rule its own way, typically a client-side row key, since an unsaved rule has no id.","example":"rule-3"}}},"AdminVoiceCallFlowProbe":{"type":"object","additionalProperties":false,"required":["calls"],"properties":{"calls":{"type":"array","minItems":1,"maxItems":25,"description":"The calls to resolve.","items":{"$ref":"#/components/schemas/AdminVoiceCallFlowProbeCall"}},"rules":{"type":"array","maxItems":200,"description":"The dial plan's rules to run each call through, in the state the caller holds them, an unsaved edit included. Evaluated in priority order, first match wins, so every rule after the match reports `not_reached`. Omit to resolve the facts and the price without matching anything.","items":{"$ref":"#/components/schemas/AdminVoiceCallFlowProbeRule"}},"organization_id":{"$ref":"#/components/schemas/OrganizationID","description":"Price the calls as this customer would be priced, walking their rate-card cascade. Omit to price against the platform default."}}},"AdminVoiceCallFlowRefusalReason":{"type":["string","null"],"enum":["unclassifiable_destination","non_geographic_destination",null],"description":"Why the call would be refused before any rule was consulted, as a code the editor can branch on. `unclassifiable_destination` covers a number that cannot be parsed and a class the platform does not carry; `non_geographic_destination` is a range with no country to authorize against. Null when the call is routable. The sentence a human reads is `final_reason`.","example":null},"AdminVoiceCallSourceFacts":{"type":"object","additionalProperties":false,"required":["number","country","type","provider","supplier","outbound_routing"],"description":"The calling end, as a rule expression addresses it. Absence is null on every field here; a rule expression sees the same absence as the EMPTY STRING, which is what `src.country == \"\"` tests for. A caller ID the workspace may not present leaves ALL of these absent, including the supplier facts: a number a caller does not hold must not report who supplies it.\n`number`, `country` and `type` repeat the shape of `AdminVoiceCallEndpointFacts` rather than composing with it, deliberately: that schema is closed, so the three source-only facts below could not be added to it without advertising a `dst.supplier` that can never be populated. The two ends validate differently because only one of them has provenance.","properties":{"number":{"type":["string","null"],"minLength":1,"description":"Digits only, no leading plus. Null when there is no trusted number for this end.","example":"31612345678"},"country":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"description":"The country the number resolves to. Null when it resolves to none."},"type":{"type":["string","null"],"minLength":1,"description":"Line type of the number. Null when it resolves to none.","example":"mobile"},"provider":{"oneOf":[{"$ref":"#/components/schemas/VoiceNumberProviderType"},{"type":"null"}],"description":"How the right to present this number arose. Null when there is no trusted number for this end."},"supplier":{"oneOf":[{"$ref":"#/components/schemas/AdminNumbersSupplierSlug"},{"type":"null"}],"description":"Slug of the supplier that sold the number. Null for a verified number, which we did not supply, and for a supplier row that has no slug yet."},"outbound_routing":{"oneOf":[{"$ref":"#/components/schemas/AdminNumberOutboundRouting"},{"type":"null"}],"description":"Where a call presenting this number must leave by. Null means nobody has asked the number's supplier, which is not the same as `default`."}}},"AdminVoiceDestinationNetwork":{"type":"string","minLength":1,"enum":["public","bird_eu1","bird_us1"],"description":"Destination allocation network used by routing expressions.\n- `public`: no Bird allocation region was established within the lookup budget.\n- `bird_eu1`: an allocated regional number or active global allocation identifies eu1.\n- `bird_us1`: an allocated regional number or active global allocation identifies us1.\nThis hint does not grant permission to receive a call or change its charges.\n","example":"bird_eu1"},"AdminVoiceCallEndpointFacts":{"type":"object","additionalProperties":false,"required":["number","country","type","net"],"description":"The dialled end, as a rule expression addresses it. Number, country and type represent absence as null; an expression sees the same absence as an empty string. A number whose line type cannot be resolved leaves type null. Network is populated when destination facts are returned.","properties":{"number":{"type":["string","null"],"minLength":1,"description":"Digits only, no leading plus. Null when there is no trusted number for this end.","example":"31612345678"},"country":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"description":"The country the number resolves to. Null when it resolves to none."},"type":{"type":["string","null"],"minLength":1,"description":"Line type of the number. Null when it resolves to none.","example":"mobile"},"net":{"$ref":"#/components/schemas/AdminVoiceDestinationNetwork"}}},"AdminVoiceCallRelationFacts":{"type":"object","additionalProperties":false,"required":["zone","breakout_code"],"description":"The relation between the two ends of the call, as a rule expression addresses it.","properties":{"zone":{"type":"string","minLength":1,"description":"The calling zone the two countries put the call in.","example":"domestic"},"breakout_code":{"type":["string","null"],"minLength":1,"description":"The most specific breakout code for the destination. Null when the rate deck does not cover it, which a rule expression sees as the empty string.","example":"NL-MOB-KPN"}}},"AdminVoiceCallFacts":{"type":"object","additionalProperties":false,"required":["src","dst","call"],"description":"The facts a routing-rule expression is evaluated against, in the three namespaces it addresses them by.","properties":{"src":{"$ref":"#/components/schemas/AdminVoiceCallSourceFacts","description":"Where the call comes from."},"dst":{"$ref":"#/components/schemas/AdminVoiceCallEndpointFacts","description":"Where the call is going."},"call":{"$ref":"#/components/schemas/AdminVoiceCallRelationFacts"}}},"AdminVoiceCallFlowPrice":{"type":"object","additionalProperties":false,"required":["rate","billing_unit_seconds"],"properties":{"rate":{"$ref":"#/components/schemas/Money","description":"What one billing unit costs."},"billing_unit_seconds":{"type":"integer","minimum":1,"description":"How many seconds one unit of the rate covers, so a per-minute rate reads 60.","example":60},"matched_dimensions":{"type":"object","additionalProperties":{"type":"string"},"description":"The rating axes the matched price row was keyed on, including which rung of the breakout ladder priced the call.","example":{"breakout_code":"NL-MOB","call_zone":"eea","country_code":"NL"}}}},"AdminVoiceCallFlowVerdict":{"type":"string","minLength":1,"enum":["matched","not_matched","not_reached","error"],"description":"`matched` is the rule that decides the call; `not_matched` was evaluated and did not apply; `not_reached` sits after the match, so first-match-wins never consulted it; `error` could not be evaluated, which REFUSES the call rather than skipping the rule: the rule may have been a block, and routing a call a block would have refused is the worst outcome available.","example":"matched"},"AdminVoiceCallFlowRuleVerdict":{"type":"object","additionalProperties":false,"required":["index","label","verdict","message"],"description":"What one rule did about one call.","properties":{"index":{"type":"integer","minimum":0,"description":"Position of the rule in the request's list, so a verdict can be matched to the rule that produced it.","example":1},"label":{"type":["string","null"],"description":"The rule's label, echoed from the request. Null when the request supplied none."},"verdict":{"$ref":"#/components/schemas/AdminVoiceCallFlowVerdict"},"message":{"type":["string","null"],"minLength":1,"description":"Why the rule could not be compiled or evaluated. Null unless the verdict is `error`.","example":null}}},"AdminVoiceCallFlowFinalAction":{"type":"string","minLength":1,"enum":["route","block","refused"],"description":"What would happen to the call. `route` and `block` are the matching rule's own action. `refused` means no rule decided it: an unclassifiable destination, a rule that could not be evaluated, or a plan whose rules all declined. That is a refusal, not a silent pass.","example":"block"},"AdminVoiceCallFlowProbeCallResult":{"type":"object","additionalProperties":false,"required":["destination_number","routable","refusal_reason","breakout_codes","rules","matched_rule_index","final_action","final_reason"],"properties":{"destination_number":{"type":"string","minLength":1,"description":"The number as it was given, echoed so a result can be matched to its input.","example":"+31612345678"},"routable":{"type":"boolean","description":"Whether the destination could be classified at all. False means the call would be refused before any rule was consulted, and `refusal_reason` says which case it is.","example":true},"refusal_reason":{"$ref":"#/components/schemas/AdminVoiceCallFlowRefusalReason"},"facts":{"$ref":"#/components/schemas/AdminVoiceCallFacts","description":"The facts a routing rule sees for this call. Absent when the call is not routable."},"breakout_codes":{"type":"array","description":"Every breakout code the destination reaches, most specific first. The first is the one a rule matches on and the one pricing starts from; an empty list means the rate deck does not cover the destination.","items":{"type":"string","minLength":1},"example":["NL-MOB-KPN","NL-MOB","NL"]},"price":{"$ref":"#/components/schemas/AdminVoiceCallFlowPrice","description":"What the call would cost. Absent when the deck covers no code for it, when no price row matches, or when pricing is not available in this deployment."},"rules":{"type":"array","description":"What each supplied rule did about this call, in the order they were supplied. Empty when no rules were supplied or the call is not routable.","items":{"$ref":"#/components/schemas/AdminVoiceCallFlowRuleVerdict"}},"matched_rule_index":{"type":["integer","null"],"minimum":0,"description":"Which rule decided the call. Null when none matched, when the call is not routable, or when a rule failed to evaluate before any match.","example":1},"final_action":{"$ref":"#/components/schemas/AdminVoiceCallFlowFinalAction"},"final_reason":{"type":["string","null"],"minLength":1,"description":"Why the call ends the way it does, in one phrase for a human. Null when a rule matched cleanly.","example":"no rule matched, so the call is refused"}}},"AdminVoiceCallFlowProbeResult":{"type":"object","additionalProperties":false,"required":["calls"],"properties":{"calls":{"type":"array","description":"One entry per probed call, in the order they were given.","items":{"$ref":"#/components/schemas/AdminVoiceCallFlowProbeCallResult"}}}},"AdminVoiceSampleNumbers":{"type":"object","additionalProperties":false,"required":["numbers"],"properties":{"numbers":{"type":"array","description":"Numbers in E.164 form, each valid for its own numbering plan.","items":{"type":"string","minLength":1},"example":["+31612345678","+493012345678","+18002345678"]}}},"AdminVoiceGatewayLatencyTransport":{"type":"string","minLength":1,"enum":["udp","tcp","tls"],"x-enum-varnames":["AdminVoiceGatewayLatencyTransportUdp","AdminVoiceGatewayLatencyTransportTcp","AdminVoiceGatewayLatencyTransportTls"],"description":"Transport to time the gateway over. Not nullable, unlike a stored gateway's transport: a null there means RFC 3263 resolution picks, and this probe dials one transport directly, so accepting null would time a route the call may not take.","example":"tls"},"AdminVoiceGatewayLatencyTarget":{"type":"object","additionalProperties":false,"required":["host","port","transport"],"description":"One gateway to time, addressed exactly as a call would address it. Every field is required, including the two that may be null: a caller states the port and transport it wants timed rather than leaving the probe to guess, so there is no second spelling of the same request.","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"Hostname or address of the gateway. Refused unless it resolves to a publicly routable address.","example":"ams.example.net"},"port":{"type":["integer","null"],"minimum":1,"maximum":65535,"description":"Null takes the transport's default, 5060 for udp and tcp and 5061 for tls.","example":5061},"transport":{"$ref":"#/components/schemas/AdminVoiceGatewayLatencyTransport"}}},"AdminVoiceGatewayLatencyProbe":{"type":"object","additionalProperties":false,"required":["gateways"],"description":"The gateways to time. They travel in the request rather than being read from a routing version, so an editing session can be timed before it is saved, and a region times only the gateways it would itself dial.","properties":{"gateways":{"type":"array","minItems":1,"maxItems":50,"description":"Gateways to time. Each is timed independently and concurrently.","items":{"$ref":"#/components/schemas/AdminVoiceGatewayLatencyTarget"}}}},"AdminVoiceGatewayLatencyMeasurement":{"type":"object","additionalProperties":false,"required":["gateway","latency_ms","sip_status","error"],"description":"One gateway's round trip as this region measured it. `gateway` echoes the request entry, so a caller attributes a measurement by address rather than by position. `latency_ms` and `sip_status` are non-null together and describe a completed round trip; `error` is non-null exactly when they are null.","properties":{"gateway":{"allOf":[{"$ref":"#/components/schemas/AdminVoiceGatewayLatencyTarget"}],"description":"The requested gateway, echoed. Host, port and transport together are what distinguish one gateway from another, so all three come back."},"latency_ms":{"type":["integer","null"],"minimum":0,"description":"Milliseconds for the whole round trip, measured as this region asks its border to send the OPTIONS and waits for the reply. It therefore includes one hop to the border and the border's own handling, which is near-constant across the gateways in one request: read it as a comparison between them, not as a service level. Null when no reply arrived.","example":24},"sip_status":{"type":["integer","null"],"minimum":100,"maximum":699,"description":"Status of the reply, whatever it was. A gateway that refuses this probe's source address still answers, and that refusal is a valid round trip, so a status other than 200 does not invalidate the number beside it. Null when no reply arrived.","example":200},"error":{"type":["string","null"],"minLength":1,"description":"Why nothing was measured. Null when a reply arrived.","example":"read udp 10.0.1.7:54012: i/o timeout"}}},"AdminVoiceGatewayLatencyProbeResult":{"type":"object","additionalProperties":false,"required":["gateways"],"description":"What this region measured for each requested gateway.","properties":{"gateways":{"type":"array","description":"One measurement per requested gateway, in request order.","items":{"$ref":"#/components/schemas/AdminVoiceGatewayLatencyMeasurement"}}}},"AdminVoiceNumberFacts":{"type":"object","description":"The facts a workspace sees about its own number. Duplicated from VoiceNumber rather than shared: that schema is published and flat, so it can be neither composed nor made composable.\n","required":["id","phone_number","country_code","name","provider","directions","inbound_configuration","created_at"],"properties":{"id":{"$ref":"#/components/schemas/VoiceNumberID","readOnly":true,"description":"Identifier of this number, to pass to the operations that read and change it. A number you registered as a caller ID carries the same identifier there, with the caller-ID prefix.\n"},"phone_number":{"type":"string","minLength":1,"readOnly":true,"description":"The phone number in E.164 format.","example":"+14155551234"},"country_code":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"readOnly":true,"description":"Country the number belongs to. Null when the number is not geographic or its country cannot be determined.\n"},"name":{"type":["string","null"],"minLength":1,"maxLength":100,"readOnly":true,"description":"Your own label for this number, to tell several apart. Null when it has none. Only you see it, so it never affects what a caller sees.\n","example":"Support line"},"provider":{"$ref":"#/components/schemas/VoiceNumberProvider","readOnly":true},"directions":{"$ref":"#/components/schemas/VoiceNumberDirections","readOnly":true,"description":"Which directions this number can carry. Both follow from the number itself, so neither is yours to change. On a SIP trunk each direction is a setting you turn on; here it is a fact about the number.\n"},"inbound_configuration":{"$ref":"#/components/schemas/VoiceInboundConfiguration","readOnly":true,"description":"What happens to a call arriving for this number."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this number became usable for voice: when it was allocated to you, or when you first registered it, whichever this number is.\n"}}},"AdminVoiceSIPTrunkStatus":{"type":"string","minLength":1,"enum":["pending","provisioned","deprovisioning","deprovisioned","failed"],"description":"Where the supplier-side trunk attachment for a number has got to."},"AdminVoiceSIPTrunkState":{"type":"object","additionalProperties":false,"description":"What Bird believes it configured at the supplier for this number, as the reconciler last recorded it.\n","required":["status","changed_at"],"properties":{"status":{"$ref":"#/components/schemas/AdminVoiceSIPTrunkStatus"},"ref":{"type":["string","null"],"minLength":1,"description":"The supplier's own handle for the attachment. Null before one exists."},"reason":{"type":["string","null"],"minLength":1,"description":"The supplier's own words on a failed attachment. Null unless the status is `failed`."},"changed_at":{"type":"string","format":"date-time","minLength":1,"description":"When the reconciler last recorded a change to this state."}}},"AdminVoiceNumberInternals":{"type":"object","description":"What staff see about a voice number beyond what its workspace sees: which workspace holds it, how far proving control of it got, and the state of the trunk registration behind it. Numbers a workspace has given up are not listed at all, so every number here is one it currently holds.\n","required":["workspace_id","updated_at"],"properties":{"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this number's record last changed."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID","description":"The workspace holding this number. Resolve the owning organization from the workspace record."},"verification_started_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When proving control of this number began. Null when nobody has tried, which includes every number Bird allocated."},"verification_failed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the last attempt to prove control failed. Null when none has."},"sip_trunk_state":{"oneOf":[{"$ref":"#/components/schemas/AdminVoiceSIPTrunkState"},{"type":"null"}],"readOnly":true,"description":"The supplier-side trunk attachment for this number. Null for a number that answers no inbound call, or before the reconciler has recorded a state.\n"}}},"AdminVoiceNumber":{"description":"Staff view of one voice number: everything its workspace sees about it, plus the workspace holding it and the facts a customer never does.\n","allOf":[{"$ref":"#/components/schemas/AdminVoiceNumberFacts"},{"$ref":"#/components/schemas/AdminVoiceNumberInternals"}],"unevaluatedProperties":false},"AdminVoiceNumberList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceNumber"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminVoiceTrunk":{"description":"Staff view of a customer's SIP trunk: the owning tenant, the Bird-assigned wire domain, and the two allow lists that gate SIP traffic on it.\n","allOf":[{"$ref":"#/components/schemas/VoiceTrunkCore"},{"type":"object","required":["organization_id"],"properties":{"organization_id":{"readOnly":true,"$ref":"#/components/schemas/OrganizationID","description":"The organization owning this trunk's workspace, and so the region the trunk's data lives in."}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminVoiceTrunkList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceTrunk"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminVoiceTrunkCreate":{"type":"object","additionalProperties":false,"description":"Provisions a SIP trunk on a customer's behalf. The trunk's wire domain is assigned by Bird and the owning organization is taken from the workspace, so neither is accepted here.\n","required":["workspace_id","display_name"],"properties":{"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"display_name":{"type":"string","minLength":1,"description":"The customer-visible label for this trunk. Mutable, and distinct from the wire domain.","example":"Production PBX trunk"},"outbound_enabled":{"type":"boolean","default":false,"description":"Whether the trunk may place calls. Both directions are off unless set, so a trunk provisioned with neither carries no traffic until the customer or an operator turns one on.\n","example":true},"inbound_enabled":{"type":"boolean","default":false,"description":"Whether the trunk may receive calls on the numbers it answers.","example":true}}},"AdminVoiceTrunkDetail":{"type":"object","description":"Staff view of a single SIP trunk, adding whether the workspace can currently route outbound calls, the second half of \"why is this customer's trunk not working\", after the trunk's own allow lists.\n","allOf":[{"$ref":"#/components/schemas/AdminVoiceTrunk"},{"$ref":"#/components/schemas/VoiceTrunkRouting"}]},"VoiceSigningCredID":{"type":"string","minLength":1,"pattern":"^vsc_[0-9a-hjkmnp-tv-z]{26}$","example":"vsc_01krdgeqcxet5s7t44vh8rt9mg"},"AdminVoiceSigningRegime":{"type":"string","minLength":1,"enum":["shaken_us","man_fr"],"description":"The attestation authority a signing credential answers to. It decides the trust anchor a certificate must chain to and, on every signed call, which classifier decides the attestation level.","example":"shaken_us"},"AdminVoiceSigningCredential":{"allOf":[{"type":"object","required":["id","name","regime","operator_code","x5u","certificate","not_before","not_after","enabled","uploaded_by","activated_by","activated_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceSigningCredID","description":"Unique identifier for this signing credential."},"name":{"type":"string","minLength":1,"maxLength":128,"description":"Operator-facing label for this credential. Not unique, because a renewal is a new credential carrying the same name and both exist while one is staged.","example":"us-082k-2026"},"regime":{"$ref":"#/components/schemas/AdminVoiceSigningRegime"},"operator_code":{"type":"string","minLength":1,"maxLength":32,"description":"The service provider code inside the certificate's TNAuthList extension, read from the certificate itself rather than trusted from the upload.","example":"082K"},"x5u":{"type":"string","minLength":1,"maxLength":1024,"description":"The HTTPS URL where this certificate is published. It is copied verbatim into every PASSporT this credential signs, so a verifier fetches the certificate from here.","example":"https://api.man-bpco.fr/certs/MESS00/12345.cer"},"certificate":{"type":"string","minLength":1,"description":"The certificate, PEM-encoded, exactly as it was installed. Public by construction; the private key is never returned by any operation."},"not_before":{"type":"string","minLength":1,"format":"date-time","description":"Start of the certificate's own validity window, parsed from the certificate at upload."},"not_after":{"type":"string","minLength":1,"format":"date-time","description":"End of the certificate's own validity window. A credential past this instant is not selectable for signing even while enabled."},"enabled":{"type":"boolean","description":"Whether this credential is in the live set for its regime. Several can be, because a renewal is armed before its window opens; a call is signed with the newest of them whose validity window covers the moment of the call. An upload is never enabled, because activation is a separate operation.","example":false},"uploaded_by":{"type":"string","minLength":1,"description":"Who installed this credential.","example":"operator@bird.com"},"activated_by":{"type":["string","null"],"minLength":1,"description":"Who last activated this credential, or null if it has never been activated.","example":"operator@bird.com"},"activated_at":{"type":["string","null"],"minLength":1,"format":"date-time","description":"When this credential was last activated, or null if it has never been activated."}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminVoiceSigningCredentialList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceSigningCredential"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminVoiceSigningCredentialCreate":{"type":"object","additionalProperties":false,"required":["name","regime","operator_code","x5u","certificate","private_key"],"properties":{"name":{"type":"string","minLength":1,"maxLength":128,"description":"Operator-facing label for this credential.","example":"us-082k-2026"},"regime":{"$ref":"#/components/schemas/AdminVoiceSigningRegime"},"operator_code":{"type":"string","minLength":1,"maxLength":32,"description":"The service provider code this certificate is expected to carry. It is checked against the certificate's own TNAuthList extension, and a disagreement refuses the upload, because a certificate signing under an operator code Bird did not expect is rejected silently by every verifier downstream.","example":"082K"},"x5u":{"type":"string","minLength":1,"maxLength":1024,"description":"The HTTPS URL where this certificate is published. Verifiers fetch the certificate from here, so it must serve this exact certificate by the time the credential is activated.","example":"https://api.man-bpco.fr/certs/MESS00/12345.cer"},"certificate":{"type":"string","minLength":1,"description":"The signing certificate, PEM-encoded. A pasted chain is accepted, and the leaf is taken from the first block."},"private_key":{"type":"string","minLength":1,"writeOnly":true,"x-sensitive":true,"description":"The matching private key, PEM-encoded (SEC1 or PKCS#8), on the P-256 curve SHAKEN requires. Write-only, so no operation ever returns it, and it is sealed before it reaches storage."}}},"AdminVoiceSigningCheckName":{"type":"string","minLength":1,"x-extensible-enum":["certificate_parses","not_expired","operator_code_matches_tnauthlist","key_pairs_with_certificate","chain_verifies_to_trust_anchor","test_passport_signs_and_verifies"],"description":"Which upload check this is. Open enum: the set of checks grows as more of them become worth running before a certificate signs anything, so treat an unrecognized name as a newer server rather than an error.","example":"chain_verifies_to_trust_anchor"},"AdminVoiceSigningCheckStatus":{"type":"string","minLength":1,"enum":["passed","failed","skipped"],"description":"The check's outcome. A status of skipped means the check could not run, today only the chain check where the regime has no trust anchors configured.","example":"skipped"},"AdminVoiceSigningCheck":{"type":"object","additionalProperties":false,"required":["name","status"],"properties":{"name":{"$ref":"#/components/schemas/AdminVoiceSigningCheckName"},"status":{"$ref":"#/components/schemas/AdminVoiceSigningCheckStatus"},"detail":{"type":"string","minLength":1,"description":"Why the check did not pass. Absent for a passing check.","example":"no trust anchors configured for regime shaken_us"}}},"AdminVoiceSigningCredentialCreateResult":{"type":"object","additionalProperties":false,"required":["credential","checks"],"properties":{"credential":{"$ref":"#/components/schemas/AdminVoiceSigningCredential"},"checks":{"type":"array","minItems":1,"description":"What was verified about this certificate and key, one entry per check. A check that could not run reports a status of skipped rather than passed, because an unverifiable certificate chain and a verified one must not read the same.\n","items":{"$ref":"#/components/schemas/AdminVoiceSigningCheck"}}}},"VoiceAttestationPolicyID":{"type":"string","minLength":1,"pattern":"^vap_[0-9a-hjkmnp-tv-z]{26}$","example":"vap_01krdgeqcxet5s7t44vh8rt9mg"},"AdminVoiceAttestationPolicy":{"allOf":[{"type":"object","required":["id","regime","country_code","priority"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceAttestationPolicyID","description":"Unique identifier for this policy."},"regime":{"$ref":"#/components/schemas/AdminVoiceSigningRegime"},"country_code":{"$ref":"#/components/schemas/CountryCode","description":"The destination country this authority covers. Each authority covers a fixed set of countries, because a national regime cannot be declared the authority for another country's numbers, so a country outside its set is refused."},"priority":{"type":"integer","format":"int32","description":"Tried lowest first, which matters only where two authorities claim the same country.","example":10}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminVoiceAttestationPolicyList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceAttestationPolicy"}}}},"AdminVoiceAttestationPolicyCreate":{"type":"object","additionalProperties":false,"required":["regime","country_code","priority"],"properties":{"regime":{"$ref":"#/components/schemas/AdminVoiceSigningRegime"},"country_code":{"$ref":"#/components/schemas/CountryCode","description":"The destination country this authority covers. Each authority covers a fixed set of countries, because a national regime cannot be declared the authority for another country's numbers, so a country outside its set is refused. Declaring the same country again for one authority updates its priority rather than adding a row."},"priority":{"type":"integer","format":"int32","minimum":0,"description":"Tried lowest first, which matters only where two authorities claim the same country.","example":10}}},"VoiceLimitProfileID":{"type":"string","minLength":1,"pattern":"^vlp_[0-9a-hjkmnp-tv-z]{26}$","example":"vlp_01krdgeqcxet5s7t44vh8rt9mg"},"AdminVoiceLimitProfile":{"allOf":[{"type":"object","required":["id","workspace_id","name","daily_spend_limit_micro","destination_allow","destination_deny"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceLimitProfileID","description":"Unique identifier for this limit profile."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"name":{"type":"string","minLength":1,"description":"A human-readable label for this limit profile.","example":"Default limits"},"daily_spend_limit_micro":{"type":["integer","null"],"format":"int64","minimum":0,"description":"Maximum outbound call spend allowed per day, in micro-units of the workspace currency (1,000,000 = one currency unit). Null means no daily spend limit.","example":50000000},"destination_allow":{"type":"array","items":{"type":"string","minLength":1},"description":"Allowed destination prefixes or ISO 3166-1 alpha-2 country codes. When non-empty, only destinations matching an entry may be dialed. An empty list means no allow-list restriction.","example":["+1","GB"]},"destination_deny":{"type":"array","items":{"type":"string","minLength":1},"description":"Blocked destination prefixes or ISO 3166-1 alpha-2 country codes. Destinations matching an entry are refused. An empty list means no deny-list restriction.","example":["+7"]}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminVoiceLimitProfileList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceLimitProfile"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminVoiceLimitProfileCreate":{"type":"object","additionalProperties":false,"required":["workspace_id","name"],"properties":{"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"name":{"type":"string","minLength":1,"description":"A human-readable label for this limit profile.","example":"Default limits"},"daily_spend_limit_micro":{"type":["integer","null"],"format":"int64","minimum":0,"description":"Maximum outbound call spend allowed per day, in micro-units of the workspace currency (1,000,000 = one currency unit). Null or omitted means no daily spend limit.","example":50000000},"destination_allow":{"type":"array","items":{"type":"string","minLength":1},"description":"Allowed destination prefixes or ISO 3166-1 alpha-2 country codes. When non-empty, only destinations matching an entry may be dialed. Defaults to an empty list.","example":["+1","GB"]},"destination_deny":{"type":"array","items":{"type":"string","minLength":1},"description":"Blocked destination prefixes or ISO 3166-1 alpha-2 country codes. Destinations matching an entry are refused. Defaults to an empty list.","example":["+7"]}}},"AdminVoiceLimitProfileUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"name":{"type":"string","minLength":1,"description":"A human-readable label for this limit profile. Omit to keep the current value.","example":"Default limits"},"daily_spend_limit_micro":{"type":["integer","null"],"format":"int64","minimum":0,"description":"Maximum outbound call spend allowed per day, in micro-units of the workspace currency (1,000,000 = one currency unit). Null clears the limit; omitting the field keeps the current value.","example":50000000},"destination_allow":{"type":"array","items":{"type":"string","minLength":1},"description":"Replaces the whole allow-list. Omit to keep the current list; send an empty list to remove the allow-list restriction.","example":["+1","GB"]},"destination_deny":{"type":"array","items":{"type":"string","minLength":1},"description":"Replaces the whole deny-list. Omit to keep the current list; send an empty list to remove the deny-list restriction.","example":["+7"]}}},"AdminVoiceCaptureWindow":{"type":"object","additionalProperties":false,"description":"The time range to inspect, including its start and excluding its end.","required":["from","to"],"properties":{"from":{"type":"string","minLength":1,"format":"date-time","description":"Start of the time range."},"to":{"type":"string","minLength":1,"format":"date-time","description":"End of the time range."}}},"AdminVoiceCaptureProfile":{"type":"string","minLength":1,"description":"SIP capture profile to search:\n\n- `1_call`: SIP call signalling.\n- `1_registration`: SIP registration messages.\n- `1_default`: Other SIP messages, including high-volume OPTIONS keepalives.\n\nA correlated read names a SIP profile too: the quality-report and log records\nare added by the correlation, so naming one as the profile queries it twice.","enum":["1_call","1_registration","1_default"],"default":"1_call"},"AdminVoiceCaptureFilters":{"type":"object","additionalProperties":false,"description":"Exact-match capture filters. All supplied filters must match.","properties":{"sid":{"type":"string","description":"SIP Call-ID recorded for the capture."},"ruri_user":{"type":"string","description":"User portion of the request URI."},"ruri_domain":{"type":"string","description":"Domain portion of the request URI."},"from_user":{"type":"string","description":"User portion of the From address."},"to_user":{"type":"string","description":"User portion of the To address."},"callid":{"type":"string","description":"Call-ID parsed from the SIP message."},"method":{"type":"string","description":"SIP request method or response code."},"source_ip":{"type":"string","description":"Source IP address."},"destination_ip":{"type":"string","description":"Destination IP address."},"correlation_id":{"type":"string","description":"Correlation identifier recorded with the capture."}}},"AdminVoiceCaptureSearchRequest":{"type":"object","additionalProperties":false,"description":"Search one capture profile, or supply call_id to return the complete correlated selection, within an explicit time range of at most 24 hours.","required":["window"],"properties":{"window":{"$ref":"#/components/schemas/AdminVoiceCaptureWindow"},"profile":{"$ref":"#/components/schemas/AdminVoiceCaptureProfile"},"filters":{"$ref":"#/components/schemas/AdminVoiceCaptureFilters"},"call_id":{"type":"string","minLength":1,"description":"Return the complete correlated SIP, quality-report, and log selection for this Call-ID. Filters and cursors cannot be combined with this mode."}},"dependentSchemas":{"call_id":{"propertyNames":{"enum":["call_id","window","profile"]}}}},"AdminVoiceCapture":{"type":"object","x-sensitive":true,"additionalProperties":false,"description":"A captured SIP message, quality report, or log record. Filtered search omits raw; correlated search includes it.","required":["id","sid","created_at","region","profile","protocol_header","data_header"],"properties":{"id":{"type":"string","minLength":1,"pattern":"^[1-9][0-9]*$","description":"Capture identifier within its region and profile."},"sid":{"type":["string","null"],"minLength":1,"description":"SIP Call-ID or quality-report correlation identifier, or null when the capture recorded none."},"created_at":{"type":"string","minLength":1,"format":"date-time","description":"Time the record was captured."},"region":{"$ref":"#/components/schemas/Region"},"profile":{"type":"string","minLength":1,"description":"Profile the record was captured under: a SIP profile (`1_call`, `1_registration`, `1_default`), `5_default` for a quality report, or `100_default` for a log record. The last two appear only in a correlated selection."},"protocol_header":{"type":"object","additionalProperties":true,"description":"Original capture endpoint, transport, and agent metadata."},"data_header":{"type":"object","additionalProperties":true,"description":"Original parsed message fields, whose shape depends on the capture profile."},"raw":{"type":"string","minLength":1,"format":"byte","description":"Base64-encoded original message bytes. SIP profiles contain SIP text; quality reports contain JSON."},"cursor":{"type":"string","minLength":1,"description":"Opaque resume token for a filtered search row. Echo it unchanged; absent in correlated results."}}},"AdminVoiceCaptureRegion":{"type":"object","additionalProperties":false,"description":"Whether one region answered the capture request.","required":["region","error"],"properties":{"region":{"$ref":"#/components/schemas/Region"},"error":{"type":["string","null"],"minLength":1,"description":"Reason the region failed, or null when it answered."}}},"AdminVoiceCaptureSearchResult":{"type":"object","unevaluatedProperties":false,"description":"A page of capture records from the serving region. A failed region is reported explicitly even when the page is empty.","allOf":[{"$ref":"#/components/schemas/_ListEnvelope"},{"type":"object","required":["data","regions","window","profile"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceCapture"}},"regions":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceCaptureRegion"}},"window":{"$ref":"#/components/schemas/AdminVoiceCaptureWindow"},"profile":{"$ref":"#/components/schemas/AdminVoiceCaptureProfile"}}}]},"AdminVoiceCaptureExportFormat":{"type":"string","description":"Pcapng includes reconstruction provenance. Classic pcap supports older recipient tools but cannot include those comments. Text includes the correlated records with their timestamps and endpoints.","enum":["pcapng","pcap","text"],"default":"pcapng"},"AdminVoiceCaptureExportRequest":{"type":"object","additionalProperties":false,"description":"Export a Call-ID within an explicit time range of at most 24 hours.","required":["call_id","window"],"properties":{"call_id":{"type":"string","minLength":1,"description":"SIP Call-ID to correlate, including captures without a Bird call record."},"profile":{"$ref":"#/components/schemas/AdminVoiceCaptureProfile"},"window":{"$ref":"#/components/schemas/AdminVoiceCaptureWindow","description":"An explicit positive range of at most 24 hours, widened by correlation padding before the read."},"format":{"$ref":"#/components/schemas/AdminVoiceCaptureExportFormat"}}},"AdminVerificationCallerID":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["country","number","description"],"properties":{"country":{"type":"string","minLength":1,"pattern":"^([A-Z]{2}|\\*)$","description":"Destination country this caller ID is presented to, as an ISO 3166-1 alpha-2 code. The literal `*` is the fallback used when no country matches.","example":"NL"},"number":{"type":"string","minLength":2,"pattern":"^\\+[1-9][0-9]{1,14}$","description":"The number presented as the caller ID, in E.164 format. It is the origin of the call, so it decides how the leg rates and what the recipient sees calling them.","example":"+3197058018050"},"description":{"type":["string","null"],"description":"Why this number is used for this country, for whoever next considers changing it.","example":"Dutch national number, keeps NL verification calls domestic."}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminVerificationCallerIDList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","description":"Every configured verification caller ID, specific countries first and the `*` fallback last. This list is not paginated, because it holds one row per country staff have configured and resolving a destination reads all of it.","items":{"$ref":"#/components/schemas/AdminVerificationCallerID"}}}},"AdminVerificationCallerIDUpsert":{"type":"object","additionalProperties":false,"required":["number"],"properties":{"number":{"type":"string","minLength":2,"pattern":"^\\+[1-9][0-9]{1,14}$","description":"The number to present as the caller ID for this country, in E.164 format. It must be one Bird still holds for its own use, meaning it is recorded in the number inventory with scope `internal`, the voice capability, an `available` status, and no organization or workspace holding it.","example":"+3197058018050"},"description":{"type":["string","null"],"description":"Why this number is used for this country, for whoever next considers changing it.","example":"Dutch national number, keeps NL verification calls domestic."}}},"VoiceCallRejectionReason":{"type":"string","minLength":1,"enum":["source_not_allowed","caller_id_not_verified","routing_not_configured","no_route_found","destination_blocked","destination_not_enabled","insufficient_balance","daily_spend_exceeded","concurrent_calls_exceeded","calls_per_second_exceeded","call_not_permitted","number_ownership_not_verified"],"x-enum-varnames":["VoiceCallRejectionReasonSourceNotAllowed","VoiceCallRejectionReasonCallerIDNotVerified","VoiceCallRejectionReasonRoutingNotConfigured","VoiceCallRejectionReasonNoRouteFound","VoiceCallRejectionReasonDestinationBlocked","VoiceCallRejectionReasonDestinationNotEnabled","VoiceCallRejectionReasonInsufficientBalance","VoiceCallRejectionReasonDailySpendExceeded","VoiceCallRejectionReasonConcurrentCallsExceeded","VoiceCallRejectionReasonCallsPerSecondExceeded","VoiceCallRejectionReasonCallNotPermitted","VoiceCallRejectionReasonNumberOwnershipNotVerified"],"description":"Why we rejected the call. Use `rejection_reason` to identify the cause;\n`sip_response_code` alone cannot distinguish these reasons.\n\nYou can resolve these issues:\n\n- `source_not_allowed`: The call came from an IP address that is not in the\n  trunk's allowed-address list. Add the address your PBX sends from.\n- `caller_id_not_verified`: The number in the `From` header is not a verified\n  caller ID for this workspace. Verify it or use a verified caller ID.\n- `number_ownership_not_verified`: The ownership documents for this purchased\n  number have not yet been accepted under its country's requirements. We\n  block outgoing and incoming calls on the number until verification is\n  complete. Blocked incoming calls never reach your PBX, and their route type\n  is `reject` regardless of the number's configuration. Open the number\n  under **Numbers** and complete its ownership requirements, then retry\n  the call.\n- `destination_not_enabled`: Calling to this destination country is disabled.\n  Enable it in your voice destination settings.\n- `insufficient_balance`: Your wallet balance was too low for the call.\n  Top up or enable automatic top-ups.\n- `daily_spend_exceeded`: The call would exceed your organization's daily\n  voice spend limit. Retry after the limit resets at the start of the next\n  UTC day.\n- `concurrent_calls_exceeded`: You already have as many calls in progress as\n  your account allows. Wait for one to end or ask support to raise the limit.\n- `calls_per_second_exceeded`: You placed calls faster than your account\n  allows. Reduce your dialing rate and retry.\n\nFor all other reasons, contact support and provide the call `id`:\n\n- `routing_not_configured`: This trunk has no dial plan, which can happen on\n  a new trunk.\n- `no_route_found`: A dial plan is attached, but no rule in it covers this\n  destination.\n- `destination_blocked`: The destination is blocked by our routing\n  configuration.\n- `call_not_permitted`: The call could not be priced for your account.\n","example":"destination_not_enabled"},"VoiceCallInboundRouteReject":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/VoiceCallRouteType"}],"description":"The number turned the call away. This is where every number starts, so it covers a number nobody has configured as well as one set to reject.\n"}}},"VoiceCallInboundRouteTrunk":{"type":"object","additionalProperties":false,"required":["type","trunk_id"],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/VoiceCallRouteType"}],"description":"The call was delivered to one of your SIP trunks."},"trunk_id":{"allOf":[{"$ref":"#/components/schemas/SIPTrunkID"}],"description":"The SIP trunk the call was delivered to. Recorded as it was at the time, so it may name a trunk you have since changed or deleted.\n"}}},"VoiceCallInboundRouteForward":{"type":"object","additionalProperties":false,"required":["type","forward_to","forward_as"],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/VoiceCallRouteType"}],"description":"The call was forwarded to another of your numbers."},"forward_to":{"type":"string","minLength":1,"description":"The number the call was forwarded to, in E.164 format. Recorded as it was at the time, so it may name a number you have since stopped verifying.\n","example":"+14155551234"},"forward_as":{"allOf":[{"$ref":"#/components/schemas/VoiceInboundForwardAs"}],"description":"Which of the call's two numbers the forwarded leg presented as its caller. The value that went on the wire, not the one the number is set to now.\n"}}},"VoiceCallInboundRoute":{"description":"The routing choice recorded for an incoming call. A recorded route does not\nguarantee that the call connected. Check `status` for the outcome and\n`rejection_reason` for the cause when present.\n","oneOf":[{"$ref":"#/components/schemas/VoiceCallInboundRouteReject"},{"$ref":"#/components/schemas/VoiceCallInboundRouteTrunk"},{"$ref":"#/components/schemas/VoiceCallInboundRouteForward"}],"discriminator":{"propertyName":"type","mapping":{"reject":"#/components/schemas/VoiceCallInboundRouteReject","trunk":"#/components/schemas/VoiceCallInboundRouteTrunk","forward":"#/components/schemas/VoiceCallInboundRouteForward"}}},"VoiceMediaQuality":{"type":"object","additionalProperties":false,"required":["mos","jitter_ms","packet_loss_pct","round_trip_time_ms"],"properties":{"mos":{"type":"number","minimum":1,"maximum":5,"readOnly":true,"description":"Mean opinion score, the single number for how the call sounded, from 1 (unintelligible) to 5 (as good as being in the same room). Anything at or above 4.0 is what most people would call a clear line, and below 3.5 is where callers start asking each other to repeat themselves. The three other fields are the impairments that move it.\n","example":4.32},"jitter_ms":{"type":"integer","minimum":0,"readOnly":true,"description":"Variation in the arrival time of the audio packets, in milliseconds. Audio arriving unevenly is heard as choppiness even when no packets are lost at all.","example":12},"packet_loss_pct":{"type":"number","minimum":0,"readOnly":true,"description":"Percentage of audio packets that never arrived. Heard as brief gaps or clipped words, and the impairment that degrades a call fastest.","example":1.5},"round_trip_time_ms":{"type":"integer","minimum":0,"readOnly":true,"description":"Round-trip time between the two ends, in milliseconds. It does not distort the audio. Above roughly 300 ms, the two parties start talking over each other.","example":42}}},"VoiceCallCost":{"type":"object","additionalProperties":false,"required":["amount","currency_code","outbound_amount","inbound_amount","call_handling_amount","recording_amount","transcription_amount"],"description":"What was charged for a call, split into the components that make it up.\n","properties":{"amount":{"type":"string","minLength":1,"readOnly":true,"description":"Total charged, as a decimal string: the sum of the components below. Net of tax, which applies to your wallet balance rather than to an individual charge.\n","example":"0.013000"},"currency_code":{"readOnly":true,"$ref":"#/components/schemas/CurrencyCode","description":"ISO 4217 currency code. Every component is denominated in this currency.","example":"USD"},"outbound_amount":{"type":["string","null"],"readOnly":true,"description":"What we charged to carry the call to the destination network, as a decimal string. `null` until this component is priced.\n","example":"0.013000"},"inbound_amount":{"type":["string","null"],"readOnly":true,"description":"What we charged to receive the call from the originating network, as a decimal string. Only a call that arrived at your number can carry it. `null` until this component is priced.\n","example":null},"call_handling_amount":{"type":["string","null"],"readOnly":true,"description":"What we charged for handling the call itself, as a decimal string. A call is charged for handling once, however many legs it has, so only one leg's record carries it. `null` until this component is priced.\n","example":null},"recording_amount":{"type":["string","null"],"readOnly":true,"description":"What we charged to record the call, as a decimal string, billed per second over the same billable time as the rest of the call. `null` until this component is priced.\n","example":null},"transcription_amount":{"type":["string","null"],"readOnly":true,"description":"What we charged to transcribe the call's audio, as a decimal string, billed per second of recorded audio rather than for the length of the call. A transcript is produced after the call ends, so this can appear after the rest of the cost. `null` until this component is priced.\n","example":null}}},"AdminVoiceCallUnbilledReason":{"type":"string","minLength":1,"x-extensible-enum":["platform_verification"],"description":"Why the platform deliberately did not charge this call. Absent on every call\nthat billed normally, which is all of them but the ones Bird itself causes.\n\n- `platform_verification`: a call placed to verify one of the workspace's\n  numbers with a channel provider, which claimed the number's one-time\n  exemption on arrival. The reservation is skipped outright, so the leg has no\n  session to commit and no daily spend metered against it. The claim is spent\n  at admission, so an attempt that was never answered carries this too.\n\nOpen enum: a later reason is added here, so treat an unrecognized one as newer\nrather than as an error.","example":"platform_verification"},"AdminVoiceCall":{"type":"object","additionalProperties":false,"required":["id","workspace_id","direction","from","to","status","started_at","duration_ms","billable_ms"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallID","description":"Unique identifier for this call record."},"session_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VoiceSessionID"},{"type":"null"}],"description":"Session identifier shared across all legs of a multi-party or transferred call. Null when session correlation is not available for the call."},"media_session_id":{"type":["string","null"],"readOnly":true,"description":"Media session identifier assigned when the call was answered. Null for calls that were not answered.","example":"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"direction":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallDirection"}]},"from":{"type":"string","minLength":1,"readOnly":true,"description":"Calling party number in E.164 format.","example":"+14155551234"},"to":{"type":"string","minLength":1,"readOnly":true,"description":"Called party number in E.164 format.","example":"+16505559876"},"actor":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Actor"}],"description":"The credential owner the call authenticated as. Either the API key that answered the digest, or the subject that minted the session credential it used. Absent when the leg was admitted by IP allow list, which carries no credential."},"sip_trunk_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/SIPTrunkID"},{"type":"null"}],"description":"Identifier of the SIP trunk that originated this call. Null when no trunk is associated."},"interconnect_name":{"type":["string","null"],"readOnly":true,"description":"Display name of the carrier interconnect at the time of the call. Null when no interconnect was selected.","example":"Digitalk EU"},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallStatus"}]},"sip_response_code":{"readOnly":true,"type":["integer","null"],"minimum":100,"description":"Final SIP response code received from the carrier. Null when no SIP response was received, for example on timeout or DNS failure.","example":200},"rejection_reason":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallRejectionReason"}],"description":"Why the platform refused the call before dialing a carrier. Absent whenever the refusal was not the platform's, which covers a call that connected, a call the carrier or the far end turned down (`sip_response_code` carries their answer, and a 6xx decline reads as `rejected` rather than `failed`), and an incoming call turned away by the number it dialed, which fails no check and so names no reason. `route` says what that number was set to do."},"route":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallInboundRoute"}],"description":"Which answer the dialled number gave an incoming call, recorded when the call was handled rather than read back from the number's current setup. It says what the number was set to do, not that it succeeded: a `trunk` route on a call that never connected is a number pointed at a trunk that did not take it. Absent on outbound calls and on calls recorded before this field existed."},"tags":{"type":"array","maxItems":5,"readOnly":true,"items":{"$ref":"#/components/schemas/Tag"},"description":"The customer's own labels for this call, read off the `X-Bird-Call-Tag` headers on the INVITE. Customer-written, so nothing here is a platform assertion. Read a tag as what the caller said about their own call, never as a fact Bird established. What is here may be less than what was sent: a tag whose name or value broke the rules on `Tag` was dropped, anything past the first five was ignored, and a name sent more than once kept its first value. Absent on a call whose caller set none, and on calls recorded before this field existed."},"started_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the call was initiated."},"answered_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the call was answered (200 OK received). Null for unanswered calls."},"ended_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the call ended (BYE or final non-2xx response). Null for calls that ended abnormally without a recorded end event."},"reaped_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this record was reconstructed because no ending was ever reported for the call, for example after the SIP border holding it restarted. On such a record `ended_at`, `duration_ms` and `billable_ms` are derived from the last moment the call was observed, rather than measured from its real ending. Null for a call that ended normally, which is almost all of them."},"pdd_ms":{"type":"integer","minimum":0,"readOnly":true,"description":"Post-dial delay in milliseconds, from the first INVITE to the carrier's first ringback. The per-call figure behind the `pdd_ms_p50` / `pdd_ms_p95` percentiles on the stats endpoint, so a percentile regression can be traced to the calls and carrier that caused it. Absent when the call never rang.","example":850},"duration_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Total call duration in milliseconds, measured from the first INVITE to the BYE or final response. Null while the call is still in progress and has no final duration yet, matching `ended_at`.","example":65000},"billable_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Billable duration in milliseconds, measured from answer to call end. Zero for unanswered calls, and null while the call is still in progress, where zero would read as a measurement rather than as no answer yet.","example":60000},"media_quality":{"$ref":"#/components/schemas/VoiceMediaQuality","description":"How the audio sounded, as opposed to whether the call connected. The evidence behind a \"the line was terrible\" complaint, and what a carrier dispute is argued with. Absent when the call carried no audio, or when the far end reported nothing to measure from."},"cost":{"$ref":"#/components/schemas/VoiceCallCost","description":"What the call cost, net of tax, at full precision, split into the components that make it up. Absent until the call has been rated; unanswered and unpriced calls have no cost, and neither does one the platform deliberately did not charge, which `unbilled_reason` names."},"unbilled_reason":{"oneOf":[{"$ref":"#/components/schemas/AdminVoiceCallUnbilledReason"},{"type":"null"}],"readOnly":true,"description":"Why this call carries no `cost` although the platform recorded it. Null on every call that billed normally. Not on the customer's own call record, so a support answer built on it has to be given rather than pointed at."}}},"AdminVoiceCallList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceCall"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminVoiceCallsStatsVolume":{"type":"object","required":["total_calls","answered_calls","total_billable_ms"],"properties":{"total_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Call attempts in this slice of the period, answered or not.","example":4820},"answered_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Attempts the far end answered.","example":3110},"total_billable_ms":{"type":"integer","minimum":0,"readOnly":true,"description":"Sum of answered call durations in milliseconds, the quantity customers are charged on.","example":594100000}}},"AdminVoiceCallsStatsTotals":{"allOf":[{"type":"object","additionalProperties":false,"description":"The headline voice figures over one period, used both for the reporting period and for the equal-length period before it.","required":["asr","acd_ms","total_duration_ms","short_calls","short_call_rate","pdd_ms_p50","pdd_ms_p95","active_workspaces","cost","unpriced_calls"],"properties":{"asr":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Answer-seizure ratio, meaning answered calls as a fraction of all attempts. Null when nothing was attempted. A drop here is the first sign of a carrier or routing problem.","example":0.64},"acd_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Average call duration in milliseconds across answered calls. Null when nothing was answered.","example":187000},"total_duration_ms":{"type":"integer","minimum":0,"readOnly":true,"description":"Sum of all call durations in milliseconds, including unanswered attempts.","example":1930000000},"short_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Answered calls that lasted under six seconds. A high count alongside a healthy answer-seizure ratio points at false answers rather than real conversations.","example":412},"short_call_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Short calls as a fraction of answered calls. Null when nothing was answered.","example":0.045},"pdd_ms_p50":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Median post-dial delay in milliseconds, meaning how long callers waited before hearing ringing. Covers only calls where it was measured, and is null when none were.","example":1200},"pdd_ms_p95":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"95th-percentile post-dial delay in milliseconds, over the calls where it was measured. Null when none were.","example":3400},"active_workspaces":{"type":"integer","minimum":0,"readOnly":true,"description":"Workspaces that attempted at least one call in the period.","example":37},"unpriced_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Call legs in the period carrying no cost, and so contributing nothing to `cost`. A leg counts here whether it went unanswered, has not been rated yet, or was not chargeable. Read against `total_calls` this says how complete the spend figure is.","example":143},"cost":{"type":"array","readOnly":true,"description":"Spend on the period's calls, net of tax, one entry per currency. Empty until calls have been rated, and it keeps rising while rating catches up with the legs `unpriced_calls` counts, so a recent period is not a settled figure.","items":{"$ref":"#/components/schemas/Money"}}}},{"$ref":"#/components/schemas/AdminVoiceCallsStatsVolume"}]},"AdminVoiceCallsStatsPoint":{"allOf":[{"type":"object","additionalProperties":false,"description":"One UTC day of the reporting period. Days with no attempts are included with zero counts, so the series can be plotted without gap-filling.","required":["day","asr","cost"],"properties":{"day":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"The UTC day these figures cover.","example":"2026-07-14"},"cost":{"type":"array","readOnly":true,"description":"Spend on the day's calls, net of tax, one entry per currency. Empty on a day whose calls are not rated yet, which on the most recent days is expected rather than a figure of zero.","items":{"$ref":"#/components/schemas/Money"}},"asr":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Answer-seizure ratio for the day. Null on a day with no attempts.","example":0.65}}},{"$ref":"#/components/schemas/AdminVoiceCallsStatsVolume"}]},"AdminVoiceCallsStatsCarrier":{"allOf":[{"type":"object","additionalProperties":false,"description":"One carrier's slice of the period, the scorecard row for comparing carriers on quality and spend.","required":["interconnect_name","asr","acd_ms","cost"],"properties":{"interconnect_name":{"type":["string","null"],"readOnly":true,"description":"The carrier's name as recorded on the call. Null for attempts that never reached a carrier.","example":"Digitalk EU"},"asr":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Answer-seizure ratio for this carrier. Compare across rows to spot the carrier dragging the platform figure down.","example":0.645},"acd_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Average answered call duration in milliseconds for this carrier. Null when it answered nothing.","example":191000},"cost":{"type":"array","readOnly":true,"description":"Spend with this carrier over the period, net of tax, one entry per currency.","items":{"$ref":"#/components/schemas/Money"}}}},{"$ref":"#/components/schemas/AdminVoiceCallsStatsVolume"}]},"AdminVoiceCallsStatsSipCode":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One final SIP response code and how much of the period's traffic ended on it. This is the breakdown that answers why calls failed.","required":["sip_response_code","total_calls","share"],"properties":{"sip_response_code":{"type":["integer","null"],"minimum":100,"maximum":699,"readOnly":true,"description":"The final SIP response code the call ended on. Null when no final response was received at all, which is what a timeout or a DNS failure looks like.","example":486},"total_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Attempts that ended on this code.","example":731},"share":{"type":"number","minimum":0,"maximum":1,"readOnly":true,"description":"This code's share of all attempts in the period.","example":0.051}}},"AdminVoiceCallsStatsWorkspace":{"allOf":[{"type":"object","additionalProperties":false,"description":"One workspace's slice of the period, for the busiest-workspaces board.","required":["workspace_id","asr","acd_ms","cost"],"properties":{"workspace_id":{"$ref":"#/components/schemas/WorkspaceID","description":"The workspace these figures cover."},"asr":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Answer-seizure ratio for this workspace. Well below the platform figure usually means bad destination data on the customer's side rather than a carrier fault.","example":0.645},"acd_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Average answered call duration in milliseconds for this workspace. Null when nothing was answered.","example":176000},"cost":{"type":"array","readOnly":true,"description":"Carrier spend attributable to this workspace over the period, net of tax, one entry per currency.","items":{"$ref":"#/components/schemas/Money"}}}},{"$ref":"#/components/schemas/AdminVoiceCallsStatsVolume"}]},"AdminVoiceCallsStats":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Cross-workspace voice call statistics for a period, with the equal-length period before it for comparison, a daily series, and the per-carrier, per-SIP-response and per-workspace breakdowns.","required":["region","period_start","period_end","previous_period_start","previous_period_end","current","previous","series","by_interconnect","by_sip_response","by_workspace"],"properties":{"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region these figures are drawn from. A response covers one region; combine regions for the platform view."},"period_start":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"Start of the reported period (inclusive)."},"period_end":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"End of the reported period (exclusive)."},"previous_period_start":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"Start of the comparison period (inclusive). It is the equal-length period ending where the reported one begins."},"previous_period_end":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"End of the comparison period (exclusive). Always equal to `period_start`."},"current":{"$ref":"#/components/schemas/AdminVoiceCallsStatsTotals","description":"Figures for the reported period."},"previous":{"$ref":"#/components/schemas/AdminVoiceCallsStatsTotals","description":"The same figures for the comparison period, so a change can be shown without a second request."},"series":{"type":"array","readOnly":true,"description":"One entry per UTC day of the reported period, oldest first, including days with no calls.","items":{"$ref":"#/components/schemas/AdminVoiceCallsStatsPoint"}},"by_interconnect":{"type":"array","readOnly":true,"description":"Per-carrier figures for the reported period, busiest first.","items":{"$ref":"#/components/schemas/AdminVoiceCallsStatsCarrier"}},"by_sip_response":{"type":"array","readOnly":true,"description":"Final SIP response codes for the reported period, most frequent first.","items":{"$ref":"#/components/schemas/AdminVoiceCallsStatsSipCode"}},"by_workspace":{"type":"array","readOnly":true,"description":"The ten busiest workspaces of the reported period, ranked by billable duration. When `current.active_workspaces` exceeds the number of rows here, only the top ten are shown.","items":{"$ref":"#/components/schemas/AdminVoiceCallsStatsWorkspace"}}}},"AdminVoiceCallCorrelation":{"type":"object","additionalProperties":false,"description":"The identifiers other systems index this call by, so a record here can be matched to the platform's logs and traces, and to what the carrier saw. These are the raw values those systems carry, not the `vcl_`/`vcs_` identifiers the rest of this API uses.\n","required":["call_id"],"properties":{"call_id":{"type":"string","format":"uuid","minLength":1,"readOnly":true,"description":"The identifier the platform's own logs and traces carry for this leg. One leg, one value.","example":"01a00045-42b6-700b-b11e-498014ea4255"},"session_id":{"type":["string","null"],"format":"uuid","readOnly":true,"description":"The identifier every leg of the same call shares, which is how a transferred or bridged call is followed across legs. Null when the call was never correlated to a session.","example":"01a00045-42b6-700b-b11e-498014ea4256"},"sip_call_id":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The SIP Call-ID of the carrier leg: the value a carrier quotes in a dispute, and the one the signalling capture indexes every message of the call by.\n\nNull on a call that was never answered, so a busy or carrier-rejected call has none. Those are found in the capture by the two numbers and the time window instead, which can also match a neighbouring call between the same parties.","example":"3f9a1c8e7b2d4a5f@sip.example.net"}}},"AdminVoiceCallEdge":{"type":"string","minLength":1,"x-extensible-enum":["sip","webrtc"],"description":"Which border listener the call arrived on. `sip` is a SIP peer (a customer\ntrunk, or a numbers provider delivering a DID call) and `webrtc` is a browser\nclient. Open enum: a listener added later is a new value, so treat an\nunrecognized one as a newer border rather than an error.\n\nThis is how the call reached us, which is a different question from which trunk\nadmitted it and from which credential authenticated it. The same session\ncredential is minted for a browser dialer and for the CLI, so the credential\nnever answers this.","example":"sip"},"AdminVoiceCallSource":{"type":"string","minLength":1,"x-extensible-enum":["verify"],"description":"Which Bird product asked for this call. `verify` is a one-time-passcode leg the\nplatform placed on the workspace's behalf. Open enum: each product that starts\nplacing calls adds a value, so treat an unrecognized one as a newer product\nrather than an error.\n\nAbsent is an answer, not a gap: it means no product asked, so the customer\nplaced the call themselves over their own trunk.","example":"verify"},"AdminVoiceCallShakenAttestation":{"type":"string","minLength":1,"enum":["A","B","C"],"description":"How much the platform is willing to vouch for the caller identity it signed, and\nthe level it put in the PASSporT.\n\n- `A`: the caller's right to present that number is established, either because\n  the platform allocated the number to the workspace and the jurisdiction's\n  ownership paperwork is satisfied, or because the caller ID passed verification.\n- `B`: the customer is known and authenticated, but nothing establishes their\n  right to this particular number.\n- `C`: the origination was not authenticated, so neither the customer nor the\n  number is established.\n\nDerived per call from what is known about the caller ID at the moment of signing,\nnever configured, so the same workspace can produce all three. France draws the\nA/B line more strictly than the US: a verified caller ID earns A under SHAKEN but\nnot under MAN, where only allocation with satisfied ownership does.\n\nClosed enum: ATIS-1000074 defines exactly these three and France's MAN reuses them,\nso a fourth level would be a new standard rather than a new value here.","example":"A"},"AdminVoiceCallInternals":{"type":"object","description":"The internal facts about a call leg that no customer surface carries. Everything here is recorded for operating the platform: how the call arrived, where it was handled, why it ended, and the identifiers the logs and traces index it by.\n","required":["correlation"],"properties":{"correlation":{"$ref":"#/components/schemas/AdminVoiceCallCorrelation"},"edge":{"oneOf":[{"$ref":"#/components/schemas/AdminVoiceCallEdge"},{"type":"null"}],"readOnly":true,"description":"Which border listener the call arrived on. Null when the record predates edge attribution or the border did not report one, which is never defaulted to a plausible value."},"source":{"oneOf":[{"$ref":"#/components/schemas/AdminVoiceCallSource"},{"type":"null"}],"readOnly":true,"description":"Which Bird product asked for this call. Null means none did, so the customer placed it themselves."},"destination_country":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"readOnly":true,"description":"The country the called number resolves to, decided from the whole number when the call was recorded. Null when the number does not parse, is invalid, or is non-geographic, which is never guessed at."},"termination_reason":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"Why a call that reached a carrier ended, as the border reported it. `platform-reject` marks a call the platform refused itself, where `rejection_reason` names the gate that tripped. Null when nothing reported a reason.\n","example":"normal-clearing"},"region":{"readOnly":true,"$ref":"#/components/schemas/Region","description":"The deployment region whose routing plane wrote this record."},"admitted_region":{"oneOf":[{"$ref":"#/components/schemas/Region"},{"type":"null"}],"readOnly":true,"description":"The region the border reported on admission, which is a different reporter from `region`. The two normally agree; a disagreement means the call was admitted in one place and recorded in another, which is worth investigating. Null when the border reported none."},"shaken_verstat":{"type":["string","null"],"minLength":1,"readOnly":true,"description":"The caller-identity attestation result passed through from the originating network. Null until STIR/SHAKEN pass-through is carried on the wire, which is every call today.","example":"TN-Validation-Passed"},"shaken_attestation":{"oneOf":[{"$ref":"#/components/schemas/AdminVoiceCallShakenAttestation"},{"type":"null"}],"readOnly":true,"description":"The attestation level the platform asserted when it signed this call, a different fact from `shaken_verstat`, which is what an upstream network asserted about a call arriving here. Null when no Identity header was attached, meaning the call went out unsigned, which is a distinct answer from any level rather than a low one."}}},"AdminVoiceCallLeg":{"type":"object","description":"One call leg with everything the platform recorded about it, customer-visible and internal alike. The leg is the unit: a transferred or bridged call is several legs, each with its own identifier, correlated by session.\n","allOf":[{"$ref":"#/components/schemas/AdminVoiceCall"},{"$ref":"#/components/schemas/AdminVoiceCallInternals"}]},"AdminVoiceCallDetail":{"type":"object","additionalProperties":false,"description":"Staff view of one call for troubleshooting: the leg that was asked for, with all of its internal facts, alongside the other legs of the same call. Both sides carry the identical shape, so a transferred or bridged call reads end to end without the perspective changing halfway.\n","required":["call","related_legs"],"properties":{"call":{"$ref":"#/components/schemas/AdminVoiceCallLeg","description":"The leg that was asked for."},"related_legs":{"type":"array","readOnly":true,"maxItems":50,"description":"Every other leg that shares this leg's session, oldest first. Empty when the call carries no session identifier, or when it is the only leg in its session, which is the common case for a plain outbound call.\n","items":{"$ref":"#/components/schemas/AdminVoiceCallLeg"}}}},"VoiceBreakoutVersionID":{"type":"string","minLength":1,"pattern":"^vbv_[0-9a-hjkmnp-tv-z]{26}$","example":"vbv_01krdgeqcxet5s7t44vh8rt9mg"},"AdminVoiceBreakoutInactiveReason":{"type":"string","minLength":1,"enum":["retired_prefix","country_ambiguous","country_none","country_unknown"],"description":"Why the platform could not finish placing this row.\n\n`retired_prefix` means the prefix uses a numbering form the country has\nwithdrawn, so no live call can match it. `country_ambiguous` means the\ncalling code covers several countries without naming one. `country_none`\nmeans the range belongs to no country at all, such as satellite or universal\nfreephone, in which case having no country is correct. `country_unknown`\nmeans nothing could be determined.\n","example":"country_ambiguous"},"VoiceBreakoutCodeID":{"type":"string","minLength":1,"pattern":"^vbc_[0-9a-hjkmnp-tv-z]{26}$","example":"vbc_01krdgeqcxet5s7t44vh8rt9mg"},"AdminVoiceBreakoutLabel":{"type":"string","minLength":1,"maxLength":64,"description":"The destination label this prefix breaks out to, as it appears in the vendor's destination-name column. Many prefixes share one label, which is how a rule can name a whole class of destination without listing every operator in it.\n","example":"MEXICO CELLULAR TELCEL"},"AdminVoiceBreakoutNumberType":{"type":"string","minLength":1,"enum":["mobile","fixed","toll_free"],"description":"The destination class calls under this code reach, and half of the key customers are\nquoted on: a published rate is meaningless without knowing whether it prices a `mobile`,\na `fixed` line or a `toll_free` number. `fixed` is the landline case, and covers the VoIP\nand universal-access ranges a carrier terminates the same way.\n\nThis is the same vocabulary the platform classifies a dialled number into before it will\nplace a call, so it holds only classes Bird actually calls. Destinations outside it, such\nas premium rate, shared cost, personal numbers and paging, are refused before a price is\nresolved and are therefore left unclassified rather than given a class of their own.\n","example":"mobile"},"AdminVoiceBreakoutStatus":{"type":"string","minLength":1,"enum":["active","inactive"],"description":"Whether this row is used to classify calls. Inactive rows stay listed and searchable but classify nothing, which is how a row the platform could not fully place stays visible to repair instead of being refused at import. A row imported without a country, or under a retired numbering form, arrives inactive.\n","example":"active"},"AdminVoiceBreakoutCode":{"allOf":[{"type":"object","required":["id","breakout_code","prefixes","country_code","number_type","status"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceBreakoutCodeID","description":"Unique identifier for this row."},"breakout_code":{"$ref":"#/components/schemas/AdminVoiceBreakoutLabel"},"country_code":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"description":"The country this code covers, as an ISO 3166-1 alpha-2 code. Derived from the code's prefixes on import when the file supplies none, and unanimously: a code is one destination, so prefixes that disagree leave it without a country. Editable afterwards, because a rate card's own country column is wrong wherever a calling code is shared.\n\nNull means no country is assigned, whether the calling code covers several countries, the range belongs to none (satellite, universal freephone), or nothing could be determined. Null never counts as domestic, and does not by itself mean the row is unfinished: a satellite range genuinely has no country and is valid active.\n","example":"UZ"},"prefixes":{"type":"array","items":{"type":"string","minLength":1,"maxLength":15,"pattern":"^[0-9]{1,15}$"},"description":"Every destination prefix that reaches this code, as E.164 digits with no leading plus.\n\nA prefix belongs to exactly one code, which is what decides who prices a call: a dialed number is classified by the code holding its longest matching prefix, and the coarser codes it also reaches stay available as cheaper-to-express rules. A prefix carries nothing of its own, so the country, status and reason above apply to all of a code's prefixes at once.\n","example":["31610","31612","31613"]},"number_type":{"oneOf":[{"$ref":"#/components/schemas/AdminVoiceBreakoutNumberType"},{"type":"null"}],"description":"The destination class calls under this code reach, or null when it has not been\nclassified.\n\nNothing works this out for you, unlike the country above: it is stored exactly as\nyou send it, and null until someone sets it.\n\nSet it from the code's PREFIXES, never from its name. Reading the name is the\nobvious shortcut and the wrong one: measured against a classification of 41,616\ndial codes it is right 91.2% of the time, and codes named for mobile carry\nlandline and special-service ranges. A wrong class is a wrong price.\n\nNull costs coverage rather than correctness: an unclassified code prices and\nroutes exactly as before, and is left off the public price list.\n","example":"mobile"},"status":{"$ref":"#/components/schemas/AdminVoiceBreakoutStatus"},"inactive_reason":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/AdminVoiceBreakoutInactiveReason"},{"type":"null"}],"description":"Why the platform parked this code, or null when it did not. Null on an inactive code is normal and means a person made it inactive deliberately.\n\nWorked out from the code's prefixes and country and kept in step with them, so assigning a country clears this at the same time. Not something you set.\n"},"inactive_reason_explanation":{"readOnly":true,"type":["string","null"],"description":"The reason above in a sentence, including what to do about it, so it reads the same everywhere it is shown.\n","example":"The calling code covers several countries and does not name one, so no country was assigned rather than guessed. Assign the country this range belongs to."},"description":{"type":["string","null"],"maxLength":1024,"description":"A free-text note, yours to write. Nothing on the platform ever changes it, which makes it the place to record why you assigned a country a rate card disagreed with. It survives an export and re-import unchanged.\n","example":"Ucell mobile range"}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminVoiceBreakoutCodeList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceBreakoutCode"}}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"AdminVoiceBreakoutVersion":{"allOf":[{"type":"object","required":["id","version","status","source_note","rows"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceBreakoutVersionID","description":"Unique identifier for this version."},"version":{"type":"integer","format":"int32","minimum":1,"description":"Sequential number, assigned when the draft is created.","example":7},"status":{"type":"string","minLength":1,"enum":["draft","active","superseded"],"description":"`draft` is the only editable state. `active` is the deck routing reads, and exactly one version is active at a time. `superseded` is a version that was live and has been replaced; it is kept so what was published at a point in time stays readable.\n","example":"draft"},"source_note":{"type":"string","minLength":1,"maxLength":512,"description":"What this version is, in your words. Name the card and the date it came from.\n","example":"BTS card 2026-07"},"rows":{"type":"integer","format":"int64","minimum":0,"description":"How many prefixes the version holds.","example":99858},"created_by":{"readOnly":true,"type":["string","null"],"description":"Identifier of the staff user who opened the draft."},"approved_by":{"readOnly":true,"type":["string","null"],"description":"Identifier of the staff user who published it."},"activated_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When this version became the live deck, or null if it never did."}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminVoiceBreakoutVersionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceBreakoutVersion"}}}},"AdminVoiceBreakoutVersionCreate":{"type":"object","additionalProperties":false,"required":["source_note"],"properties":{"source_note":{"type":"string","minLength":1,"maxLength":512,"description":"What this version is, in your words. Name the card and the date it came from.\n","example":"BTS card 2026-07"}}},"AdminVoiceBreakoutRowOp":{"type":"object","additionalProperties":false,"required":["breakout_code","prefixes"],"properties":{"breakout_code":{"$ref":"#/components/schemas/AdminVoiceBreakoutLabel"},"prefixes":{"type":"array","items":{"type":"string","minLength":1,"maxLength":16},"description":"The code's WHOLE prefix set, not a change to it: prefixes present here and not stored are added, stored and not present here are removed, and one that moves to a different code is re-pointed rather than duplicated.\n\nWhole-set rather than add/remove because you already hold the set, and because a delta protocol makes a dropped or reordered request silently lossy in a way a full set cannot be. A leading plus is accepted and stripped.\n","example":["31610","31612"]},"country_code":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"description":"The country this code covers. Send null to state that it has none, which is not the same as omitting the field: an omitted country is worked out from the code's prefixes, null is a decision to leave it without one.\n","example":"UZ"},"number_type":{"oneOf":[{"$ref":"#/components/schemas/AdminVoiceBreakoutNumberType"},{"type":"null"}],"description":"The destination class this code reaches. Send null to state that it has none, which is\nnot the same as omitting the field: an omitted class leaves whatever is already stored,\nnull clears it.\n\nThe distinction matters more here than it does for country_code, and for the opposite\nreason. A country is worked out from the prefixes when you omit it, so the flag exists\nthere to stop a derived value overwriting yours. Nothing works out a class, so an\nomitted field that cleared it would erase work nothing can rebuild, and every client\nwritten before this field omits it.\n\nA code left unclassified is priced and routed exactly as before and is simply not\npublished on the public price list, which is the right outcome both for one nobody has\nclassified yet and for one the platform refuses to call at all.\n","example":"mobile"},"status":{"$ref":"#/components/schemas/AdminVoiceBreakoutStatus"},"description":{"type":["string","null"],"maxLength":1024,"description":"A free-text note, yours to write. Nothing on the platform ever changes it.","example":"Ucell mobile range"}}},"AdminVoiceBreakoutOps":{"type":"object","additionalProperties":false,"description":"A batch of edits to one draft, applied in a single transaction so a save that\nboth changes and removes rows cannot half-apply.\n\nRows are keyed on `breakout_code`, not on a row id: it is the natural key within\na version and what the grid already holds. That also makes a batch idempotent,\nso a retried request re-applies the same values rather than duplicating\nanything, which is what lets edits stream as they are made.\n","properties":{"truncate":{"type":"boolean","description":"Empty the draft before applying, which is what an import is: the file replaces the deck rather than merging into it. Send it on the first chunk only.\n","example":false},"upserts":{"type":"array","maxItems":10000,"description":"Rows to create or overwrite. A country you supply is kept as given; omit the field entirely and it is worked out from the prefix, exactly as an import does.\n","items":{"$ref":"#/components/schemas/AdminVoiceBreakoutRowOp"}},"deletes":{"type":"array","maxItems":10000,"description":"Breakout codes to remove from the draft. Their prefixes go with them.\n","items":{"$ref":"#/components/schemas/AdminVoiceBreakoutLabel"}}}},"AdminVoiceBreakoutInactiveCount":{"type":"object","additionalProperties":false,"required":["reason","explanation","rows"],"properties":{"reason":{"$ref":"#/components/schemas/AdminVoiceBreakoutInactiveReason"},"explanation":{"type":"string","minLength":1,"description":"The reason in a sentence, including what to do about it.","example":"Uses a numbering form the country has withdrawn, so no live call can ever match it. Fix the prefix in the upstream rate card; this row cannot be repaired here, only deleted."},"rows":{"type":"integer","format":"int64","minimum":1,"description":"How many rows in this file have this reason.","example":15102}}},"AdminVoiceBreakoutImportResult":{"type":"object","additionalProperties":false,"required":["rows_total","prefixes_total","rows_inserted","rows_updated","rows_unchanged","rows_inactive","inactive_by_reason"],"properties":{"rows_total":{"type":"integer","format":"int64","minimum":0,"description":"Breakout codes read from the file.","example":975},"prefixes_total":{"type":"integer","format":"int64","minimum":0,"description":"How many prefixes those codes hold between them. This is the number you recognise from the file, since a rate card is a list of prefixes and a code is what the platform groups them into.\n","example":99858},"rows_inserted":{"type":"integer","format":"int64","minimum":0,"description":"Codes the deck did not previously carry.","example":1204},"rows_updated":{"type":"integer","format":"int64","minimum":0,"description":"Codes whose country, status, description or prefix set changed.","example":87},"rows_unchanged":{"type":"integer","format":"int64","minimum":0,"description":"Codes already stored exactly as the file has them, prefix sets included. These are not written at all, so re-uploading the same file reports every code unchanged.\n","example":98567},"rows_inactive":{"type":"integer","format":"int64","minimum":0,"description":"Codes in this file that classify nothing: no country could be determined, or their prefixes use a retired numbering form. They are imported rather than refused, so this count is what tells you to filter the list and find them.\n","example":15919},"inactive_by_reason":{"type":"array","description":"The inactive codes broken down by reason, in a fixed order so two imports of the same file read the same. Reasons with no codes are left out.\n\nRead this rather than the bare count: a whole country's withdrawn numbering form being parked is a single upstream problem, not one per code.\n","items":{"$ref":"#/components/schemas/AdminVoiceBreakoutInactiveCount"}}}},"AdminVoiceBreakoutDiffRow":{"type":"object","additionalProperties":false,"required":["breakout_code","kind"],"description":"One code's before and after between two versions.","properties":{"breakout_code":{"$ref":"#/components/schemas/AdminVoiceBreakoutLabel"},"kind":{"type":"string","minLength":1,"enum":["added","removed","changed"],"description":"`added` is in the draft only, `removed` in the live deck only, `changed` in both with different values. That includes a different PREFIX SET, since a moved prefix reprices every call under it while leaving both codes' own fields untouched.\n","example":"changed"},"before":{"description":"The code as the live deck has it. Absent for an addition. Its `prefixes` carries the right COUNT but not the values: one code can hold 20,497 of them, so even a capped sample shipping the sets could be millions of strings.\n","$ref":"#/components/schemas/AdminVoiceBreakoutCode"},"after":{"description":"The code as this draft has it. Absent for a removal. Same prefix caveat as `before`.","$ref":"#/components/schemas/AdminVoiceBreakoutCode"}}},"AdminVoiceBreakoutDiff":{"type":"object","additionalProperties":false,"required":["added","removed","changed","unchanged","sample"],"description":"What publishing this draft would change, against the deck currently live. The counts are exact and the sample is capped, because the decision rests on the shape of the change.\n","properties":{"added":{"type":"integer","format":"int64","minimum":0,"description":"Codes the live deck does not have.","example":1204},"removed":{"type":"integer","format":"int64","minimum":0,"description":"Codes the live deck has and this draft does not.","example":12},"changed":{"type":"integer","format":"int64","minimum":0,"description":"Codes in both, differing in country, status, note or prefix set.\n","example":87},"unchanged":{"type":"integer","format":"int64","minimum":0,"description":"Codes identical in both.","example":98555},"sample":{"type":"array","description":"Up to 500 differing codes, in code order.","items":{"$ref":"#/components/schemas/AdminVoiceBreakoutDiffRow"}}}},"VoiceRoutingVersionID":{"type":"string","minLength":1,"pattern":"^vrv_[0-9a-hjkmnp-tv-z]{26}$","example":"vrv_01krdgeqcxet5s7t44vh8rt9mg"},"AdminVoiceRoutingVersionStatus":{"type":"string","minLength":1,"enum":["draft","active","superseded"],"description":"`draft` is the only editable state. `active` is the graph every cell routes with, and exactly one version is active at a time. `superseded` was live and has been replaced; activating it again is rollback.","example":"draft"},"AdminVoiceRoutingVersion":{"allOf":[{"type":"object","required":["id","version","status","source_note"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceRoutingVersionID","description":"Unique identifier for this version."},"version":{"type":"integer","format":"int32","minimum":1,"description":"Sequential number, assigned when the draft is created; what an operator says out loud when rolling back.","example":7},"status":{"$ref":"#/components/schemas/AdminVoiceRoutingVersionStatus"},"source_note":{"type":"string","minLength":1,"maxLength":512,"description":"What this version is, in your words.","example":"DIDWW two-way pinning for NL"},"created_by":{"readOnly":true,"type":["string","null"],"description":"Identifier of the staff user who opened the draft."},"approved_by":{"readOnly":true,"type":["string","null"],"description":"Identifier of the staff user who activated it."},"activated_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When this version last became the live graph, or null if it never did."}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminVoiceRoutingVersionList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceRoutingVersion"}}}},"AdminVoiceRoutingVersionCreate":{"type":"object","additionalProperties":false,"required":["source_note"],"properties":{"source_note":{"type":"string","minLength":1,"maxLength":512,"description":"What this draft is for.","example":"Open Epsilon in eu1"}}},"AdminVoiceRoutingVersionImport":{"type":"object","additionalProperties":false,"required":["source_note","document"],"properties":{"source_note":{"type":"string","minLength":1,"maxLength":512,"description":"What this draft is for.","example":"Restored from the 2026-09-01 export"},"document":{"type":"string","minLength":1,"maxLength":4194304,"description":"The routing document as YAML, in the shape the export produces. JSON is accepted, being YAML. Rules may omit their ids; the import mints them.","example":"schema: 1\ntables:\n  root:\n    name: Root\n    rules: []\ncarriers: {}\ncredentials: {}\n"}}},"AdminVoiceRoutingVersionUpdate":{"type":"object","additionalProperties":false,"required":["source_note"],"properties":{"source_note":{"type":"string","minLength":1,"maxLength":512,"description":"The draft's new note.","example":"Open Epsilon in eu1 and us1"}}},"AdminVoiceRoutingSlug":{"type":"string","minLength":1,"maxLength":40,"pattern":"^[a-z0-9]+(_[a-z0-9]+)*$","description":"Name of a routing table, carrier or credential as rules and gateways reference it: lowercase words joined by single underscores, the same grammar as a supplier slug so `orig.supplier == \"didww\"` and a carrier `didww` read as one company. Immutable once set, because stored rules carry the text.","example":"didww"},"AdminVoiceRoutingDiffChange":{"type":"string","minLength":1,"enum":["added","removed","changed"],"description":"How activating the version would move this entry relative to the active one.","example":"changed"},"AdminVoiceRoutingDiffEntry":{"type":"object","additionalProperties":false,"required":["slug","change"],"properties":{"slug":{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"},"change":{"$ref":"#/components/schemas/AdminVoiceRoutingDiffChange"}}},"AdminVoiceRoutingProblem":{"type":"object","additionalProperties":false,"required":["param","message"],"description":"One thing wrong with the graph the draft now holds. A draft accepts these rather than refusing them: an operator builds a graph one rule at a time and the halfway states are invalid by nature, so a rule may name a table that does not exist yet. Publishing is what refuses them, with the same findings.","properties":{"param":{"type":"string","minLength":1,"description":"Path to what is wrong, addressed as the graph document spells it.","example":"tables.rest_of_world.rules[2].expression"},"message":{"type":"string","minLength":1,"example":"names table \"us_terminaton\", which this version does not define"}}},"AdminVoiceRoutingVersionDiff":{"type":"object","additionalProperties":false,"required":["tables","carriers","credentials","advisories","problems","has_base"],"description":"What activating a version would change against the active one, by slug, and what would refuse it. Read the two versions' tables and carriers for the detail; this answers where to look.","properties":{"tables":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceRoutingDiffEntry"}},"carriers":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceRoutingDiffEntry"}},"credentials":{"type":"array","description":"Credential references gained or lost; a credential's secret is not versioned and never appears here.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingDiffEntry"}},"has_base":{"type":"boolean","description":"Whether an active version was compared against. False makes an all-added diff readable: with nothing behind it, everything the version holds is new by definition, and no resource has a copy to be restored from.","example":true},"advisories":{"type":"array","description":"Findings that do not block publishing, such as a rule naming a supplier slug no held number carries yet. Recomputed on every read, so they survive the refresh that loses a write's own answer.","items":{"type":"string","minLength":1}},"problems":{"type":"array","description":"What is wrong with this version's graph. Empty means it is publishable. Recomputed on every read, so a tab opened on an invalid draft shows why publishing is refused without waiting for a write.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingProblem"}}}},"AdminVoiceRoutingSourceType":{"type":"string","enum":["","verify","voice_forward"],"description":"Call origin: `verify` for Verify OTP calls, `voice_forward` for platform forwarding, or empty for ordinary SIP trunk calls.","example":"verify"},"AdminVoiceCarrierRegion":{"type":"string","minLength":1,"enum":["eu1","us1"],"description":"The cell a call is routed in. A rule never names a region; the cell routing the call reads its own entry of the group's `regions`.","example":"eu1"},"AdminVoiceRoutingProbe":{"type":"object","additionalProperties":false,"required":["region","calls"],"properties":{"source_type":{"$ref":"#/components/schemas/AdminVoiceRoutingSourceType"},"region":{"$ref":"#/components/schemas/AdminVoiceCarrierRegion","description":"The cell to evaluate as; it sets `call.region` and selects each group's gateways for that region."},"calls":{"type":"array","minItems":1,"maxItems":25,"items":{"$ref":"#/components/schemas/AdminVoiceCallFlowProbeCall"}},"workspace_id":{"type":["string","null"],"minLength":1,"description":"Sets `call.ws_id`. Omit to leave it empty."},"organization_id":{"type":["string","null"],"minLength":1,"description":"Sets `call.org_id`. Omit to leave it empty."},"trunk_id":{"type":["string","null"],"minLength":1,"description":"Sets `call.trunk_id`. Omit for a call with no trunk, such as a Verify leg."},"billing_plan":{"type":["string","null"],"minLength":1,"description":"Sets `call.billing_plan`, the billing product id. Omit to leave it empty."}}},"AdminVoiceRoutingProbeOrigFacts":{"type":"object","additionalProperties":false,"description":"The `orig` namespace as the rules saw it. Every field empties together when the caller ID is not presentable, so an absent value is the empty string and the property is always present.","properties":{"number":{"type":"string","description":"Caller ID, digits only; empty when the workspace may not present it."},"country":{"type":"string","description":"Caller-ID country."},"type":{"type":"string","description":"Line type of the caller ID."},"provider":{"type":"string","description":"How the caller ID was obtained."},"supplier":{"type":"string","description":"Slug of the supplier that sold it."},"outbound_routing":{"type":"string","description":"Where a call presenting it must leave by."}}},"AdminVoiceRoutingProbeDestFacts":{"type":"object","additionalProperties":false,"description":"The `dest` namespace as the rules saw it.","required":["net"],"properties":{"number":{"type":"string","description":"Dialled number, digits only."},"country":{"type":"string","description":"Destination country."},"type":{"type":"string","description":"Line type of the dialled number."},"net":{"$ref":"#/components/schemas/AdminVoiceDestinationNetwork"}}},"AdminVoiceRoutingProbeCallFacts":{"type":"object","additionalProperties":false,"description":"The `call` namespace as the rules saw it. An absent value is the empty string.","properties":{"source_type":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminVoiceRoutingSourceType"}]},"zone":{"type":"string","description":"Calling zone the two countries put the call in."},"breakout_code":{"type":"string","description":"Rating key resolved for the destination."},"trunk_id":{"type":"string","description":"SIP trunk the call arrived on; empty for a Verify leg."},"ws_id":{"type":"string","description":"Workspace placing the call."},"org_id":{"type":"string","description":"Organization placing the call."},"billing_plan":{"type":"string","description":"Billing product id of that organization."},"region":{"type":"string","description":"Cell routing the call."}}},"AdminVoiceRoutingProbeFacts":{"type":"object","additionalProperties":false,"required":["orig","dest","call"],"description":"The facts as the rules saw them, by namespace.","properties":{"orig":{"$ref":"#/components/schemas/AdminVoiceRoutingProbeOrigFacts"},"dest":{"$ref":"#/components/schemas/AdminVoiceRoutingProbeDestFacts"},"call":{"$ref":"#/components/schemas/AdminVoiceRoutingProbeCallFacts"}}},"VoiceRoutingRuleID":{"type":"string","minLength":1,"pattern":"^vrr_[0-9a-hjkmnp-tv-z]{26}$","example":"vrr_01krdgeqcxet5s7t44vh8rt9mg"},"AdminVoiceRoutingProbeVerdict":{"type":"string","minLength":1,"enum":["matched","not_matched","error"],"description":"A `matched` table rule descended; the steps that follow are inside the table it named until evaluation resumes.","example":"matched"},"AdminVoiceRoutingProbeStep":{"type":"object","additionalProperties":false,"required":["table","rule_id","verdict"],"properties":{"table":{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"},"rule_id":{"$ref":"#/components/schemas/VoiceRoutingRuleID"},"verdict":{"$ref":"#/components/schemas/AdminVoiceRoutingProbeVerdict"}}},"AdminVoiceRoutingProbeFinalAction":{"type":"string","minLength":1,"enum":["route","block","refused"],"description":"`route` with the matched rule's carriers, `block` on a matched block rule, and `refused` when nothing matched, an expression failed to evaluate, or the destination was unroutable. A matched route whose groups dial nothing in the probed region is still `route`: carrier availability never feeds routing.","example":"route"},"AdminVoiceCarrierOutboundRef":{"type":"string","minLength":3,"maxLength":81,"pattern":"^[a-z0-9]+(_[a-z0-9]+)*:[a-z0-9]+(_[a-z0-9]+)*$","description":"One carrier's outbound group as a rule names it: the carrier slug, a colon, then the group slug, each of them a routing slug. Both halves are required, so adding a group to a carrier never changes what an existing rule dials.","example":"epsilon:domestic"},"AdminVoiceRoutingProbeCarrier":{"type":"object","additionalProperties":false,"required":["ref","present"],"description":"One rung of the matched route rule's carrier ladder, in hunt order.","properties":{"ref":{"$ref":"#/components/schemas/AdminVoiceCarrierOutboundRef"},"present":{"type":"boolean","description":"Whether this group dials anything in the probed region. False is the rung failing closed there; routing decided all the same."}}},"AdminVoiceRoutingProbeCallResult":{"type":"object","additionalProperties":false,"required":["destination_number","routable","steps","final_action","reason"],"properties":{"destination_number":{"type":"string","minLength":1},"routable":{"type":"boolean","description":"False when the destination refused before any rule was consulted; `reason` says why."},"facts":{"$ref":"#/components/schemas/AdminVoiceRoutingProbeFacts"},"steps":{"type":"array","description":"Every rule consulted, in order, across the tables descended into.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingProbeStep"}},"final_action":{"$ref":"#/components/schemas/AdminVoiceRoutingProbeFinalAction"},"matched_table":{"type":["string","null"],"minLength":1},"matched_rule_id":{"type":["string","null"],"minLength":1},"carriers":{"type":"array","description":"The matched route rule's carrier ladder, in hunt order, each rung with whether it dials anything in the probed region.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingProbeCarrier"}},"reason":{"type":"string","minLength":1,"description":"Why the decision came out as it did, in words.","example":"matched rule vrr_... in table two_way_pinning"}}},"AdminVoiceRoutingProbeResult":{"type":"object","additionalProperties":false,"required":["calls"],"properties":{"calls":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceRoutingProbeCallResult"}}}},"AdminVoiceRoutingGraphRuleAction":{"type":"string","minLength":1,"enum":["route","block","table"],"description":"What a matching call does. `route` sends it to the rule's carriers in preference order; `block` refuses it; `table` descends into another table, where a match is terminal and no match resumes here at the next priority.","example":"route"},"AdminVoiceRoutingGraphRuleExpression":{"type":"string","minLength":0,"maxLength":4000,"description":"Expression the call must satisfy for this rule to match, in the CEL dialect `AdminVoiceRoutingRuleExpression` documents in full. Unlike a dial plan's it is optional, may be empty, and need not compile: a draft holds rules an author has not finished writing, and the write reports the expression as a problem rather than refusing it. Publishing is what refuses it. Absent and empty both mean nothing has been written yet, so a rule with neither never matches a call.","example":"dst.country == DE && dst.type == TYPE_MOBILE"},"AdminVoiceRoutingRuleLabel":{"type":["string","null"],"maxLength":200,"description":"An operator's note on why this rule exists: who asked for it, what it is working around. CEL carries no comments, and a first-match-wins table gets its meaning from the rules around each one, so this is the only place that reasoning can live. Never read by selection.","example":"Blocks the premium ranges Finance flagged in July."},"AdminVoiceRoutingCarrierRef":{"type":"object","additionalProperties":false,"required":["ref"],"description":"One rung of the ladder. Its position in the list is its place in the hunt: the border dials the first rung with gateways in the serving region, skips any rung with none there, and falls to the next when a carrier fails to deliver or refuses the destination. It never falls past a callee who answered, busy included.","properties":{"ref":{"allOf":[{"$ref":"#/components/schemas/AdminVoiceCarrierOutboundRef"}],"description":"The carrier's outbound group this rung dials, as `<carrier>:<group>`. The cell routing the call resolves it to the gateways that group dials there."}}},"AdminVoiceRoutingGraphRule":{"type":"object","additionalProperties":false,"required":["id","priority","action"],"properties":{"id":{"$ref":"#/components/schemas/VoiceRoutingRuleID"},"priority":{"type":"integer","description":"Lower values are evaluated first, across the table; ties break by rule id. Blocks are negative by convention so they evaluate ahead of every routing rule.","example":10},"action":{"$ref":"#/components/schemas/AdminVoiceRoutingGraphRuleAction"},"expression":{"$ref":"#/components/schemas/AdminVoiceRoutingGraphRuleExpression"},"label":{"$ref":"#/components/schemas/AdminVoiceRoutingRuleLabel"},"carriers":{"type":"array","description":"The carrier ladder, in hunt order. Required for a `route` rule, absent otherwise.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingCarrierRef"}},"table":{"type":"string","maxLength":40,"pattern":"^([a-z0-9]+(_[a-z0-9]+)*)?$","description":"The table a `table` rule descends into, or empty on a draft rule whose author has not picked one yet. Absent on any other action."}}},"AdminVoiceRoutingTable":{"allOf":[{"type":"object","required":["slug","name","rules"],"properties":{"slug":{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"},"name":{"type":"string","minLength":1,"maxLength":120,"description":"A label for operators; the slug is the identifier.","example":"Two-way pinning"},"rules":{"type":"array","description":"The table's rules in evaluation order.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingGraphRule"}}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminVoiceRoutingTableList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceRoutingTable"}}}},"AdminVoiceRoutingGraphRuleWrite":{"type":"object","additionalProperties":false,"required":["priority","action"],"description":"One rule as it should stand after the write. The payload follows the action: `route` carries carriers, `table` carries a table, `block` neither; a write that contradicts that is refused. The shape is a discriminator beside optional payload fields rather than a `oneOf` because it is also the export document an operator hand-edits, where `action: route` next to `carriers:` is one rule; the server enforces the pairing the schema cannot.","properties":{"id":{"$ref":"#/components/schemas/VoiceRoutingRuleID","description":"The rule to keep, updated to this shape. Omit for a rule being added."},"priority":{"type":"integer","description":"Lower values are evaluated first, across the table; ties break by rule id. Blocks are negative by convention so they evaluate ahead of every routing rule.","example":10},"action":{"$ref":"#/components/schemas/AdminVoiceRoutingGraphRuleAction"},"expression":{"$ref":"#/components/schemas/AdminVoiceRoutingGraphRuleExpression"},"label":{"$ref":"#/components/schemas/AdminVoiceRoutingRuleLabel"},"carriers":{"type":"array","description":"The carrier ladder, in hunt order. Required for a `route` rule, absent otherwise.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingCarrierRef"}},"table":{"type":"string","maxLength":40,"pattern":"^([a-z0-9]+(_[a-z0-9]+)*)?$","description":"The table a `table` rule descends into, or empty on a draft rule whose author has not picked one yet. Absent on any other action."}}},"AdminVoiceRoutingTableWrite":{"type":"object","additionalProperties":false,"required":["name","rules"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120,"example":"Two-way pinning"},"rules":{"type":"array","maxItems":1000,"description":"Every rule the table should hold, in any order; priority decides the evaluation order.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingGraphRuleWrite"}},"updated_at":{"type":["string","null"],"format":"date-time","description":"The `updated_at` this write was based on. When set, the write is refused if the table changed in between, so two operators editing one table cannot silently overwrite each other. Omit when creating."}}},"AdminVoiceRoutingTableWriteResult":{"type":"object","additionalProperties":false,"required":["table","advisories","problems"],"properties":{"table":{"$ref":"#/components/schemas/AdminVoiceRoutingTable"},"advisories":{"type":"array","description":"Findings that did not refuse the write, such as a rule naming a supplier slug no held number carries yet. Kept apart from the verdict so a caller cannot turn a warning into a save blocker by accident.","items":{"type":"string","minLength":1},"example":["tables.two_way_pinning.rules[0].expression: no held number carries supplier \"epsilon\" yet"]},"problems":{"type":"array","description":"What is wrong with the graph this write leaves behind. Empty means the draft is publishable. Reported rather than refused, so the editor can mark the offending rule while the operator keeps working.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingProblem"}}}},"AdminVoiceRoutingTableRename":{"type":"object","additionalProperties":false,"required":["slug"],"description":"The slug to move to, and the token the row was read with. The rename and the repointing of every rule that descended into the old slug are one transaction, so the version never holds a rule naming a table that is gone.","properties":{"slug":{"allOf":[{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"}],"description":"The new slug. Refused if the version already has a table by it, if it is the slug already in use, or if it names the root table."},"updated_at":{"type":["string","null"],"format":"date-time","description":"The table's `updated_at` as the caller read it. The rename is refused if the row has moved on, because it writes the table as it read it and would otherwise discard whatever changed. Null skips the check. It guards THIS row only: the tables whose rules the repoint touches are written unconditionally, since refusing on a rule someone else added would leave the table renamed and the rules naming it behind."}}},"AdminVoiceRoutingDiscardResult":{"type":"object","additionalProperties":false,"required":["removed","advisories","problems"],"properties":{"removed":{"type":"boolean","description":"Whether discarding deleted the resource. True means the draft is what added it, so \"as the active version holds it\" is \"not at all\".","example":false},"advisories":{"type":"array","description":"Findings that do not block publishing, for the graph the discard leaves behind. Always present: an empty list means none, and the editor replaces what it was showing with this, so a warning about the discarded resource does not outlive it.","items":{"type":"string","minLength":1}},"problems":{"type":"array","description":"What is wrong with the graph the discard leaves behind. Empty means the draft is publishable. Reported rather than refused, so the editor can mark the offending rule while the operator keeps working.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingProblem"}}}},"AdminVoiceCarrierInbound":{"type":"object","additionalProperties":false,"description":"How this carrier's traffic is admitted, wherever it arrives. Each field is a method and presence is the switch: `cidrs` admits by source address, `credential` admits whatever answers a digest challenge, both admit either way, and neither admits nothing.","properties":{"cidrs":{"type":"array","maxItems":100,"description":"Source networks admitted for this carrier, in CIDR notation.","items":{"type":"string","minLength":1},"example":["203.0.113.0/24"]},"credential":{"oneOf":[{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"},{"type":"null"}],"description":"Credential this carrier authenticates against when challenged. Null admits no digest.","example":"didww_main"}}},"AdminVoiceCarrierGatewayTransport":{"type":["string","null"],"minLength":1,"enum":["udp","tcp","tls",null],"x-enum-varnames":["AdminVoiceCarrierGatewayTransportUdp","AdminVoiceCarrierGatewayTransportTcp","AdminVoiceCarrierGatewayTransportTls","AdminVoiceCarrierGatewayTransportNull"],"description":"SIP transport for the outbound leg. Null lets RFC 3263 resolution decide.","example":"tls"},"AdminVoiceCarrierGateway":{"type":"object","additionalProperties":false,"required":["host","origination_format","destination_format"],"description":"One dialable endpoint, the same wherever Bird dials it; which regions dial it, and in what order, is its group's `regions`. Two gateways that would dial the same Request-URI are refused as duplicates.","properties":{"host":{"type":"string","minLength":1,"maxLength":253,"description":"Hostname or address of the gateway. The Request-URI is composed from it, the port, the transport and the destination format.","example":"ams.example.net"},"port":{"type":["integer","null"],"minimum":1,"maximum":65535,"description":"Omit for the transport's default.","example":5061},"label":{"type":["string","null"],"minLength":1,"maxLength":120,"description":"A note for operators; nothing routes on it.","example":"Amsterdam primary"},"transport":{"$ref":"#/components/schemas/AdminVoiceCarrierGatewayTransport"},"origination_format":{"type":"string","minLength":1,"maxLength":64,"description":"How this carrier wants the calling number spelled; `{number}` stands for the number without its leading plus.","example":"{number}"},"destination_format":{"type":"string","minLength":1,"maxLength":64,"description":"How this carrier wants the dialled number spelled; `{number}` is required and any carrier prefix is written around it.","example":"1234#{number}"},"credential":{"oneOf":[{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"},{"type":"null"}],"description":"Slug of the credential this gateway authenticates with when the carrier challenges, shared with any other gateway naming it. Null when the carrier admits Bird by source address.","example":"didww_main"}}},"AdminVoiceRegionGatewayEntry":{"type":"object","additionalProperties":false,"required":["gateway","priority","weight"],"description":"One gateway's place in one region's list. `gateway` is its index in the enclosing outbound group's `gateways`. Lower priority is tried first; gateways sharing a priority split traffic by weight.","properties":{"gateway":{"type":"integer","minimum":0,"example":0},"priority":{"type":"integer","minimum":0,"example":0},"weight":{"type":"integer","minimum":1,"example":100}}},"AdminVoiceCarrierRegionConfig":{"type":"object","additionalProperties":false,"required":["gateways"],"description":"The outbound group's delivery settings for one originating region. Drafts may retain conflicting delivery settings as validation problems. The server enforces the local-gateway and exit-region constraints on activation; this schema permits those draft states.","properties":{"gateways":{"type":"array","maxItems":50,"description":"Local carrier gateways in selection order, indexed into the outbound group's gateway list. Activation requires this list to be empty when a foreign exit is selected.","items":{"$ref":"#/components/schemas/AdminVoiceRegionGatewayEntry"}},"exit_region":{"allOf":[{"$ref":"#/components/schemas/AdminVoiceCarrierRegion"}],"description":"Optional foreign region that emits the carrier leg. Activation requires that region to have gateways and no further redirect. Omit for local delivery."}}},"AdminVoiceCarrierOutbound":{"type":"object","additionalProperties":false,"required":["slug","name","gateways","regions"],"description":"One addressable set of a carrier's gateways, with the gateways each region dials out of it. A rule names this group as `<carrier>:<group>`; a carrier with a single group slugs it `main` by convention.","properties":{"slug":{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"},"name":{"type":"string","minLength":1,"maxLength":120,"example":"Domestic"},"gateways":{"type":"array","maxItems":50,"description":"Every endpoint Bird can dial for this group, in a region-agnostic list that `regions` indexes.","items":{"$ref":"#/components/schemas/AdminVoiceCarrierGateway"}},"regions":{"type":"object","description":"Each region configures its local gateway list or a foreign exit. An absent region dials no gateways.","additionalProperties":{"$ref":"#/components/schemas/AdminVoiceCarrierRegionConfig"},"example":{"eu1":{"gateways":[{"gateway":0,"priority":0,"weight":100}]},"us1":{"gateways":[],"exit_region":"eu1"}}}}},"AdminVoiceCarrier":{"allOf":[{"type":"object","required":["slug","name","inbound","suppliers","outbound"],"properties":{"slug":{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"},"name":{"type":"string","minLength":1,"maxLength":120,"example":"Epsilon"},"inbound":{"$ref":"#/components/schemas/AdminVoiceCarrierInbound"},"suppliers":{"type":"array","maxItems":50,"description":"Number suppliers whose DIDs this carrier may present on an inbound call. Empty or absent places no restriction, so any DID is allowed; naming suppliers confines the carrier to theirs.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"},"example":["didww"]},"outbound":{"type":"array","maxItems":20,"description":"This carrier's outbound groups, each a set of gateways a rule can name as `<carrier>:<group>`. Empty means nothing dials this carrier.","items":{"$ref":"#/components/schemas/AdminVoiceCarrierOutbound"}}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminVoiceCarrierList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceCarrier"}}}},"AdminVoiceCarrierWrite":{"type":"object","additionalProperties":false,"required":["name","inbound","outbound"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120,"example":"Epsilon"},"inbound":{"$ref":"#/components/schemas/AdminVoiceCarrierInbound"},"suppliers":{"type":"array","maxItems":50,"description":"Number suppliers whose DIDs this carrier may present on an inbound call. Empty or absent places no restriction, so any DID is allowed; naming suppliers confines the carrier to theirs.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"},"example":["didww"]},"outbound":{"type":"array","maxItems":20,"description":"This carrier's outbound groups, each a set of gateways a rule can name as `<carrier>:<group>`. Empty means nothing dials this carrier.","items":{"$ref":"#/components/schemas/AdminVoiceCarrierOutbound"}},"updated_at":{"type":["string","null"],"format":"date-time","description":"The `updated_at` this write was based on; when set, a carrier that changed in between refuses the write. Omit when creating."}}},"AdminVoiceCarrierWriteResult":{"type":"object","additionalProperties":false,"required":["carrier","problems"],"properties":{"carrier":{"$ref":"#/components/schemas/AdminVoiceCarrier"},"problems":{"type":"array","description":"What is wrong with the graph this write leaves behind. Empty means the draft is publishable. Reported rather than refused, so the editor can mark the offending rule while the operator keeps working.","items":{"$ref":"#/components/schemas/AdminVoiceRoutingProblem"}}}},"AdminVoiceCarrierRename":{"type":"object","additionalProperties":false,"required":["slug"],"description":"The slug to move to, and the token the row was read with. Shared by the carrier and outbound-group renames: what moves is whichever slug the path names. The rename and the rewrite of every rule that named the old slug are one transaction, so the version never holds a reference to a slug that is gone.","properties":{"slug":{"allOf":[{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"}],"description":"The new slug. Refused if a sibling already holds it (another carrier in the version, or another group under the same carrier), or if it is the slug already in use. Each operation's own description says which of the two it means."},"updated_at":{"type":["string","null"],"format":"date-time","description":"The carrier's `updated_at` as the caller read it. The rename is refused if the row has moved on, because it writes the carrier as it read it and would otherwise discard whatever changed. Null skips the check. It guards THIS row only: the tables whose rules the rewrite touches are written unconditionally, since refusing on a rule someone else added would leave the carrier renamed and the rules naming it behind."}}},"AdminVoiceCarrierCredential":{"allOf":[{"type":"object","required":["slug","username","has_secret"],"properties":{"slug":{"$ref":"#/components/schemas/AdminVoiceRoutingSlug"},"username":{"type":"string","minLength":1,"maxLength":120,"example":"bird_eu"},"has_secret":{"type":"boolean","description":"Whether a password is stored. A version naming a credential without one cannot be activated.","example":true}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminVoiceCarrierCredentialList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminVoiceCarrierCredential"}}}},"AdminVoiceCarrierCredentialWrite":{"type":"object","additionalProperties":false,"required":["username"],"properties":{"username":{"type":"string","minLength":1,"maxLength":120,"example":"bird_eu"},"password":{"type":"string","minLength":1,"maxLength":512,"writeOnly":true,"x-sensitive":true,"description":"Sets or rotates the secret. Omit to keep the stored one.","example":"s3cr3t"}}},"EsimSupplierID":{"type":"string","minLength":1,"pattern":"^esp_[0-9a-hjkmnp-tv-z]{26}$","example":"esp_01krdgeqcxet5s7t44vh8rt9mg"},"AdminEsimSupplierState":{"type":"string","minLength":1,"enum":["active","degraded","draining","retired"],"description":"Registry state of a supplier.\n\n- `active`: eligible for new-order routing.\n- `degraded`: temporarily removed from routing by drift or health signals; existing eSIMs keep working.\n- `draining`: permanently removed from routing; live eSIMs run out their packages.\n- `retired`: no bindings with unexpired packages remain; terminal.\n"},"AdminEsimSupplier":{"unevaluatedProperties":false,"allOf":[{"type":"object","description":"A wholesale eSIM supplier in the routing registry. Suppliers are never visible to customers.","required":["id","slug","display_name","state","request_rate_per_second","has_credentials"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimSupplierID"}]},"slug":{"type":"string","minLength":1,"readOnly":true,"description":"Stable machine name, set at creation.","example":"firsty"},"display_name":{"type":"string","minLength":1,"description":"Human-readable name for admin surfaces.","example":"Firsty"},"state":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminEsimSupplierState"}]},"request_rate_per_second":{"type":"number","minimum":0.1,"description":"The account's declared API rate budget. Order traffic takes priority inside it; background reads use the remainder."},"has_credentials":{"type":"boolean","readOnly":true,"description":"Whether API credentials are stored. Credentials are write-only and never returned."},"esim_count":{"type":"integer","minimum":0,"readOnly":true,"description":"eSIMs ever issued on this supplier, counted from its immutable ICCID bindings in this region. Bindings never move or unbind, so this only grows. Served on the supplier detail read only.\n"},"unexpired_esim_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Of the bound eSIMs, how many still hold an unexpired package. Retiring is refused while this is non-zero; a draining supplier reaches zero as its live packages expire. Served on the supplier detail read only.\n"}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminEsimSupplierList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEsimSupplier"}}}},"AdminEsimSupplierCreate":{"type":"object","additionalProperties":false,"description":"Registers a supplier. It starts with every capability unknown and cannot serve orders until its capabilities are declared and it is activated.","required":["slug","display_name","adapter","request_rate_per_second"],"properties":{"slug":{"type":"string","minLength":1,"pattern":"^[a-z][a-z0-9_-]{1,63}$","description":"Stable machine name. Immutable after creation.","example":"firsty"},"display_name":{"type":"string","minLength":1,"example":"Firsty"},"adapter":{"type":"string","minLength":1,"description":"Name of the in-repo adapter implementation that serves this supplier.","example":"firsty"},"base_url":{"type":"string","format":"uri","description":"The supplier API endpoint the adapter calls."},"request_rate_per_second":{"type":"number","minimum":0.1},"credentials":{"type":"string","minLength":1,"writeOnly":true,"x-sensitive":true,"description":"API credential material for the adapter, stored encrypted. Write-only; rotate by writing a new value."}}},"AdminEsimSupplierUpdate":{"type":"object","additionalProperties":false,"description":"Fields that can change on a supplier. State transitions are validated; retiring requires no bindings with unexpired packages.","properties":{"display_name":{"type":"string","minLength":1},"state":{"$ref":"#/components/schemas/AdminEsimSupplierState"},"base_url":{"type":"string","format":"uri"},"request_rate_per_second":{"type":"number","minimum":0.1},"credentials":{"type":"string","minLength":1,"writeOnly":true,"x-sensitive":true,"description":"New credential material; replaces the stored value."}}},"AdminEsimIssuanceModel":{"type":"string","minLength":1,"enum":["synchronous","callback","stock","vendor_stock","unknown"],"description":"How the supplier delivers a profile: synchronous (minted in the API response, so orders complete inline), callback (acknowledged now, delivered asynchronously), stock (profiles are pre-purchased inventory on the supplier side; serving orders requires a maintained pool, and pool depth gates routing eligibility), or vendor_stock (a finite pool the vendor holds and assigns; the supplier picks the profile, so there is no Bird-side pool and exhaustion surfaces only as a refusal at order time)."},"AdminEsimEndUserIdentity":{"type":"string","minLength":1,"enum":["none","email","full_kyc","unknown"],"description":"What the supplier requires about the person behind an eSIM.\n\n- `none`: no end-user data required; guest checkout works.\n- `email`: an end-user record with an email address is required per eSIM.\n- `full_kyc`: a full identity is required per eSIM - name, postal address, and identity documents.\n- `unknown`: not yet declared; non-sellable.\n"},"AdminEsimCapabilityValue":{"type":"string","minLength":1,"enum":["yes","no","unknown"],"description":"Declared capability answer. There are no defaults; `unknown` means the supplier has not stated it, and an unknown capability is never sold against."},"AdminEsimSupplierMsisdn":{"type":"string","minLength":1,"enum":["always","optional","no","unknown"],"description":"Whether a subscription comes with a phone number (MSISDN). `always` means every subscription carries one; `optional` means the vendor attaches one on request per subscription, so data-only stays orderable; `no` means the vendor never attaches one; `unknown` means the supplier has not stated it and is never sold against."},"AdminEsimUsageFeed":{"type":"string","minLength":1,"enum":["realtime","hourly","daily","file","none","unknown"],"description":"How the supplier reports usage, which bounds how fresh balances and usage records can be. none means the supplier keeps no queryable usage history at all; eSIMs it serves stay routable, but their usage read returns no records and the catalog should say so."},"AdminEsimSupplierCapabilities":{"type":"object","additionalProperties":false,"description":"The supplier's declared capability descriptor. Every field is stated explicitly or declared unknown; nothing defaults. Routing only offers what a supplier has affirmatively declared, so a wrong `no` withholds product while a wrong `yes` sells what cannot be delivered.\n","required":["issuance","end_user_identity","suspend","resume","release","package_removal","install_state_feed","attach_event_feed","usage_country_split","webhook_signing","webhook_idempotency","scheduled_activation","throttled_bundles","msisdn","voice_in","voice_out","sms_in","sms_out","msisdn_persistence","msisdn_surcharge","msisdn_home_country","usage_feed","byte_scale","stack_max","balance_min_interval_seconds","history_retention_days"],"properties":{"issuance":{"$ref":"#/components/schemas/AdminEsimIssuanceModel"},"end_user_identity":{"$ref":"#/components/schemas/AdminEsimEndUserIdentity"},"suspend":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the supplier can pause service on an eSIM while preserving its active packages and their running validity. A pause that ends, removes, or forfeits packages is not a suspend - a supplier offering only that declares `no`. `no` or `unknown` refuses the customer suspend verb with a precondition error."},"resume":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether a suspension can be reversed with packages intact. A supplier with suspend `yes` but resume `no` or `unknown` gets neither verb - a pause that cannot be undone is not the product the surface sells."},"release":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the supplier can permanently retire a profile so it can never be installed or used again. `no` or `unknown` does not withhold the customer release - it is substituted with suspend where available plus a permanent quarantine of the ICCID, which never returns to any pool; the stranded inventory is the declared cost of lacking the verb."},"package_removal":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether an applied package can be removed. `no` or `unknown` refuses the customer package delete with a precondition error; slots then free only by expiry."},"install_state_feed":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the supplier reports profile install state. `no` leaves installation.state at its defaults and withholds the esim.installed event for this supplier's eSIMs."},"attach_event_feed":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the supplier reports network attachments. `no` leaves last_attachment null for this supplier's eSIMs."},"usage_country_split":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the usage feed attributes bytes to countries. `no` lands usage records with a null country rather than inventing one."},"webhook_signing":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the supplier signs its callbacks."},"webhook_idempotency":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether callbacks carry a deduplication identity."},"scheduled_activation":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the supplier can activate at a future time. `no` means our scheduler substitutes and customers see no difference."},"throttled_bundles":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether reduced-speed bundles exist, with their double low-usage and depletion signals."},"msisdn":{"allOf":[{"$ref":"#/components/schemas/AdminEsimSupplierMsisdn"}],"description":"Whether a subscription comes with a phone number (MSISDN) - on every subscription (`always`), on request per subscription with data-only still orderable (`optional`), or not at all. Every other phone-service answer rides on this one - no direction, persistence, or surcharge may be `yes`, and no home country may be stated, while this is `no` or `unknown`."},"voice_in":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the traveler can receive voice calls on the number. Voice and SMS are declared per direction because vendors provision and price each separately - travel eSIMs commonly ship receive-only service. `yes` requires `msisdn` `always` or `optional`."},"voice_out":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the traveler can place voice calls from the number. `yes` requires `msisdn` `always` or `optional`."},"sms_in":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the traveler can receive SMS on the number - the direction OTP delivery needs, and commonly the only phone service a travel eSIM has. `yes` requires `msisdn` `always` or `optional`."},"sms_out":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the traveler can send SMS from the number. `yes` requires `msisdn` `always` or `optional`."},"msisdn_persistence":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the number survives package changes and suspend rather than being reassigned. `yes` requires `msisdn` `always` or `optional`."},"msisdn_surcharge":{"allOf":[{"$ref":"#/components/schemas/AdminEsimCapabilityValue"}],"description":"Whether the number costs extra on top of the data bundle. Under `msisdn` `optional` this is the price of choosing to attach a number; under `always` it is a charge included on top of every bundle. `yes` requires `msisdn` `always` or `optional`."},"msisdn_home_country":{"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"description":"The ISO 3166-1 alpha-2 country every assigned number is homed in. Stated only when the vendor documents a single rule and `msisdn` is `always` or `optional`; null means no single documented rule is on record, whether the vendor is silent or homes numbers per destination. Stored uppercase; lowercase input is canonicalized."},"usage_feed":{"$ref":"#/components/schemas/AdminEsimUsageFeed"},"byte_scale":{"type":["integer","null"],"minimum":1,"maximum":1048576,"description":"Bytes per unit of the data volume this supplier reports. Bird multiplies the supplier's balance figures by it to reach bytes. Exactly 1, 1000000 or 1048576 is accepted; anything else is refused. `1` is a supplier that already reports bytes, so nothing is converted; the other two are the decimal and binary MB. A guess between the two MB scales is a silent 4.8% balance error, and an MB scale guessed for a byte-native supplier overstates every balance a millionfold. Null when unknown, which blocks selling."},"stack_max":{"type":["integer","null"],"minimum":1,"description":"Maximum concurrent packages per eSIM. Feeds the customer-visible package_limit. Null when unknown."},"balance_min_interval_seconds":{"type":["integer","null"],"minimum":0,"description":"Minimum interval between balance reads per eSIM. Drives the customer resync rate limit. Null when unknown."},"history_retention_days":{"type":["integer","null"],"minimum":1,"description":"How long the supplier retains order and top-up history, which bounds how far back a resume can prove a command landed. Null when unknown."}}},"EsimZoneID":{"type":"string","minLength":1,"pattern":"^ezn_[0-9a-hjkmnp-tv-z]{26}$","example":"ezn_01krdgeqcxet5s7t44vh8rt9mg"},"AdminEsimPoolSegment":{"type":"object","additionalProperties":false,"description":"One segment of a supplier's profile stock. Stock that is not zone-specific reports as a single segment with a null zone.","required":["zone_id","available","reserved","target","low_water"],"properties":{"zone_id":{"readOnly":true,"description":"Zone this stock serves, or null when the supplier's profiles are zone-agnostic.","oneOf":[{"$ref":"#/components/schemas/EsimZoneID"},{"type":"null"}]},"available":{"type":"integer","minimum":0,"readOnly":true,"description":"Unused profiles ready to serve orders."},"reserved":{"type":"integer","minimum":0,"readOnly":true,"description":"Profiles held by in-flight orders."},"target":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Configured stock level replenishment aims for. Null when only the default applies."},"low_water":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Configured alert threshold. Below it the segment alerts; at zero the supplier stops being a routing candidate for the zone. Null when only the default applies."}}},"AdminEsimSupplierPool":{"type":"object","additionalProperties":false,"description":"The maintained stock of pre-purchased profiles for a stock-issuance supplier. Orders are served from this pool; replenishment purchases happen on the supplier's own channel per the ops runbook and are recorded against the pool - Bird does not read the supplier's inventory.\n","required":["supplier_id","default_target","default_low_water","segments","last_synced_at"],"properties":{"supplier_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimSupplierID"}]},"default_target":{"type":"integer","minimum":0,"readOnly":true,"description":"Stock level replenishment aims for, for segments without their own target."},"default_low_water":{"type":"integer","minimum":0,"readOnly":true,"description":"Alert threshold for segments without their own."},"segments":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AdminEsimPoolSegment"}},"last_synced_at":{"type":["string","null"],"minLength":1,"format":"date-time","readOnly":true,"description":"When the pool's counts were last re-derived. Null before the first re-count."}}},"AdminEsimPoolZoneTarget":{"type":"object","additionalProperties":false,"description":"Per-zone override of the pool's target and alert threshold.","required":["zone_id","target","low_water"],"properties":{"zone_id":{"$ref":"#/components/schemas/EsimZoneID"},"target":{"type":"integer","minimum":0},"low_water":{"type":"integer","minimum":0}}},"AdminEsimSupplierPoolUpdate":{"type":"object","additionalProperties":false,"description":"Full statement of the pool's maintenance configuration. Zone overrides replace the stored set; a zone absent from the list falls back to the defaults.","required":["default_target","default_low_water"],"properties":{"default_target":{"type":"integer","minimum":0},"default_low_water":{"type":"integer","minimum":0},"zone_targets":{"type":"array","items":{"$ref":"#/components/schemas/AdminEsimPoolZoneTarget"}}}},"AdminEsimPoolProfileStatus":{"type":"string","minLength":1,"enum":["available","reserved","assigned","quarantined"],"description":"Where one pre-purchased ICCID stands in Bird's inventory. Only the first two count as stock.\n\n- `available`: unused, ready to serve an order.\n- `reserved`: held by an in-flight order.\n- `assigned`: bound to a Bird eSIM; terminal.\n- `quarantined`: permanently withheld; terminal. An ICCID that ever carried a Bird binding never returns to any pool.\n"},"AdminEsimPoolProfile":{"type":"object","additionalProperties":false,"description":"One pre-purchased ICCID in a stock supplier's pool: the row a reservation draws and the counts in the pool read are derived from. Identified by the ICCID itself, which is unique across suppliers.\n","required":["iccid","status","zone_id","recorded_at"],"properties":{"iccid":{"type":"string","minLength":1,"readOnly":true,"description":"The card's ICCID, as recorded at intake.","example":"8999900000000000101"},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminEsimPoolProfileStatus"}]},"zone_id":{"readOnly":true,"description":"Zone the stock was filed under, or null when the card is zone-agnostic and any attached subscription decides.","oneOf":[{"$ref":"#/components/schemas/EsimZoneID"},{"type":"null"}]},"recorded_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the ICCID was taken into the pool."}}},"AdminEsimPoolProfilePage":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEsimPoolProfile"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminEsimPoolProfileStatusUpdate":{"type":"string","minLength":1,"enum":["quarantined"],"description":"The one profile state an operator may set by hand. `reserved` and `assigned`\nbelong to the order engine, and `quarantined` cannot be undone: a withdrawn\ncard may already have been delivered to a traveler, and a supplier that\nrecycles unused ICCIDs could otherwise hand it out again. Intake leaves an\nICCID the pool already knows untouched, so a withdrawal is final - a card\nwithdrawn in error is replaced by the supplier, not restored.\n"},"AdminEsimPoolProfileUpdate":{"type":"object","additionalProperties":false,"minProperties":1,"description":"A correction to one recorded profile. Name at least one field; a field left out is left as it is.","properties":{"zone_id":{"oneOf":[{"$ref":"#/components/schemas/EsimZoneID"},{"type":"null"}],"description":"Zone to file the card under. Pass `null` to move it to the zone-agnostic segment, where any attached subscription decides. Naming the zone the card is already in changes nothing and succeeds."},"status":{"allOf":[{"$ref":"#/components/schemas/AdminEsimPoolProfileStatusUpdate"}],"description":"Withdraw the card. `quarantined` is the only state an operator can set, and it is permanent."}},"example":{"status":"quarantined"}},"AdminEsimSupplierPoolWithdrawalCreate":{"type":"object","additionalProperties":false,"description":"A batch of recorded ICCIDs to withdraw from the pool for good.","required":["iccids"],"properties":{"iccids":{"type":"array","items":{"type":"string","pattern":"^[0-9]{18,22}$","minLength":18,"maxLength":22},"minItems":1,"maxItems":10000,"description":"ICCIDs to quarantine, 18 to 22 digits each. One malformed entry refuses the whole batch; a repeated entry counts once."}}},"AdminEsimSupplierPoolWithdrawalResult":{"type":"object","additionalProperties":false,"description":"What one withdrawal batch changed. The split is the point: a batch that withdrew nothing named cards the order engine holds, cards already withdrawn, or another pool's cards, and the depth alone cannot say which.","required":["withdrawn_count","skipped_count","pool"],"properties":{"withdrawn_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct ICCIDs that were available and are now quarantined."},"skipped_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Distinct ICCIDs left as they were: reserved or assigned to an order, already quarantined, or not recorded in this supplier's pool."},"pool":{"allOf":[{"$ref":"#/components/schemas/AdminEsimSupplierPool"}],"readOnly":true,"description":"The pool after the batch, so the new depth is read in the same response."}}},"AdminEsimSupplierPoolStockCreate":{"type":"object","additionalProperties":false,"description":"A batch of pre-purchased ICCIDs to take into the pool.","required":["iccids"],"properties":{"zone_id":{"allOf":[{"$ref":"#/components/schemas/EsimZoneID"}],"description":"Zone these profiles are pre-bound to. Omit for stock any zone can draw on, which is the usual case - most profiles decide their zone when a subscription attaches."},"iccids":{"type":"array","items":{"type":"string","pattern":"^[0-9]{18,22}$","minLength":18,"maxLength":22},"minItems":1,"maxItems":10000,"description":"ICCIDs to record, 18 to 22 digits each. One malformed entry refuses the whole batch; a repeated entry is recorded once."}}},"AdminEsimSupplierPoolStockResult":{"type":"object","additionalProperties":false,"description":"What one intake batch changed. The two counts are taken over the batch's distinct ICCIDs, so they sum to fewer than the ICCIDs sent when the batch repeated one.","required":["recorded_count","already_present_count","pool"],"properties":{"recorded_count":{"type":"integer","minimum":0,"readOnly":true,"description":"ICCIDs the pool did not hold before and now holds as available stock."},"already_present_count":{"type":"integer","minimum":0,"readOnly":true,"description":"ICCIDs already on record, left in whatever state they were in. On a replayed purchase this is the whole batch. An ICCID belongs to one supplier for good, so a card another supplier already holds counts here too and is not added to this pool - which is why a batch that records nothing is either a replay or the wrong supplier's file, and the pool below is what settles it."},"pool":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminEsimSupplierPool"}],"description":"The pool as it stands after the batch, so its new depth needs no second read."}}},"EsimRoutingPolicyID":{"type":"string","minLength":1,"pattern":"^erp_[0-9a-hjkmnp-tv-z]{26}$","example":"erp_01krdgeqcxet5s7t44vh8rt9mg"},"AdminEsimRoutingPolicyMode":{"type":"string","minLength":1,"enum":["pin","prefer"],"description":"How the policy steers supplier selection.\n\n- `pin`: always route to the named supplier when it can serve; fall back to the cost ranking when it cannot.\n- `prefer`: route to the named supplier unless its cost exceeds the cheapest capable candidate by more than `threshold_percent`.\n"},"AdminEsimRoutingPolicy":{"unevaluatedProperties":false,"allOf":[{"type":"object","description":"An admin routing rule layered over the default lowest-wholesale-cost ranking. Scoped to one zone; the highest-priority matching policy wins. Country-grain control is a policy on a single-country zone. Takes effect on the next order, never retroactively.\n","required":["id","zone_id","mode","supplier_id","priority","enabled"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimRoutingPolicyID"}]},"zone_id":{"allOf":[{"$ref":"#/components/schemas/EsimZoneID"}],"description":"Zone this policy applies to."},"mode":{"$ref":"#/components/schemas/AdminEsimRoutingPolicyMode"},"supplier_id":{"allOf":[{"$ref":"#/components/schemas/EsimSupplierID"}],"description":"The supplier this policy steers toward."},"threshold_percent":{"type":["number","null"],"minimum":0,"description":"For prefer mode, how much more expensive the preferred supplier may be, as a percentage of the cheapest capable candidate's cost. Null for pin mode."},"priority":{"type":"integer","minimum":1,"description":"Lower number wins when several policies match an order."},"enabled":{"type":"boolean"},"notes":{"type":["string","null"],"description":"Why this policy exists, for the next admin."}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminEsimRoutingPolicyList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEsimRoutingPolicy"}}}},"AdminEsimRoutingPolicyCreate":{"type":"object","additionalProperties":false,"description":"Creates a routing policy for one zone. For country-grain control, target a single-country zone.","required":["zone_id","mode","supplier_id","priority"],"properties":{"zone_id":{"$ref":"#/components/schemas/EsimZoneID"},"mode":{"$ref":"#/components/schemas/AdminEsimRoutingPolicyMode"},"supplier_id":{"$ref":"#/components/schemas/EsimSupplierID"},"threshold_percent":{"type":"number","minimum":0,"description":"Required for prefer mode; rejected for pin mode."},"priority":{"type":"integer","minimum":1},"enabled":{"type":"boolean","default":true},"notes":{"type":"string"}}},"AdminEsimRoutingPolicyUpdate":{"type":"object","additionalProperties":false,"description":"Fields that can change on a routing policy. The zone is immutable; create a new policy to re-scope.","properties":{"mode":{"$ref":"#/components/schemas/AdminEsimRoutingPolicyMode"},"supplier_id":{"$ref":"#/components/schemas/EsimSupplierID"},"threshold_percent":{"type":["number","null"],"minimum":0},"priority":{"type":"integer","minimum":1},"enabled":{"type":"boolean"},"notes":{"type":["string","null"]}}},"AdminEsimRoutingTest":{"type":"object","additionalProperties":false,"description":"Dry-run the supplier selection an order for this offer would make right now. Nothing is ordered or charged.","required":["offer_id"],"properties":{"offer_id":{"$ref":"#/components/schemas/EsimOfferID"},"esim_id":{"allOf":[{"$ref":"#/components/schemas/EsimID"}],"description":"Dry-run a top-up instead. Top-ups have no supplier choice, so the result shows whether the eSIM's own network can serve the offer."}}},"AdminEsimRoutingExclusionReason":{"type":"string","minLength":1,"enum":["supplier_not_active","backing_held","descriptor_unsellable","throttling_unsupported","stacking_unsupported","pool_exhausted","cost_currency_mismatch"],"description":"Why a supplier was filtered out of a routing decision before ranking. The set is stable: reasons are recorded on every decision and read back months later.\n\n- `supplier_not_active`: the supplier's registry state does not take orders (degraded, draining, or retired).\n- `backing_held`: the offer's backing for this supplier is inactive, so it is held out of routing.\n- `descriptor_unsellable`: the supplier's capability descriptor is incomplete, so nothing can be sold from it.\n- `throttling_unsupported`: the offer throttles after its full-speed allowance and the supplier declares no throttled bundles.\n- `stacking_unsupported`: the offer is stackable and the supplier does not carry two concurrent packages.\n- `pool_exhausted`: the supplier issues from pre-purchased stock and its pool holds nothing available for this zone.\n- `cost_currency_mismatch`: the offer's backings are priced in different currencies, so no candidate can be ranked.\n"},"AdminEsimRoutingCandidate":{"type":"object","additionalProperties":false,"description":"One supplier's evaluation in a routing decision, in ranking order.","required":["supplier_id","eligible","selected","rank","supplier_product_ref"],"properties":{"supplier_id":{"$ref":"#/components/schemas/EsimSupplierID"},"eligible":{"type":"boolean","description":"Whether the supplier passed the filters (state, capabilities, full zone coverage, an active backing)."},"exclusion_reason":{"description":"Why the supplier was filtered out. Null when eligible.","oneOf":[{"$ref":"#/components/schemas/AdminEsimRoutingExclusionReason"},{"type":"null"}]},"rank":{"type":["integer","null"],"minimum":1,"description":"The 1-based position in the cost ranking. Null when ineligible."},"supplier_product_ref":{"type":"string","minLength":1,"description":"The supplier's own product identifier on the backing this evaluation ran against, naming which product the cost belongs to."},"wholesale_cost":{"description":"The backing's cost used for ranking. Null when ineligible.","oneOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"selected":{"type":"boolean","description":"Whether this candidate wins the order."}},"oneOf":[{"properties":{"eligible":{"const":true},"exclusion_reason":{"not":{"type":"string"}},"rank":{"type":"integer"},"wholesale_cost":{"type":"object"}},"required":["rank","wholesale_cost"]},{"properties":{"eligible":{"const":false},"exclusion_reason":{"type":"string","minLength":1},"rank":{"not":{"type":"integer"}},"wholesale_cost":{"not":{"type":"object"}}},"required":["exclusion_reason"]}]},"AdminEsimRoutingAppliedPolicy":{"type":"object","additionalProperties":false,"description":"The rule that decided the outcome, snapshotted from the evaluation itself rather than joined against today's policy list: the policy may have been edited or deleted since, and the decision still has to explain itself.\n","required":["id","mode","threshold_percent"],"properties":{"id":{"$ref":"#/components/schemas/EsimRoutingPolicyID"},"mode":{"$ref":"#/components/schemas/AdminEsimRoutingPolicyMode"},"threshold_percent":{"type":["number","null"],"minimum":0,"description":"The prefer ceiling the rule ran with, as a percentage of the cheapest capable candidate's cost. Null for pin mode."}},"oneOf":[{"properties":{"mode":{"const":"pin"},"threshold_percent":{"not":{"type":"number"}}}},{"properties":{"mode":{"const":"prefer"},"threshold_percent":{"type":"number"}}}]},"AdminEsimRoutingTestResult":{"type":"object","additionalProperties":false,"description":"The reproducible routing decision, with every candidate's evaluation and the rule that decided it.","required":["candidates","applied_policy"],"properties":{"candidates":{"type":"array","items":{"$ref":"#/components/schemas/AdminEsimRoutingCandidate"}},"applied_policy":{"description":"The routing policy that decided the outcome, or null when the default cost ranking decided.","oneOf":[{"$ref":"#/components/schemas/AdminEsimRoutingAppliedPolicy"},{"type":"null"}]}}},"AdminEsimRoutingDecision":{"type":"object","additionalProperties":false,"description":"One routing decision, exactly as recorded when an order was routed: the winning supplier, every candidate's evaluation, and the policy rule that decided, snapshotted at decision time and never re-evaluated against today's registry, catalog, or policies.\n","required":["id","offer_id","offer_revision","zone_id","supplier_id","applied_policy","candidates","created_at"],"properties":{"id":{"type":"string","format":"uuid","minLength":1,"readOnly":true,"description":"Identifier of the decision, as referenced by the order it routed."},"offer_id":{"allOf":[{"$ref":"#/components/schemas/EsimOfferID"}],"description":"The offer the order was placed against."},"offer_revision":{"type":"integer","minimum":1,"description":"The offer revision in force when the decision was made."},"zone_id":{"allOf":[{"$ref":"#/components/schemas/EsimZoneID"}],"description":"The zone the offer covered at decision time."},"supplier_id":{"description":"The supplier the order routed to, or null when no candidate was eligible.","oneOf":[{"$ref":"#/components/schemas/EsimSupplierID"},{"type":"null"}]},"applied_policy":{"description":"The routing policy that decided the outcome, or null when the default cost ranking decided.","oneOf":[{"$ref":"#/components/schemas/AdminEsimRoutingAppliedPolicy"},{"type":"null"}]},"candidates":{"type":"array","description":"Every supplier evaluated, with its recorded cost, rank, or exclusion.","items":{"$ref":"#/components/schemas/AdminEsimRoutingCandidate"}},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"example":"2026-05-20T09:14:52Z"}}},"EsimZoneType":{"type":"string","minLength":1,"enum":["local","regional","global"],"description":"Zone breadth: a single country, a multi-country region, or worldwide."},"AdminEsimZoneStatus":{"type":"string","minLength":1,"enum":["active","disabled"],"description":"Whether the zone is promoted.\n\n- `active`: the zone's offers appear in the customer catalog and accept new orders.\n- `disabled`: the zone is withdrawn. Its offers leave the customer catalog and new orders are refused, while eSIMs and packages already sold keep working. Reversible.\n"},"AdminEsimZone":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Staff view of a coverage zone: the customer shape plus the promotion status. The status is staff-only wire state, because customers only ever see promoted zones.\n","required":["id","name","revision","type","status","countries","created_at"],"properties":{"id":{"$ref":"#/components/schemas/EsimZoneID"},"name":{"type":"string","minLength":1,"description":"Human-readable zone name.","example":"Europe"},"revision":{"type":"integer","minimum":1,"description":"Increments whenever the country list changes."},"type":{"$ref":"#/components/schemas/EsimZoneType"},"status":{"$ref":"#/components/schemas/AdminEsimZoneStatus"},"countries":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/CountryCode"},"description":"Countries covered by the zone, as ISO 3166-1 alpha-2 codes."},"created_at":{"type":"string","format":"date-time","minLength":1}}},"AdminEsimZoneList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEsimZone"}}}},"AdminEsimZoneCreate":{"type":"object","additionalProperties":false,"description":"Creates a coverage zone at revision 1. The country list is the product guarantee every offer selling this zone inherits.","required":["name","type","countries"],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/EsimZoneType"}],"description":"Classification shown to buyers (local, regional, or global)."},"name":{"type":"string","minLength":1,"example":"Europe"},"countries":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/CountryCode"}}}},"AdminEsimZoneUpdate":{"type":"object","additionalProperties":false,"description":"Changes a zone. Any change to the country list bumps the zone's revision, making the coverage change observable to customers.","properties":{"type":{"allOf":[{"$ref":"#/components/schemas/EsimZoneType"}],"description":"Classification shown to buyers (local, regional, or global)."},"name":{"type":"string","minLength":1},"countries":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/CountryCode"}},"status":{"allOf":[{"$ref":"#/components/schemas/AdminEsimZoneStatus"}],"description":"Disabling stops promoting the zone: its offers leave the customer catalog and new orders are refused, while eSIMs already sold keep working. Re-activating reverses it. No revision bump either way.\n"}}},"AdminEsimOfferPricingType":{"type":"string","minLength":1,"enum":["bundle"],"description":"Commercial shape of an offer's terms.\n\n- `bundle`: a fixed allowance with a validity period.\n"},"EsimSpeed":{"type":"string","minLength":1,"enum":["full","reduced"],"description":"Speed class. full: data flows at full network speed until the allowance is used; reduced: speed is limited, typically after the full-speed allowance. Full-speed and reduced-speed packages cannot be combined on one eSIM.\n"},"AdminEsimOfferTerms":{"type":"object","additionalProperties":false,"description":"What a package sold from this offer delivers. They carry no money: the price a customer sees is the resolved rate of the offer's billing product. Routing serves an order from a supplier backing whose own product is equivalent to these terms.\n","required":["allowance_bytes","validity_days"],"properties":{"allowance_bytes":{"type":"integer","minimum":1,"readOnly":true,"description":"The bundle's data allowance, in exact bytes."},"throttled_after_bytes":{"type":["integer","null"],"minimum":1,"readOnly":true,"description":"For reduced-speed offers, the full-speed allowance before throttling starts. Null on full-speed offers."},"validity_days":{"type":"integer","minimum":1,"readOnly":true,"description":"How long a package stays valid once activated, capped in practice by the eSIM's service period."}}},"EsimOfferStatus":{"type":"string","minLength":1,"enum":["draft","active","retired"],"description":"Availability of an offer.\n\n- `draft`: being curated; never returned on the customer surface and not purchasable.\n- `active`: purchasable.\n- `retired`: no longer purchasable; packages already sold are unaffected.\n"},"AdminEsimOfferBase":{"type":"object","description":"Staff view of a catalog offer. Deliberately not the customer offer: that one carries a price resolved for the calling workspace, and an admin request has no workspace to resolve against. Staff curate the terms and the billing product; the amount a customer pays is that product's resolved rate.\n","required":["id","name","type","revision","zone_id","speed","stackable","terms","product","status","created_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferID"}]},"name":{"type":"string","minLength":1,"readOnly":true,"example":"Europe 10 GB / 30 days"},"type":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminEsimOfferPricingType"}],"description":"Which commercial shape the offer's terms take."},"revision":{"type":"integer","minimum":1,"readOnly":true,"description":"Increments whenever the offer's terms change. Orders lock the revision they were quoted at."},"zone_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimZoneID"}]},"speed":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimSpeed"}]},"stackable":{"type":"boolean","readOnly":true},"terms":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminEsimOfferTerms"}]},"product":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/BillingProductSlug"}],"description":"Billing product the offer's charges post under. Its workspace-resolved rate is what a customer is quoted and charged."},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferStatus"}]},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminEsimOfferSummary":{"unevaluatedProperties":false,"description":"Compact offer row for staff lists, being the offer without its embedded coverage zone. Fetch the offer or the zone for the country list.\n","allOf":[{"$ref":"#/components/schemas/AdminEsimOfferBase"}]},"AdminEsimOfferList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEsimOfferSummary"}}}},"AdminEsimOfferCreate":{"type":"object","additionalProperties":false,"description":"Creates an offer at revision 1, in draft status until activated via update. The offer carries no money amount: it names the billing product whose workspace-resolved rate is the customer's price, so custom pricing is a billing rate override, never an eSIM-side edit.\n","required":["name","zone_id","product","speed","stackable","allowance_bytes","validity_days"],"properties":{"name":{"type":"string","minLength":1,"example":"Europe 10 GB / 30 days"},"zone_id":{"$ref":"#/components/schemas/EsimZoneID"},"product":{"allOf":[{"$ref":"#/components/schemas/BillingProductSlug"}],"description":"Billing product the offer's charges post under. Its resolved rate is the listed and charged price."},"speed":{"$ref":"#/components/schemas/EsimSpeed"},"stackable":{"type":"boolean"},"allowance_bytes":{"type":"integer","minimum":1,"description":"The bundle's data allowance, in exact bytes."},"throttled_after_bytes":{"type":"integer","minimum":1,"description":"For reduced-speed offers, the full-speed allowance before throttling starts. Required when speed is reduced; rejected otherwise."},"validity_days":{"type":"integer","minimum":1,"description":"How long a purchased package lasts once counting starts."}}},"EsimZone":{"type":"object","additionalProperties":false,"description":"A coverage footprint: the set of countries eSIM service works in. Zones are curated centrally and shared by every offer selling the same footprint, so coverage is maintained in one place. The country list is the guarantee: an eSIM using a package from an offer in this zone works in at least these countries (the serving network may cover more).\n","required":["id","name","revision","type","countries","created_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimZoneID"}]},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Human-readable zone name.","example":"Europe"},"revision":{"type":"integer","minimum":1,"readOnly":true,"description":"Increments whenever the country list changes."},"type":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimZoneType"}]},"countries":{"type":"array","minItems":1,"readOnly":true,"items":{"$ref":"#/components/schemas/CountryCode"},"description":"Countries covered by the zone, as ISO 3166-1 alpha-2 codes."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"AdminEsimOffer":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AdminEsimOfferBase"},{"type":"object","description":"The offer with its coverage zone embedded, so a staff reader sees the country list the offer sells without a second fetch.","required":["zone"],"properties":{"zone":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimZone"}]}}}]},"AdminEsimOfferUpdate":{"type":"object","additionalProperties":false,"description":"Changes an offer. Any change to commercial terms bumps the offer's revision; in-flight orders keep the revision they locked.","properties":{"name":{"type":"string","minLength":1},"status":{"$ref":"#/components/schemas/EsimOfferStatus"},"allowance_bytes":{"type":"integer","minimum":1},"throttled_after_bytes":{"type":["integer","null"],"minimum":1,"description":"For reduced-speed offers, the full-speed allowance before throttling starts. Null is rejected while speed is reduced."},"validity_days":{"type":"integer","minimum":1}}},"AdminEsimOfferBacking":{"unevaluatedProperties":false,"allOf":[{"type":"object","description":"One supplier's product backing an offer. A backing must be equivalent to the offer's terms (allowance exact, validity exact, coverage a superset) or routing never selects it. The wholesale cost feeds routing and margin reporting only; it never touches the customer price.\n","required":["supplier_id","supplier_product_ref","wholesale_cost","active"],"properties":{"supplier_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimSupplierID"}]},"supplier_product_ref":{"type":"string","minLength":1,"description":"The supplier's own identifier for the product, as their catalog names it."},"wholesale_cost":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"The wholesale amount paid to the supplier per package sold from this backing."},"active":{"type":"boolean","description":"Inactive backings are held out of routing, for example after drifting out of equivalence."}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminEsimOfferBackingList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEsimOfferBacking"}}}},"AdminEsimOfferBackingUpsert":{"type":"object","additionalProperties":false,"description":"Sets or replaces this supplier's backing for the offer.","required":["supplier_product_ref","wholesale_cost"],"properties":{"supplier_product_ref":{"type":"string","minLength":1},"wholesale_cost":{"$ref":"#/components/schemas/Money"},"active":{"type":"boolean","default":true}}},"EsimStatus":{"type":"string","minLength":1,"enum":["provisioning","ready","activating","active","suspending","suspended","resuming","releasing","released","expired","failed"],"description":"Lifecycle state of an eSIM. A pending state always settles: to its target on\nsuccess, or back to the prior status when the network definitively rejects\nthe change, reported by the `esim.operation_failed` event.\n\n- `provisioning`: the initial data package is being applied; install credentials may already be available.\n- `ready`: installable and usable, waiting for first network use; allows top-up orders and release. Activate before `ready_until` or the eSIM expires.\n- `activating`: an activation is being applied.\n- `active`: in service; the service period ends at `active_until`. Allows top-up orders, suspend, resync, and release.\n- `suspending`: a suspension was requested and is being applied.\n- `suspended`: data service is paused; allows resume and release.\n- `resuming`: a resume was requested and is being applied.\n- `releasing`: a permanent release is in progress.\n- `released`: permanently released; terminal.\n- `expired`: dormant. No data package has been live (each one depleted, expired, removed, or failed) for the expiry window. Not terminal: a completed top-up returns the eSIM to `active`, and release remains available. Balances and the installed profile are untouched.\n- `failed`: provisioning failed; any charge on the owning order is credited back automatically, reported by that order. Terminal.\n"},"EsimInstallation":{"type":"object","additionalProperties":false,"description":"Device-side installation state of the eSIM profile.","required":["state"],"properties":{"state":{"type":"string","minLength":1,"readOnly":true,"x-extensible-enum":["pending","downloaded","installed","removed","error"],"description":"pending: not yet downloaded by a device; downloaded: downloaded but not installed; installed: installed on the device; removed: deleted from the device; whether the profile can be installed again depends on the carrier profile, so treat removal as final; error: download or installation failed, see error_reason. Open enum: installation state is reported by the device, so additional states may be added over time. Treat an unrecognized value as a future state, not an error.\n"},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the installation state last changed. Null before the first device interaction."},"error_reason":{"type":["string","null"],"readOnly":true,"description":"Human-readable reason installation failed, for example an ineligible device or an exhausted download limit. Null unless state is error.\n"}}},"EsimPackageStatus":{"type":"string","minLength":1,"enum":["provisioning","pending_first_use","active","depleted","expired","removing","removed","failed"],"description":"Lifecycle state of a data package.\n\n- `provisioning`: being applied to the eSIM.\n- `pending_first_use`: confirmed; activates on first use in its coverage zone.\n- `active`: consuming data; validity is running.\n- `depleted`: balance fully used.\n- `expired`: validity ended; unused balance is gone.\n- `removing`: a removal was requested and is being applied. On definitive rejection the package returns to its prior status, reported by `esim.operation_failed`.\n- `removed`: taken off the eSIM at your request; remaining balance forfeited.\n- `failed`: could not be applied; any charge is credited back automatically through the owning order, which reports this through its `failed` status and the `esim.order.failed` event.\n"},"EsimPackageBalance":{"type":"object","additionalProperties":false,"description":"Remaining balance of a data package. Balances update as usage is reported by mobile networks and can trail live usage: as_of tells you how fresh the numbers are.\n","required":["total_bytes","used_bytes","remaining_bytes","used_percent","as_of"],"properties":{"total_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Total data in bytes."},"used_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Data used in bytes."},"remaining_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Data remaining in bytes."},"used_percent":{"type":"number","minimum":0,"maximum":100,"readOnly":true,"description":"Share of the total data already used, as a percentage."},"as_of":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this balance was last confirmed."}}},"EsimPackage":{"type":"object","additionalProperties":false,"description":"One data package held by an eSIM: a single purchase with its own balance, validity, and coverage zone. Every order that completes creates exactly one package, so a package is always traceable to the order that bought it. An eSIM can hold several packages per zone; the right one is consumed automatically based on the device's location, and zone_balances on the eSIM carries the combined remainder per zone.\n","required":["id","order_id","offer_id","zone_id","zone_name","countries","status","speed","balance","price","created_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimPackageID"}]},"order_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOrderID"}],"description":"The order that purchased this package."},"offer_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferID"}],"description":"Offer this package was purchased from."},"zone_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimZoneID"}],"description":"Coverage zone the package draws on. The name and countries below are captured at purchase time; the zone resource carries the live footprint."},"zone_name":{"type":"string","minLength":1,"readOnly":true,"description":"Coverage zone name, from the offer.","example":"Europe"},"countries":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/CountryCode"},"description":"Countries the package's zone covers, captured at purchase time so the package is meaningful without fetching the offer.\n"},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimPackageStatus"}]},"speed":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimSpeed"}],"description":"Speed class, from the offer."},"balance":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimPackageBalance"}]},"activated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the package started consuming data (first use in its zone). Null until then."},"expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the package's validity ends and unused balance expires. Already capped by the eSIM's service period, so this is always the effective expiry. Null until the package activates.\n"},"price":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What your workspace was billed for this package."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"EsimZoneBalance":{"type":"object","additionalProperties":false,"description":"Remaining data for one coverage zone, combined across the eSIM's packages for that zone. Zones can overlap in a country; entries are per zone, not per country, so summing entries can count a country's usable data twice. Which package serves a session in an overlap is network-determined.","required":["zone_id","total_bytes","used_bytes","remaining_bytes","as_of"],"properties":{"zone_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimZoneID"}]},"total_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Total purchased data for the zone, in bytes."},"used_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Data used, in bytes."},"remaining_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Data remaining, in bytes."},"as_of":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Freshness of this combined figure - the oldest balance read among the zone's contributing packages. Each package's own balance.as_of can be newer."}}},"EsimActionName":{"type":"string","minLength":1,"x-extensible-enum":["suspend","resume","release","install","top_up"],"description":"An operation you can attempt on an eSIM.\n\n- `suspend`: pause data service, keeping packages and their validity running.\n- `resume`: restore service to a suspended eSIM.\n- `release`: permanently retire the eSIM.\n- `install`: read the install credentials for a device.\n- `top_up`: order another data package onto this eSIM.\n\nTolerate a value you do not recognize: we report on more actions over time.\n"},"EsimActionUnavailableReason":{"type":"string","minLength":1,"x-extensible-enum":["permission_denied","network_unsupported","network_unconfirmed","esim_state","operation_in_progress","package_limit_reached"],"description":"Why an action is unavailable. Three separate things can stand in the way:\nyour own permissions, what the eSIM's serving mobile network can do, and the\neSIM's current state. Each one has its own value, and none of them stands in\nfor another.\n\nYour permissions:\n\n- `permission_denied`: your credential does not carry the scope this\n  operation requires. This answer outranks the others, so it is the only one\n  you see even where the eSIM or its network would also stand in the way.\n  Adding the scope, or using a credential that holds it, gets you the eSIM's\n  own answer, which may be another reason here.\n\nThe serving mobile network:\n\n- `network_unsupported`: the mobile network serving this eSIM does not offer\n  this operation, so nothing makes it available on this eSIM. A later eSIM\n  served by another network may permit it.\n- `network_unconfirmed`: we have not established whether this network offers\n  the operation, so we withhold it rather than attempt it. The network has\n  not refused it. Contact support if you need this operation.\n\nThe eSIM's current state:\n\n- `esim_state`: the eSIM's `status`, or its not yet having a profile, does\n  not allow this operation. Read `status` for the state it is in, and\n  `EsimStatus` for what each state allows.\n- `operation_in_progress`: an order for this eSIM has not finished, so it may\n  still deliver a data package onto the profile. Retry once that order\n  reaches a terminal status.\n- `package_limit_reached`: the eSIM already holds `package_limit` concurrent\n  data packages. A package counts from the moment it is ordered until it is\n  removed or expires, so one that has used up its data still holds its place.\n  The action becomes available again once a package leaves.\n\nTolerate a value you do not recognize, and treat it as unavailable for a\nreason you cannot act on.\n"},"EsimAvailableAction":{"type":"object","additionalProperties":false,"description":"One action, and whether this eSIM permits it for you right now.\n","required":["action","operation","available","reason"],"properties":{"action":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimActionName"}]},"operation":{"type":"string","minLength":1,"readOnly":true,"description":"The operationId that performs the action. The operation's own reference page says how to call it; this says only which one.\n","example":"suspendEsim"},"available":{"type":"boolean","readOnly":true,"description":"Whether the action would be accepted as this eSIM stands. Advice, not permission to act: it is evaluated when the eSIM is read, and the operation checks everything here again when you call it. A `true` that has gone stale is refused at that point, so treat it as a reason to offer the action rather than as a guarantee it will succeed.\n"},"reason":{"readOnly":true,"description":"Why the action is unavailable. Null while `available` is true.","oneOf":[{"$ref":"#/components/schemas/EsimActionUnavailableReason"},{"type":"null"}]},"requires":{"type":"array","readOnly":true,"items":{"type":"string","minLength":1},"description":"Parameters this eSIM's current state makes mandatory, which the operation's own schema can only describe as optional. Releasing an eSIM that still holds paid data requires `acknowledge_balance_forfeit`, for example. Absent when the current state makes nothing extra mandatory, and absent on an action refused for your permissions, the serving network, or a state that forbids it, since there is no call to add a parameter to. A refusal you can wait out still carries it.\n","example":["acknowledge_balance_forfeit"]}}},"EsimNetworkAttachment":{"type":"object","additionalProperties":false,"description":"The most recent mobile network the device attached to.","required":["country_code","attached_at"],"properties":{"country_code":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"description":"Country of the network."},"country_name":{"type":["string","null"],"readOnly":true,"description":"English name of the country, or null when not known."},"network_name":{"type":["string","null"],"readOnly":true,"description":"Name of the mobile network, or null when not known."},"attached_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the device attached."}}},"Esim":{"unevaluatedProperties":false,"allOf":[{"type":"object","required":["id","status","iccid","phone_number","installation","packages","zone_balances","package_limit","usage_available","order_id"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimID"}]},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimStatus"}]},"iccid":{"type":["string","null"],"readOnly":true,"description":"ICCID of the eSIM profile. Null while no profile is allocated yet, for example when provisioning failed before allocation.","example":"8944500212345678912"},"phone_number":{"type":["string","null"],"readOnly":true,"description":"Phone number attached to this eSIM, in E.164 format, as the supplier reports it. Null while none is on record: a data-only plan comes with no number, and a plan that includes one reports it after provisioning.\n","example":"+31612345678"},"order_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOrderID"}],"description":"The order that created this eSIM."},"display_name":{"type":["string","null"],"maxLength":120,"description":"Free-text label for your own reference, for example a traveler or order reference.","example":"Amsterdam trip, order 8812"},"installation":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimInstallation"}]},"packages":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/EsimPackage"},"description":"Current data packages, one per purchase."},"zone_balances":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/EsimZoneBalance"},"description":"Remaining data per coverage zone, combined across the zone's packages. Derived; the packages are the source of truth."},"available_actions":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/EsimAvailableAction"},"description":"What this eSIM permits you to do right now, with a reason for each action it does not. Every action we report on is listed, whether or not it is available, so a `false` with a reason is the answer and a missing entry never is.\nThe eSIM read always sends this. It is absent only where this schema is reused to list eSIMs, since the answer is per caller and a list does not carry it.\nThis is advice for deciding what to offer and what to explain, never authorization: it is evaluated when the eSIM is read, and each operation re-checks all of it when called. Do not cache it as a grant.\n"},"package_limit":{"type":"integer","minimum":1,"readOnly":true,"example":3,"description":"Maximum number of concurrent data packages this eSIM can hold, counted across all zones; several packages may share one zone. Enforced when packages are added.\n"},"usage_available":{"type":"boolean","readOnly":true,"example":true,"description":"Whether daily usage records exist for this eSIM. False when the serving network keeps no queryable usage history: balances and packages still work, and the usage read returns an empty list rather than zeros.\n"},"ready_until":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Activate (first network use) before this moment or the eSIM expires. Null once activated."},"activated_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the eSIM first used a mobile network. Null until then."},"active_until":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the eSIM's service period ends. The period starts at activation and data packages cannot outlive it. Null until activated.\n"},"last_attachment":{"readOnly":true,"description":"Most recent network attachment, or null before first attach.","oneOf":[{"$ref":"#/components/schemas/EsimNetworkAttachment"},{"type":"null"}]},"tags":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/Tag"},"description":"Tags for routing, filtering, and stats grouping, echoed on webhook events for the eSIM."},"metadata":{"type":"object","additionalProperties":true,"description":"Your own key-value data, echoed on webhook events for the eSIM. Maximum 2 KB serialized."}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminEsim":{"type":"object","additionalProperties":false,"description":"Staff view of an eSIM, for cross-workspace support lookup. Adds what the customer surface deliberately hides, the workspace and the serving supplier.","required":["workspace_id","supplier_id","esim"],"properties":{"workspace_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WorkspaceID"}]},"supplier_id":{"readOnly":true,"description":"The supplier bound to this eSIM's ICCID, immutable for the eSIM's life. Null while no profile is allocated, for example when provisioning failed before allocation.","oneOf":[{"$ref":"#/components/schemas/EsimSupplierID"},{"type":"null"}]},"esim":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Esim"}]}}},"AdminEsimPage":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEsim"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminEsimRelease":{"type":"object","additionalProperties":false,"description":"Staff release of an eSIM.","required":["reason"],"properties":{"reason":{"type":"string","minLength":1,"description":"Why this eSIM is being released, for the audit record."},"acknowledge_balance_forfeit":{"type":"boolean","default":false,"description":"Confirms the operator accepts that remaining package balances are forfeited. Omitted or false while unexpired balance remains, the request is refused with a conflict.\n"}}},"EsimOrderStatus":{"type":"string","minLength":1,"enum":["scheduled","charging","provisioning","pending","completed","failed","canceled"],"description":"Order state.\n\n- `scheduled`: accepted to run at a future time; nothing has been charged.\n- `charging`: securing funds for the quoted price. An order the wallet cannot fund yet stays here, with `funding` naming the total balance the wallet must hold (not the shortfall) and when the wait lapses; retry it after a top-up, or cancel it.\n- `provisioning`: the purchase is being applied while your request is still being served.\n- `pending`: the request returned `202` and the purchase is completing asynchronously; poll the order or listen for its events.\n- `completed`: the eSIM and package are live and their ids are set on the order; terminal.\n- `failed`: nothing was delivered; any charge is credited back automatically, and `refund_transaction_id` is set when the credit lands. Terminal.\n- `canceled`: a scheduled order was withdrawn before it ran; nothing was charged. Terminal. Reserved for scheduled orders - canceling a funding-parked order lands `failed` with `failure_code: canceled` instead, because a charge that raced in must follow `failed`'s refund contract.\n"},"EsimDelivery":{"type":"object","additionalProperties":false,"description":"Where and how install credentials reach the traveler. The recipient is a raw address, so delivery works for travelers who are not stored anywhere: an email address for email, an E.164 phone number for sms.\n","required":["to","channel"],"properties":{"to":{"type":"string","minLength":3,"description":"Recipient address. An email address for the email channel, an E.164 phone number for the sms channel.","example":"traveler@example.com"},"channel":{"$ref":"#/components/schemas/EsimDeliveryChannel"},"locale":{"allOf":[{"$ref":"#/components/schemas/LanguageTag"}],"description":"Language for the message. Falls back to the closest available language, then English."}}},"EsimOrderFunding":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Why the order is parked in `charging` and what unblocks it. The wallet could not fund the last charge attempt; top up the wallet (or retry once funds arrive) and the charge proceeds on its own. A funded order then provisions and either completes, or fails and refunds automatically.\n","required":["required_amount","lapses_at"],"properties":{"required_amount":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"The amount the wallet must hold for the charge to succeed, including tax, exactly as the billing engine reported it on the last refused attempt. The balance it was refused against is not echoed here - it goes stale the moment funds move, so read the live balance from billing."},"lapses_at":{"type":"string","minLength":1,"format":"date-time","example":"2026-09-08T14:51:01Z","description":"The earliest time a still-refused charge attempt fails the order with `failure_code: insufficient_balance` instead of parking it again. Not a hard expiry: a top-up landing after this time can still complete the order, right up to its next charge attempt."}}},"EsimOrder":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/Timestamps"},{"type":"object","description":"One purchase: a new eSIM with its first package, or an additional package on an existing eSIM. The order locks the quote at creation (the offer revision and the exact price) and is the durable record of the purchase: poll it until a terminal status when it does not complete inline. Orders that can be served immediately complete within the request.\n","required":["id","status","offer_id","offer_revision","zone_id","esim_id","package_id","price","wallet_transaction_id","refund_transaction_id","funding","failure_code","failure_reason","completed_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOrderID"}]},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOrderStatus"}]},"offer_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferID"}],"description":"Offer purchased."},"offer_revision":{"type":"integer","minimum":1,"readOnly":true,"description":"Revision of the offer this order locked at creation. The quoted price stays that of this revision even if the offer changes later. The produced package snapshots its coverage at purchase; the zone's live country list governs new sales only."},"zone_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimZoneID"}],"description":"Coverage zone of the purchased offer, captured at creation."},"esim_id":{"readOnly":true,"description":"The eSIM the package lands on. Set at creation when adding to an existing eSIM; set when provisioning starts for a new-eSIM order; null before that.","oneOf":[{"$ref":"#/components/schemas/EsimID"},{"type":"null"}]},"package_id":{"readOnly":true,"description":"The purchased data package, set when the order completes; null before that.","oneOf":[{"$ref":"#/components/schemas/EsimPackageID"},{"type":"null"}]},"price":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"The quoted and charged price, locked at creation in your billing currency."},"wallet_transaction_id":{"readOnly":true,"description":"The wallet transaction that paid for this order, for reconciling against your billing transactions. Null until the charge lands.","oneOf":[{"$ref":"#/components/schemas/WalletTransactionID"},{"type":"null"}]},"refund_transaction_id":{"readOnly":true,"description":"The wallet transaction that credited the charge back after a failure. Null unless the order failed after charging.","oneOf":[{"$ref":"#/components/schemas/WalletTransactionID"},{"type":"null"}]},"delivery":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimDelivery"}],"description":"Where install credentials are delivered once available. Present when requested at creation."},"funding":{"readOnly":true,"description":"Present once a charge attempt was refused for insufficient funds, while the order waits in `charging`; null otherwise. Recording the refusal details is best-effort, so a `charging` order can carry null here and still be waiting for funds. Read the required balance from billing rather than treating null as funded.","oneOf":[{"$ref":"#/components/schemas/EsimOrderFunding"},{"type":"null"}]},"failure_code":{"type":["string","null"],"readOnly":true,"x-extensible-enum":["insufficient_balance","carrier_error","capacity_exhausted","offer_unavailable","internal_error","canceled","resolved_by_support","esim_released"],"description":"Machine-readable reason the order failed. Null unless status is failed. Open enum: treat unrecognized values as future failure kinds. canceled means the workspace canceled the order while it was waiting for funds; resolved_by_support means Bird support closed a stuck order as failed; esim_released means the top-up target became unserviceable before delivery: its eSIM was releasing, released, or failed. In every case any charge has been credited back."},"failure_reason":{"type":["string","null"],"readOnly":true,"description":"Why the order failed, in plain terms. Null unless status is failed."},"completed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the order reached completed. Null before that."}}}]},"AdminEsimOrder":{"type":"object","additionalProperties":false,"description":"Staff view of an order, with the workspace and routing outcome the customer surface hides.","required":["workspace_id","supplier_id","applied_policy_id","routing_decision_id","order"],"properties":{"workspace_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/WorkspaceID"}]},"supplier_id":{"readOnly":true,"description":"The supplier the order routed to. Null while routing has not happened.","oneOf":[{"$ref":"#/components/schemas/EsimSupplierID"},{"type":"null"}]},"applied_policy_id":{"readOnly":true,"description":"The routing policy that decided the supplier, or null when the default cost ranking decided.","oneOf":[{"$ref":"#/components/schemas/EsimRoutingPolicyID"},{"type":"null"}]},"routing_decision_id":{"readOnly":true,"description":"The recorded routing decision behind the supplier, servable by the routing-decision read. Null while routing has not happened.","type":["string","null"],"format":"uuid","minLength":1},"order":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOrder"}]}}},"AdminEsimOrderPage":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEsimOrder"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AdminEsimOrderCancel":{"type":"object","additionalProperties":false,"description":"Staff resolution of a stuck order.","required":["reason"],"properties":{"reason":{"type":"string","minLength":1,"description":"Why this order is being resolved by hand, for the audit record."}}},"AdminEsimCommandStatus":{"type":"string","minLength":1,"enum":["pending","succeeded","failed","indeterminate"],"description":"Outcome of a journaled supplier command.\n\n- `pending`: issued, no outcome yet.\n- `succeeded`: the supplier confirmed the effect.\n- `failed`: the supplier definitively rejected it.\n- `indeterminate`: the outcome is unknown (timeout, ambiguous error); reconciliation resolves it against supplier state.\n"},"AdminEsimCommandKind":{"type":"string","minLength":1,"x-extensible-enum":["reserve","apply_package","remove_package","suspend","resume","release","state_read","balance_read","credentials_read"],"description":"Which port operation was issued. Open enum: new command kinds appear as the port grows."},"EsimSupplierCommandID":{"type":"string","minLength":1,"pattern":"^ecm_[0-9a-hjkmnp-tv-z]{26}$","example":"ecm_01krdgeqcxet5s7t44vh8rt9mg"},"AdminEsimCommand":{"unevaluatedProperties":false,"allOf":[{"type":"object","description":"One row of the supplier command journal, written before every supplier mutation. The support answer to \"did it fail, is it still running, or did the supplier succeed while we missed the callback\", and the work-list the reconciliation sweep drains.\n","required":["id","supplier_id","kind","status","operation_ref","supplier_reference","esim_id","order_id","resolved_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimSupplierCommandID"}]},"supplier_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimSupplierID"}]},"kind":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminEsimCommandKind"}]},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/AdminEsimCommandStatus"}]},"supplier_reference":{"type":["string","null"],"readOnly":true,"description":"The identifier the mobile network's own system assigned to this command, when one was returned. What invoice reconciliation and support escalations join on."},"operation_ref":{"type":"string","minLength":1,"readOnly":true,"description":"The Bird-minted idempotency reference sent to the supplier with the command; what a resume proves against."},"esim_id":{"readOnly":true,"description":"The eSIM the command targets, when it targets one.","oneOf":[{"$ref":"#/components/schemas/EsimID"},{"type":"null"}]},"order_id":{"readOnly":true,"description":"The order that issued the command, when one did.","oneOf":[{"$ref":"#/components/schemas/EsimOrderID"},{"type":"null"}]},"error_class":{"type":["string","null"],"readOnly":true,"x-extensible-enum":["unavailable","rate_limited","not_found","invalid_state","capability_unsupported","capacity_exhausted","rejected","indeterminate"],"description":"The port's fault classification when the command did not succeed. Null while pending or on success."},"resolved_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the outcome became known. Null while pending or indeterminate."}}},{"$ref":"#/components/schemas/Timestamps"}]},"AdminEsimCommandPage":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminEsimCommand"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"VoiceCall":{"type":"object","additionalProperties":false,"required":["id","workspace_id","direction","from","to","status","started_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallID","description":"Unique identifier for this call record."},"session_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VoiceSessionID"},{"type":"null"}],"description":"Session identifier shared across all legs of a multi-party or transferred call. Use this to correlate related call records. `null` when session correlation is not available for the call."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"direction":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallDirection"}]},"from":{"type":"string","minLength":1,"readOnly":true,"description":"Calling party number in E.164 format.","example":"+14155551234"},"to":{"type":"string","minLength":1,"readOnly":true,"description":"Called party number in E.164 format.","example":"+16505559876"},"actor":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Actor"}],"description":"Who placed the call: the API key whose credentials it used, the integration acting for the workspace, or the user who placed it from a browser or the CLI. Absent when the call was admitted only by its source IP address, or when no actor was recorded."},"sip_trunk_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/SIPTrunkID"},{"type":"null"}],"description":"Identifier of the SIP trunk that originated this call. `null` when no trunk is associated."},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallStatus"}]},"sip_response_code":{"readOnly":true,"type":["integer","null"],"minimum":100,"description":"Final SIP response code received from the carrier. `null` when no SIP response was received, for example on timeout or DNS failure.","example":200},"rejection_reason":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallRejectionReason"}],"description":"Why we rejected the call. Absent on connected calls and calls rejected\nby the carrier or recipient. For carrier or recipient rejections, see\n`sip_response_code`; a `6xx` decline gives the call a `rejected` status.\n\nRead alongside `route` when present. A refusal caused by the number's\nconfiguration has no rejection reason; the route records that\nconfiguration.\n"},"route":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallInboundRoute"}],"description":"Which answer your number gave an incoming call: a SIP trunk, a forward, or a refusal. Recorded when the call was handled, so changing the number's setup afterwards does not change what its past calls say. Absent on outbound calls, and on calls recorded before this field existed."},"tags":{"type":"array","maxItems":5,"readOnly":true,"items":{"$ref":"#/components/schemas/Tag"},"description":"Your own `{name, value}` labels for this call, taken from the `X-Bird-Call-Tag` headers on the INVITE that placed it. Set them to organise calls by a dimension of your own (campaign, queue, agent, cost centre), then filter this list by them with `tag`. Read-only here: a call is labelled when it is placed, and never afterwards. What is here may be less than what was sent, and the call still goes through either way: a tag whose name or value breaks the rules below is dropped, anything past the first five is ignored, and a name sent more than once keeps its first value. Absent when the call carried none, and on calls recorded before this field existed."},"started_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the call was initiated."},"answered_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the call was answered (`200` OK received). `null` for unanswered calls."},"ended_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the call ended (BYE or final non-2xx response). `null` for calls that ended abnormally without a recorded end event."},"duration_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Total call duration in milliseconds, measured from the first INVITE to the BYE or final response. `null` while the call is still in progress and has no final duration yet.","example":65000},"pdd_ms":{"type":"integer","minimum":0,"readOnly":true,"description":"Post-dial delay in milliseconds: how long the caller heard nothing between dialing and the phone starting to ring at the other end. High values are what callers experience as the call `not going through`. Absent when the call never rang, either because it failed first or because the carrier answered it immediately.\n","example":850},"billable_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Billable duration in milliseconds, measured from answer to call end. Zero for unanswered calls, and `null` while the call is still in progress.","example":60000},"media_quality":{"$ref":"#/components/schemas/VoiceMediaQuality","description":"How the audio sounded, as opposed to whether the call connected. Absent when the call carried no audio, or when the far end reported nothing to measure from."},"cost":{"$ref":"#/components/schemas/VoiceCallCost","description":"What the call cost, net of tax, at full precision, split into the components that make it up. Absent until the call has been rated; unanswered or unpriced calls have no cost."}}},"VoiceCallList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceCall"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"VoiceSequencePhoneNumber":{"type":"string","minLength":5,"maxLength":16,"pattern":"^\\+[1-9][0-9]{3,14}$","description":"Canonical E.164 phone number, with a leading plus sign and four to fifteen digits."},"VoiceSequenceReference":{"type":"object","additionalProperties":false,"required":["slug"],"properties":{"slug":{"$ref":"#/components/schemas/VoiceSequenceSlug"}}},"VoiceSequencePlay":{"type":"object","additionalProperties":false,"required":["type","asset_id"],"properties":{"type":{"type":"string","minLength":1,"const":"play"},"asset_id":{"$ref":"#/components/schemas/AssetID"}}},"VoiceSequenceSay":{"type":"object","additionalProperties":false,"required":["type","text","language"],"properties":{"type":{"type":"string","minLength":1,"const":"say"},"text":{"type":"string","minLength":1,"maxLength":160,"description":"Plain speech text, limited by the service to 160 UTF-8 bytes. maxLength also bounds characters; multibyte text can reach the byte limit sooner. Exceeding the byte limit returns 422."},"language":{"type":"string","minLength":1}}},"VoiceSequenceTone":{"type":"object","additionalProperties":false,"required":["type","frequency_hz","duration_ms"],"properties":{"type":{"type":"string","minLength":1,"const":"tone"},"frequency_hz":{"type":"integer","minimum":100,"maximum":3000},"duration_ms":{"type":"integer","minimum":1,"maximum":10000}}},"VoiceSequencePause":{"type":"object","additionalProperties":false,"required":["type","duration_ms"],"properties":{"type":{"type":"string","minLength":1,"const":"pause"},"duration_ms":{"type":"integer","minimum":1,"maximum":60000}}},"VoiceSequencePromptCommand":{"oneOf":[{"$ref":"#/components/schemas/VoiceSequencePlay"},{"$ref":"#/components/schemas/VoiceSequenceSay"},{"$ref":"#/components/schemas/VoiceSequenceTone"},{"$ref":"#/components/schemas/VoiceSequencePause"}],"discriminator":{"propertyName":"type","mapping":{"play":"#/components/schemas/VoiceSequencePlay","say":"#/components/schemas/VoiceSequenceSay","tone":"#/components/schemas/VoiceSequenceTone","pause":"#/components/schemas/VoiceSequencePause"}}},"VoiceSequenceSlugNext":{"type":["object","null"],"additionalProperties":false,"required":["type","sequence"],"properties":{"type":{"type":"string","minLength":1,"const":"sequence"},"sequence":{"$ref":"#/components/schemas/VoiceSequenceSlug"}}},"VoiceSequenceWebhookNext":{"type":"object","additionalProperties":false,"required":["type","url"],"properties":{"type":{"type":"string","minLength":1,"const":"webhook"},"url":{"type":"string","format":"uri","minLength":1,"maxLength":256,"description":"Where we ask what to do next, over HTTPS on port 443 and at most 256 bytes. The request is unsigned. A `url` the response returns must share this origin, so the path may differ and the origin may not.\n","example":"https://voice.example.com/next"}}},"VoiceSequenceNext":{"type":["object","null"],"oneOf":[{"$ref":"#/components/schemas/VoiceSequenceSlugNext"},{"$ref":"#/components/schemas/VoiceSequenceWebhookNext"}]},"VoiceSequenceGather":{"type":"object","additionalProperties":false,"required":["type","input","prompts","max_digits","timeout_seconds","inter_digit_timeout_seconds","finish_on_key"],"anyOf":[{"required":["on_digits"]},{"required":["on_any"]}],"properties":{"type":{"type":"string","minLength":1,"const":"gather"},"input":{"type":"string","minLength":1,"const":"dtmf"},"prompts":{"description":"Up to four prompt commands. The combined duration_ms of tone and pause prompts must not exceed 60,000 milliseconds; the server rejects a larger sum with 422. This aggregate limit is separate from each command limit.","type":"array","maxItems":4,"items":{"$ref":"#/components/schemas/VoiceSequencePromptCommand"}},"max_digits":{"type":"integer","minimum":1,"maximum":32},"timeout_seconds":{"type":"integer","minimum":1,"maximum":10},"inter_digit_timeout_seconds":{"type":"integer","minimum":1,"maximum":5},"finish_on_key":{"type":["string","null"],"minLength":1,"pattern":"^[0-9*#ABCD]$"},"on_digits":{"type":"object","propertyNames":{"pattern":"^[0-9*#ABCD]+$"},"additionalProperties":{"$ref":"#/components/schemas/VoiceSequenceNext"}},"on_any":{"$ref":"#/components/schemas/VoiceSequenceNext"},"on_timeout":{"$ref":"#/components/schemas/VoiceSequenceNext"},"private":{"type":"boolean","default":false}}},"VoiceSequenceRecordStart":{"type":"object","additionalProperties":false,"required":["type","max_seconds"],"properties":{"type":{"type":"string","minLength":1,"const":"record_start"},"max_seconds":{"type":"integer","minimum":1,"maximum":120},"transcribe":{"type":"boolean","default":false}}},"VoiceSequenceRecordStop":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","minLength":1,"const":"record_stop"}}},"VoiceSequenceDial":{"type":"object","additionalProperties":false,"required":["type","to","timeout_seconds"],"description":"Calls one phone number and connects it to the current call. The next command\nruns after the destination call ends and the current call's media is restored.\nIf recording is active when dialing starts, both participants are recorded\nas separate streams throughout the bridge.\n","properties":{"type":{"type":"string","minLength":1,"const":"dial"},"to":{"$ref":"#/components/schemas/VoiceSequencePhoneNumber","description":"Destination phone number in E.164 format, including the leading plus sign."},"timeout_seconds":{"type":"integer","minimum":1,"maximum":120,"description":"Maximum time to wait for the destination to answer."}}},"VoiceSequenceHangup":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","minLength":1,"const":"hangup"}}},"VoiceSequenceCommand":{"oneOf":[{"$ref":"#/components/schemas/VoiceSequencePlay"},{"$ref":"#/components/schemas/VoiceSequenceSay"},{"$ref":"#/components/schemas/VoiceSequenceTone"},{"$ref":"#/components/schemas/VoiceSequencePause"},{"$ref":"#/components/schemas/VoiceSequenceGather"},{"$ref":"#/components/schemas/VoiceSequenceRecordStart"},{"$ref":"#/components/schemas/VoiceSequenceRecordStop"},{"$ref":"#/components/schemas/VoiceSequenceDial"},{"$ref":"#/components/schemas/VoiceSequenceHangup"}],"discriminator":{"propertyName":"type","mapping":{"play":"#/components/schemas/VoiceSequencePlay","say":"#/components/schemas/VoiceSequenceSay","tone":"#/components/schemas/VoiceSequenceTone","pause":"#/components/schemas/VoiceSequencePause","gather":"#/components/schemas/VoiceSequenceGather","record_start":"#/components/schemas/VoiceSequenceRecordStart","record_stop":"#/components/schemas/VoiceSequenceRecordStop","dial":"#/components/schemas/VoiceSequenceDial","hangup":"#/components/schemas/VoiceSequenceHangup"}}},"VoiceSequenceInline":{"type":"object","additionalProperties":false,"description":"The sequence to execute first. Subsequent sequence references use the selected published version. The server enforces a total of 16 commands including nested gather prompts. Gather and hangup must be last; hangup requires next to be null.","required":["schema_version","commands","next"],"properties":{"schema_version":{"type":"integer","minimum":2,"maximum":2},"commands":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/VoiceSequenceCommand"}},"next":{"$ref":"#/components/schemas/VoiceSequenceNext"}},"if":{"properties":{"commands":{"contains":{"properties":{"type":{"const":"hangup"}},"required":["type"]}}},"required":["commands"]},"then":{"properties":{"next":{"type":"null"}}}},"VoiceCallSequenceSource":{"description":"A slug in the selected published version, or the inline sequence to execute first.","oneOf":[{"$ref":"#/components/schemas/VoiceSequenceReference"},{"$ref":"#/components/schemas/VoiceSequenceInline"}]},"VoiceSequenceVersionID":{"type":"string","minLength":1,"pattern":"^vsv_[0-9a-hjkmnp-tv-z]{26}$","example":"vsv_01krdgeqcxet5s7t44vh8rt9mg"},"VoiceCreateCallRequest":{"type":"object","additionalProperties":false,"required":["from","to","sequence"],"properties":{"from":{"$ref":"#/components/schemas/VoiceSequencePhoneNumber","description":"Calling number that this workspace is permitted to present."},"to":{"$ref":"#/components/schemas/VoiceSequencePhoneNumber","description":"Recipient number in canonical E.164 form."},"sequence":{"$ref":"#/components/schemas/VoiceCallSequenceSource"},"sequence_version_id":{"$ref":"#/components/schemas/VoiceSequenceVersionID","description":"Published version to use. Omission selects the workspace's current published version at acceptance."},"ringing_timeout_seconds":{"type":"integer","minimum":5,"maximum":120,"default":30,"description":"Maximum ringing time for the original dialing attempt, shared across routing candidates."}}},"VoiceCallReceipt":{"type":"object","readOnly":true,"additionalProperties":false,"required":["id","session_id"],"properties":{"id":{"$ref":"#/components/schemas/VoiceCallID"},"session_id":{"$ref":"#/components/schemas/VoiceSessionID","description":"Reserved session identity. The session becomes readable after call registration."}}},"VoiceCallMediaID":{"type":"string","minLength":1,"pattern":"^vcm_[0-9a-hjkmnp-tv-z]{26}$","example":"vcm_01krdgeqcxet5s7t44vh8rt9mg"},"VoiceCallMediaState":{"type":"string","enum":["open","available","failed","purged"],"description":"What became of this stored audio.\n\n`open` while capture is still running. `available` once the bytes are\nstored. `failed` when capture did not produce any. `purged` when retention\nhas reclaimed the bytes, which leaves the record behind so the history still\nshows that a recording was made.\n","example":"available"},"VoicePartyEndpointType":{"type":"string","x-extensible-enum":["pstn","sip","voicemail","bridge_pstn","bridge_sip","webhook","sequence"],"description":"The technical participant observed on one side of a call. Additional endpoint types may appear in retained observations.","example":"pstn"},"VoiceParty":{"type":"object","additionalProperties":false,"properties":{"endpoint_type":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VoicePartyEndpointType"},{"type":"null"}],"description":"What kind of participant sat on this side of a leg: a telephone endpoint off the platform, a SIP or WebRTC endpoint, Bird answering, or the platform placing a leg onward. It does not name a person.\n`null` on an observation this API could not read. The entry stays, because the session counted it when it deduplicated, and dropping it here would report fewer participants than were observed.","example":"pstn"},"address":{"readOnly":true,"type":["string","null"],"description":"This side's own address, in E.164 or as a `sip:` URI. `null` when the observation carried none, which does not say whether one was withheld, missing, or nonexistent.","example":"+14155551234"},"trunk_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/SIPTrunkID"},{"type":"null"}],"description":"The workspace trunk on this side of the leg. `null` when this side sat behind no trunk."},"forwarded_to":{"readOnly":true,"type":["string","null"],"description":"Where the platform placed the leg onward, when this side is the number a\ncall arrived on and the workspace had it forward. `address` is then the\nnumber that was dialled and this is the number it was forwarded to, so\nthe two together are one hop of the call.\n\n`null` on every side that forwarded nowhere, which is most of them.\n","example":"+31612345678"},"forwarded_as":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VoiceInboundForwardAs"},{"type":"null"}],"description":"Which number the forwarded leg presented to the far end: the caller's own (`calling_number`) or the number they dialled (`dialed_number`). `null` when this side forwarded nowhere.","example":"calling_number"},"forwarded_to_trunk_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/SIPTrunkID"},{"type":"null"}],"description":"The workspace trunk the leg was delivered onward to, when the forward went to a trunk rather than to a number. Distinct from `trunk_id`, which is the trunk this side itself sat behind. `null` when the forward went to a number, or nowhere."}}},"VoiceCallMedia":{"type":"object","additionalProperties":false,"required":["id","call_id","codec","state","started_at","ended_at","offset_ms","audio_ms"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallMediaID","description":"Unique identifier for this stream."},"call_id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallID","description":"The leg this stream was captured from."},"session_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VoiceSessionID"},{"type":"null"}],"description":"`null` on a stream captured before sessions existed, which only its call addresses."},"codec":{"readOnly":true,"type":"string","minLength":1,"description":"Codec of the stored payload.","example":"PCMU"},"state":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallMediaState"}]},"offset_ms":{"readOnly":true,"type":"integer","description":"Length of the announcement that played before capture started. Zero when nothing was announced. It is not a position on a session clock."},"audio_ms":{"readOnly":true,"type":"integer","description":"Length of the payload itself. This runs shorter than `ended_at - started_at` by the silence before the first packet, so use it for anything about the sound and the window for anything about the call."},"source_party":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VoiceParty"},{"type":"null"}],"description":"The party this audio was captured from, which is what says whose voice it\ncarries. `channel` only says which direction of the leg the stream came\nfrom, and `number` is the number the leg was reached on rather than the\ncaller.\n\n`null` on a stream stored before the platform recorded a party for it.\n"},"started_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time","description":"When capture started, which is after any announcement rather than at the answer."},"ended_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time","description":"When capture ended."}}},"VoiceCallRecordingID":{"type":"string","minLength":1,"pattern":"^vrc_[0-9a-hjkmnp-tv-z]{26}$","example":"vrc_01krdgeqcxet5s7t44vh8rt9mg"},"VoiceCallChannel":{"type":"string","enum":["rx","tx"],"description":"Which direction of the leg the stream carries: `rx` for what the platform received, `tx` for what it sent.","example":"rx"},"VoiceCallRecording":{"type":"object","additionalProperties":false,"required":["id","call_id","number","channel","media_id","media_started_at","started_at","ended_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallRecordingID","description":"Unique identifier for this view."},"call_id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallID","description":"The leg this view belongs to."},"session_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VoiceSessionID"},{"type":"null"}],"description":"`null` on a recording captured before sessions existed."},"number":{"readOnly":true,"type":"string","minLength":1,"description":"The number this leg was reached on. It is not the caller."},"channel":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallChannel"}],"description":"It does not say whose voice it is."},"media_id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallMediaID","description":"The stream this view refers to. A stream captured on another leg or omitted by retention or truncation may be absent from `media` in this response."},"media_started_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time","description":"When the referenced stream started, which a view captured from the far leg does not share with its own `started_at`."},"started_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time"},"ended_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time"}}},"VoiceCallTranscriptID":{"type":"string","minLength":1,"pattern":"^vct_[0-9a-hjkmnp-tv-z]{26}$","example":"vct_01krdgeqcxet5s7t44vh8rt9mg"},"VoiceCallTranscriptState":{"type":"string","enum":["available","failed"],"description":"Whether the attempt produced a transcript. `failed` is an outcome, not evidence that a later attempt cannot succeed.","example":"available"},"VoiceCallTranscriptSource":{"type":"string","x-extensible-enum":["whole","aligned","live"],"description":"What the times in `cues` are worth: `whole` for a pass that times nothing, `aligned` for offline forced alignment, `live` for a streaming producer.","example":"whole"},"VoiceCallTranscriptCue":{"type":"object","additionalProperties":false,"required":["seq","text"],"properties":{"seq":{"readOnly":true,"type":"integer","description":"Position of this cue in the transcript."},"start_ms":{"readOnly":true,"type":["integer","null"],"description":"Offset of this cue from the start of its stream. `null` unless the producer measured it. The current pass measures nothing, so a zero would be a claim about timing that was never made."},"end_ms":{"readOnly":true,"type":["integer","null"],"description":"Offset of the end of this cue from the start of its stream. `null` on the same terms as `start_ms`."},"text":{"readOnly":true,"type":"string","minLength":1,"description":"What was transcribed for this cue."}}},"VoiceCallTranscript":{"type":"object","additionalProperties":false,"required":["id","call_id","media_id","media_started_at","state","source","channel","cues","created_at"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallTranscriptID","description":"Unique identifier for this attempt."},"call_id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallID","description":"The leg the transcribed stream was captured from."},"session_id":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VoiceSessionID"},{"type":"null"}],"description":"`null` on a transcript of a stream captured before sessions existed."},"media_id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallMediaID","description":"The stream this attempt describes."},"media_started_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time","description":"When that stream started. A transcript is retained with the stream it describes rather than on its own transcription lag."},"state":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallTranscriptState"}]},"source":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallTranscriptSource"}]},"model":{"readOnly":true,"type":"string","description":"The model that produced it."},"channel":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallChannel"}],"description":"The direction of the transcribed stream."},"cues":{"readOnly":true,"type":"array","description":"The transcript, in order. Concatenating cues across streams reads as prose nobody spoke, and a reader matching over it can match the wrong party's words.","items":{"$ref":"#/components/schemas/VoiceCallTranscriptCue"}},"source_party":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/VoiceParty"},{"type":"null"}],"description":"The party this audio was captured from, which is what says whose voice it\ncarries. `channel` only says which direction of the leg the stream came\nfrom, and `number` is the number the leg was reached on rather than the\ncaller.\n\n`null` on a stream stored before the platform recorded a party for it.\n"},"created_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time"}}},"VoiceCallRecordings":{"type":"object","additionalProperties":false,"required":["media","recordings","transcripts","truncated","evaluated_at"],"properties":{"media":{"readOnly":true,"type":"array","description":"The call's stored audio.","items":{"$ref":"#/components/schemas/VoiceCallMedia"}},"recordings":{"readOnly":true,"type":"array","description":"Each leg's view of that audio.","items":{"$ref":"#/components/schemas/VoiceCallRecording"}},"transcripts":{"readOnly":true,"type":"array","description":"Every transcription attempt over that audio, including failed ones.","items":{"$ref":"#/components/schemas/VoiceCallTranscript"}},"truncated":{"readOnly":true,"type":"boolean","description":"Whether a collection above was cut at this read's per-collection limit of\n500. When `true`, the call holds more than is shown here and this response\nis not the whole set.\n\n`false` means no collection hit that limit, and a collection that could\nnot be read in full fails the request rather than returning a short\nanswer. What `false` does not establish is that every recording the call\nproduced has arrived: delivery into this store is asynchronous, so a\nrecording made moments ago can be absent from a complete read.\n"},"evaluated_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time","description":"The instant each collection's retention was decided at. Nothing here stays true because you are still holding the response. A recording can expire after it."}}},"VoiceRecordingDisposition":{"type":"string","enum":["inline","attachment"],"default":"inline","description":"Whether a signed audio URL plays the recording or saves it. The signature carries this, so it cannot be changed after the URL is minted.\n","example":"inline"},"VoiceCallTraceOutcome":{"type":"string","minLength":1,"enum":["completed","failed","cancelled","incomplete"]},"VoiceCallTraceEndReason":{"type":"string","minLength":1,"enum":["sequence_ended","hangup","participant_hangup","deadline","error","cancelled"]},"VoiceCallTraceCallData":{"type":"object","additionalProperties":false,"required":["type","direction","answered_at","media_ready_at","end_reason"],"properties":{"type":{"type":"string","minLength":1,"const":"call"},"direction":{"$ref":"#/components/schemas/VoiceCallDirection"},"answered_at":{"type":["string","null"],"minLength":1,"format":"date-time"},"media_ready_at":{"type":["string","null"],"minLength":1,"format":"date-time"},"end_reason":{"$ref":"#/components/schemas/VoiceCallTraceEndReason"}}},"VoiceSequenceBody":{"type":"object","description":"Ordered call commands and the continuation after normal completion. The server enforces a total of 16 commands including nested gather prompts. Gather and hangup must be the final command; hangup requires next to be null. Violations return 422. Array length alone does not express these combined command and ordering constraints.","additionalProperties":false,"required":["commands","next"],"properties":{"commands":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/VoiceSequenceCommand"}},"next":{"$ref":"#/components/schemas/VoiceSequenceNext"}},"if":{"properties":{"commands":{"contains":{"properties":{"type":{"const":"hangup"}},"required":["type"]}}},"required":["commands"]},"then":{"properties":{"next":{"type":"null"}}}},"VoiceCallTraceContinuation":{"type":["object","null"],"additionalProperties":false,"required":["next"],"properties":{"next":{"$ref":"#/components/schemas/VoiceSequenceNext"}},"description":"Null when no continuation was selected; next is null when termination was selected."},"VoiceCallTraceSequenceData":{"type":"object","additionalProperties":false,"required":["type","version_id","sequence","body_digest","body","continuation"],"properties":{"type":{"type":"string","minLength":1,"const":"sequence"},"version_id":{"description":"Bound customer version, or null for a standalone builtin sequence.","oneOf":[{"$ref":"#/components/schemas/VoiceSequenceVersionID"},{"type":"null"}]},"sequence":{"oneOf":[{"$ref":"#/components/schemas/VoiceSequenceSlug"},{"type":"null"}]},"body_digest":{"type":"string","minLength":1,"pattern":"^[0-9a-f]{64}$"},"body":{"$ref":"#/components/schemas/VoiceSequenceBody"},"continuation":{"$ref":"#/components/schemas/VoiceCallTraceContinuation"}}},"VoiceCallTraceGatherCompletion":{"type":"string","minLength":1,"enum":["initial_timeout","max_digits","finish_key","inter_digit_timeout"]},"VoiceCallTraceGatherResult":{"type":"object","additionalProperties":false,"required":["type","digits","reason"],"properties":{"type":{"type":"string","minLength":1,"const":"gather"},"digits":{"type":"string","minLength":0,"maxLength":32,"pattern":"^[0-9*#ABCD]*$"},"reason":{"$ref":"#/components/schemas/VoiceCallTraceGatherCompletion"}}},"VoiceCallTraceRecordStartResult":{"type":"object","additionalProperties":false,"required":["type","recording_id","media_id"],"properties":{"type":{"type":"string","minLength":1,"const":"record_start"},"recording_id":{"$ref":"#/components/schemas/VoiceCallRecordingID"},"media_id":{"$ref":"#/components/schemas/VoiceCallMediaID"}}},"VoiceDialOutcome":{"type":"string","minLength":1,"enum":["completed","busy","no_answer","failed"],"description":"Result of a dial command after cleanup leaves the current call usable.\n\n- `completed`: The destination answered, the media connected, and the destination call ended.\n- `busy`: The destination refused the call as busy.\n- `no_answer`: The destination did not answer within the dialing timeout or was unavailable.\n- `failed`: Setup failed before the bridge connected.\n"},"VoiceCallTraceDialResult":{"type":"object","additionalProperties":false,"required":["type","call_id","outcome"],"properties":{"type":{"type":"string","minLength":1,"const":"dial"},"call_id":{"$ref":"#/components/schemas/VoiceCallID","description":"Identity allocated to the outbound attempt. A refused attempt may have no call record."},"outcome":{"$ref":"#/components/schemas/VoiceDialOutcome"}}},"VoiceCallTraceCommandData":{"type":"object","additionalProperties":false,"required":["type","command"],"if":{"properties":{"command":{"properties":{"type":{"const":"gather"}}}}},"then":{"properties":{"result":{"$ref":"#/components/schemas/VoiceCallTraceGatherResult"}}},"else":{"if":{"properties":{"command":{"properties":{"type":{"const":"record_start"}}}}},"then":{"properties":{"result":{"$ref":"#/components/schemas/VoiceCallTraceRecordStartResult"}}},"else":{"if":{"properties":{"command":{"properties":{"type":{"const":"dial"}}}}},"then":{"properties":{"result":{"$ref":"#/components/schemas/VoiceCallTraceDialResult"}}},"else":{"properties":{"result":{"not":{}}}}}},"properties":{"type":{"type":"string","minLength":1,"const":"command"},"command":{"$ref":"#/components/schemas/VoiceSequenceCommand"},"path":{"type":"string","minLength":1,"description":"Authored positional path within the sequence body."},"result":{"oneOf":[{"$ref":"#/components/schemas/VoiceCallTraceGatherResult"},{"$ref":"#/components/schemas/VoiceCallTraceRecordStartResult"},{"$ref":"#/components/schemas/VoiceCallTraceDialResult"}],"discriminator":{"propertyName":"type","mapping":{"gather":"#/components/schemas/VoiceCallTraceGatherResult","record_start":"#/components/schemas/VoiceCallTraceRecordStartResult","dial":"#/components/schemas/VoiceCallTraceDialResult"}},"description":"Observed result; omitted for private gather commands."}}},"VoiceCallTraceRecordingStopReason":{"type":["string","null"],"minLength":1,"enum":["command","cap","call_ended","error",null]},"VoiceCallTraceRecordingData":{"type":"object","additionalProperties":false,"required":["type","recording_id","media_id","stop_reason"],"properties":{"type":{"type":"string","minLength":1,"const":"recording"},"recording_id":{"$ref":"#/components/schemas/VoiceCallRecordingID"},"media_id":{"$ref":"#/components/schemas/VoiceCallMediaID"},"stop_reason":{"$ref":"#/components/schemas/VoiceCallTraceRecordingStopReason"}}},"VoiceCallTraceErrorCode":{"type":"string","minLength":1,"enum":["sequence_not_found","invalid_sequence","unsupported_capability","input_lost","preparation_failed","execution_failed","cleanup_failed","call_limit","history_limit","webhook_unavailable","webhook_invalid_response"],"description":"webhook_unavailable means the sequence webhook request could not be completed; webhook_invalid_response means the webhook response failed validation; sequence_not_found means the target sequence was absent from the bound version; invalid_sequence means its command definition was invalid; unsupported_capability means a required call capability was unavailable; input_lost means input accounting was lost and no partial collection was returned; preparation_failed means media could not be prepared; execution_failed means an operation failed; cleanup_failed means cleanup could not be confirmed; call_limit means the execution or duration limit was reached; history_limit means storage limits prevented retaining later events."},"VoiceCallTraceErrorData":{"type":"object","additionalProperties":false,"required":["type","code","message","ends_call"],"properties":{"type":{"type":"string","minLength":1,"const":"error"},"code":{"$ref":"#/components/schemas/VoiceCallTraceErrorCode"},"message":{"type":"string","minLength":1,"description":"Bounded safe diagnostic text without provider messages or observed private input."},"field_path":{"type":"string","minLength":1},"ends_call":{"type":"boolean"}}},"VoiceCallTraceWebhookData":{"type":"object","additionalProperties":false,"required":["type","url"],"properties":{"type":{"type":"string","minLength":1,"const":"webhook"},"url":{"type":"string","format":"uri","minLength":1,"maxLength":256,"description":"The URL actually requested. The span start and end record the request duration.","example":"https://voice.example.com/next"}}},"VoiceCallTraceData":{"oneOf":[{"$ref":"#/components/schemas/VoiceCallTraceCallData"},{"$ref":"#/components/schemas/VoiceCallTraceSequenceData"},{"$ref":"#/components/schemas/VoiceCallTraceCommandData"},{"$ref":"#/components/schemas/VoiceCallTraceRecordingData"},{"$ref":"#/components/schemas/VoiceCallTraceErrorData"},{"$ref":"#/components/schemas/VoiceCallTraceWebhookData"}],"discriminator":{"propertyName":"type","mapping":{"call":"#/components/schemas/VoiceCallTraceCallData","sequence":"#/components/schemas/VoiceCallTraceSequenceData","command":"#/components/schemas/VoiceCallTraceCommandData","recording":"#/components/schemas/VoiceCallTraceRecordingData","error":"#/components/schemas/VoiceCallTraceErrorData","webhook":"#/components/schemas/VoiceCallTraceWebhookData"}}},"VoiceCallTraceSpan":{"type":"object","additionalProperties":false,"required":["id","parent_id","started_at","ended_at","interrupted_at","outcome","data"],"properties":{"id":{"type":"string","minLength":1,"format":"uuid"},"parent_id":{"type":["string","null"],"minLength":1,"format":"uuid"},"started_at":{"type":"string","minLength":1,"format":"date-time"},"ended_at":{"type":["string","null"],"minLength":1,"format":"date-time","description":"Observed end time, or null when the end is unknown."},"interrupted_at":{"type":["string","null"],"minLength":1,"format":"date-time"},"outcome":{"$ref":"#/components/schemas/VoiceCallTraceOutcome"},"data":{"$ref":"#/components/schemas/VoiceCallTraceData"}}},"VoiceCallTrace":{"type":"object","additionalProperties":false,"required":["workspace_id","session_id","call_id","spans","truncated"],"properties":{"workspace_id":{"$ref":"#/components/schemas/WorkspaceID"},"session_id":{"$ref":"#/components/schemas/VoiceSessionID"},"call_id":{"$ref":"#/components/schemas/VoiceCallID"},"spans":{"type":"array","minItems":1,"maxItems":2048,"items":{"$ref":"#/components/schemas/VoiceCallTraceSpan"}},"truncated":{"type":"boolean","description":"Some observed events could not be retained within the history limits."}}},"VoiceSession":{"type":"object","additionalProperties":false,"required":["id","workspace_id","initial_call_id","direction","started_at","live","has_recording","has_transcript","parties"],"properties":{"id":{"readOnly":true,"$ref":"#/components/schemas/VoiceSessionID","description":"Unique identifier for this session, shared by every call that belongs to it."},"workspace_id":{"readOnly":true,"$ref":"#/components/schemas/WorkspaceID"},"initial_call_id":{"readOnly":true,"$ref":"#/components/schemas/VoiceCallID","description":"The call that started the session. This is the leg that established it, not whichever leg happens to be listed first."},"direction":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceCallDirection"}],"description":"Direction of the initial call."},"started_at":{"readOnly":true,"type":"string","minLength":1,"format":"date-time","description":"When the initial call started."},"ended_at":{"readOnly":true,"type":["string","null"],"format":"date-time","description":"When the session's last call ended. `null` while any call is still in progress. Recordings and transcripts can still arrive after this instant, so it does not mean the session is finished being written."},"live":{"readOnly":true,"type":"boolean","description":"Whether any call in the session currently holds a lease. `false` covers the interval between a call ending and its settlement being confirmed, and says nothing about whether transcription has finished."},"has_recording":{"readOnly":true,"type":"boolean","description":"Whether the session ever produced a recording. It stays `true` for the life of the session, so it records that a recording was made rather than promising one can still be fetched."},"has_transcript":{"readOnly":true,"type":"boolean","description":"Whether the session ever produced a transcript. A failed transcription attempt does not set it, and a later failure does not clear it."},"parties":{"readOnly":true,"type":"array","description":"The distinct participant observations the session's calls recorded, for display beside the session. The length is not a count of people and not a reconstruction of the call graph.","items":{"$ref":"#/components/schemas/VoiceParty"}}}},"VoiceSessionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceSession"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"VoiceDirectionMeasures":{"type":"object","additionalProperties":false,"readOnly":true,"description":"One direction's half of the row's calls. Present on every row that carries call-quality statistics, so a bucket, a destination and the period summary all split the same way. Every leg is either inbound or outbound, so the two halves sum to the row's `total_calls`.","required":["total_calls","answered_calls","asr"],"properties":{"total_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Call legs in this direction, within the containing row's scope.","example":1204},"answered_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Call legs in this direction that were answered.","example":913},"asr":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Answer-seizure ratio for this direction alone, between 0 and 1. `null` when this direction had no calls in scope.","example":0.7583}}},"VoiceDirectionSplit":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Both halves of a row's calls, so one dimension can be read against a fixed inbound/outbound block. `direction` has exactly these two values, so the two `total_calls` sum to the containing row's own with nothing left over.","required":["outbound","inbound"],"properties":{"outbound":{"readOnly":true,"description":"Legs your PBX originated.","allOf":[{"$ref":"#/components/schemas/VoiceDirectionMeasures"}],"example":{"total_calls":1204,"answered_calls":913,"asr":0.7583}},"inbound":{"readOnly":true,"description":"Legs that arrived from a remote party.","allOf":[{"$ref":"#/components/schemas/VoiceDirectionMeasures"}],"example":{"total_calls":216,"answered_calls":190,"asr":0.8796}}}},"VoiceStatusMeasures":{"type":"object","additionalProperties":false,"readOnly":true,"description":"How the row's calls ended, as a count per outcome rather than a separate breakdown operation. Every field is a count of the same call legs `total_calls` counts, and the six always sum to it. `other` is what keeps that true when a call ends in a state this vocabulary does not name yet, rather than the call disappearing from the split. `answered` repeats the row's own `answered_calls` so that the six reconcile on their own.","required":["answered","no_answer","failed","rejected","unknown","other"],"properties":{"answered":{"type":"integer","minimum":0,"readOnly":true,"description":"Legs that were answered. Keyed on the call having an answer time, not on the response code it ended with.","example":1103},"no_answer":{"type":"integer","minimum":0,"readOnly":true,"description":"Unanswered legs that ended in a `408` timeout. The response code alone assigns this, so it is not evidence the phone rang. A leg that timed out before any ringing is counted here too.","example":168},"failed":{"type":"integer","minimum":0,"readOnly":true,"description":"Legs refused with a 4xx or 5xx response other than the `408` counted as `no_answer`.","example":94},"rejected":{"type":"integer","minimum":0,"readOnly":true,"description":"Legs refused with a 6xx response, plus calls Bird itself refused before reaching a carrier. A platform refusal is recorded here whatever response code it carried, defaulting to `503` when it carried none, so this bucket is not identified by its response code.","example":21},"unknown":{"type":"integer","minimum":0,"readOnly":true,"description":"Legs whose outcome could not be determined, the same state `unknown` names on a call record. It covers unanswered legs that drew no final response, and unanswered legs whose response falls outside the ranges the counts above cover.","example":34},"other":{"type":"integer","minimum":0,"readOnly":true,"description":"Legs whose outcome is none of the above. Zero today. It exists so the six counts always add up to `total_calls`, which means a row's split can be reconciled against its own total without any outcome going unaccounted for.","example":0}}},"VoiceCallQualityStats":{"type":"object","description":"Call-quality counts and derived rates for the scope of the containing row (the whole period, a time bucket, or a breakdown dimension). One vocabulary everywhere: `total_calls` always means the call legs in that row's scope, so a bucket, a destination, and the period summary all name the same metric the same way. Rates are `null` when their denominator is zero.\n","required":["total_calls","answered_calls","asr","acd_ms","duration_ms_min","duration_ms_max","duration_ms_p50","duration_ms_p90","duration_ms_p95","short_calls","short_call_rate","distinct_external_numbers","pdd_ms_p50","pdd_ms_p95","mos_avg","by_direction","by_status"],"properties":{"total_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of call legs in scope. This is the denominator for `asr`.","example":1420},"answered_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of call legs in scope that were answered.","example":1103},"asr":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Answer-seizure ratio, meaning answered calls divided by total calls, between 0 and 1. `null` when no calls were placed in scope.","example":0.7768},"acd_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Average duration of answered calls in scope, in milliseconds. `null` when no calls in scope were answered.","example":183000},"duration_ms_min":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Shortest answered call in scope, in milliseconds. `null` when no calls in scope were answered.","example":4000},"duration_ms_max":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Longest answered call in scope, in milliseconds. `null` when no calls in scope were answered.","example":1840000},"duration_ms_p50":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Median answered-call duration in scope, in milliseconds. Read it alongside `acd_ms`, which a handful of very long calls can pull well above the median. `null` when no calls in scope were answered.","example":141000},"duration_ms_p90":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Duration in milliseconds that 90 percent of answered calls in scope came in under. `null` when no calls in scope were answered.","example":402000},"duration_ms_p95":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Duration in milliseconds that 95 percent of answered calls in scope came in under. `null` when no calls in scope were answered.","example":618000},"short_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Answered calls in scope that lasted less than `short_call_threshold_ms`. Read against `answered_calls` this is the share of connections too brief to have been useful, which on outbound traffic is the usual sign of a route that connects but does not carry.","example":96},"short_call_rate":{"type":["number","null"],"minimum":0,"maximum":1,"readOnly":true,"description":"Short calls as a fraction of answered calls, between 0 and 1. `null` when nothing in scope was answered.","example":0.045},"distinct_external_numbers":{"type":"integer","minimum":0,"readOnly":true,"description":"How many distinct numbers on the far end of the call the row covers, counting the destination on an outbound leg and the caller on an inbound one. Read against `total_calls` this separates traffic spread across many parties from traffic concentrated on a few.","example":812},"pdd_ms_p50":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Median post-dial delay in milliseconds, meaning how long the caller waited before hearing ringing. Covers only the legs where it was measured, and is `null` when none were.","example":1200},"pdd_ms_p95":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"95th-percentile post-dial delay in milliseconds, over the legs where it was measured. `null` when none were.","example":3400},"mos_avg":{"type":["number","null"],"minimum":1,"maximum":5,"readOnly":true,"description":"Average mean opinion score over the legs where audio quality was measured, on the same 1 to 5 scale a single call reports. `null` when none in scope were measured, which is not the same as a poor score.","example":4.21},"by_direction":{"readOnly":true,"description":"The row's calls split into outbound and inbound. `direction` stays a filter as well; unfiltered, this is how a row shows both halves at once instead of blending them.","allOf":[{"$ref":"#/components/schemas/VoiceDirectionSplit"}]},"by_status":{"readOnly":true,"description":"The row's calls counted by how they ended.","allOf":[{"$ref":"#/components/schemas/VoiceStatusMeasures"}],"example":{"answered":1103,"no_answer":168,"failed":94,"rejected":21,"unknown":34,"other":0}}}},"VoiceDurationHistogramBucket":{"type":"object","additionalProperties":false,"description":"Answered calls in a fixed billable-duration interval.","required":["lower_bound_ms","upper_bound_ms","calls"],"properties":{"lower_bound_ms":{"type":"integer","minimum":0,"readOnly":true,"description":"Inclusive lower bound, in milliseconds.","example":0},"upper_bound_ms":{"type":["integer","null"],"minimum":0,"readOnly":true,"description":"Exclusive upper bound, or null for the final unbounded interval.","example":30000},"calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Answered call legs in this interval.","example":12}}},"VoiceStatsDataAsOf":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"The approximate time through which this response includes completed calls. Active and more recent calls may not be reflected. Use the live call list for current activity. `null` when the freshness boundary is unavailable.","example":"2026-07-21T09:15:00Z"},"VoiceStatsSummaryPeriod":{"type":"object","additionalProperties":false,"description":"The window the server actually computed against. The summary serves two window grains: calendar days (bounds are `YYYY-MM-DD`) and hours (bounds are RFC 3339 instants on the hour). The grain of `from` and `to` mirrors the grain of the request's bounds.\n","required":["from","to"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window the response covers, as a calendar day (`YYYY-MM-DD`) for day windows or an hour boundary (RFC 3339) for hour windows.","example":"2026-07-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window the response covers, as a calendar day (`YYYY-MM-DD`) for day windows or an hour boundary (RFC 3339) for hour windows.","example":"2026-07-20"},"data_as_of":{"$ref":"#/components/schemas/VoiceStatsDataAsOf"}}},"VoiceStatsComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Change in each headline metric from the preceding period to the requested period. A `*_pct_change` is the signed relative change, computed as `(current - previous) / previous`. For example, `0.5` means 50% higher and `-0.2` means 20% lower. It is `null` when the preceding value is zero or undefined. `asr_pp` is the signed difference between the periods' answer-seizure ratios. For example, `0.012` is an increase of 1.2 percentage points. It is `null` when either ratio is undefined.\nOnly the four headline metrics get a computed change. The comparison block carries the preceding period's full set of call-quality measures alongside this one, so the duration distribution and the direction and status splits are present for both periods and any change over them can be derived from the two values without this object naming each one.","required":["total_calls_pct_change","answered_calls_pct_change","asr_pp","acd_ms_pct_change"],"properties":{"total_calls_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in total call legs versus the previous period, as a signed fraction. `null` when the previous period had none.","example":0.203},"answered_calls_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in answered call legs versus the previous period, as a signed fraction. `null` when the previous period had none.","example":0.222},"asr_pp":{"type":["number","null"],"minimum":-1,"maximum":1,"readOnly":true,"description":"Signed difference between this period's and the previous period's answer-seizure ratio, both fractions in [0,1] (multiply by 100 for percentage points). `null` when either period's ratio is undefined.","example":0.0124},"acd_ms_pct_change":{"type":["number","null"],"readOnly":true,"description":"Relative change in average call duration versus the previous period, as a signed fraction. `null` when either period had no answered calls.","example":0.0397}}},"VoiceStatsComparison":{"type":"object","description":"The same statistics for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Every metric below measures that preceding period. The metrics do not cover the requested period. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request.\n","allOf":[{"$ref":"#/components/schemas/VoiceCallQualityStats"},{"type":"object","required":["period","delta"],"properties":{"period":{"$ref":"#/components/schemas/VoiceStatsSummaryPeriod","description":"The preceding window these comparison figures cover, the equal-length window ending immediately before the requested start (the prior day for day windows, the prior hour for hour windows)."},"delta":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceStatsComparisonDelta"}]}}}]},"VoiceStatsSummary":{"type":"object","description":"Aggregate across the full requested period. Includes total and answered call legs, answer-seizure ratio (ASR), average call duration (ACD), and total and billable duration. Daily and hourly responses provide the same call-quality metrics per bucket. Rates and latencies are `null` when their denominator is zero.","allOf":[{"$ref":"#/components/schemas/VoiceCallQualityStats"},{"type":"object","required":["short_call_threshold_ms","total_duration_ms","total_billable_ms","duration_histogram","period"],"properties":{"duration_histogram":{"type":"array","minItems":6,"maxItems":6,"readOnly":true,"description":"Answered-call counts by billable duration, using fixed lower bounds of 0, 30000, 60000, 180000, 300000 and 600000 milliseconds. All six buckets are present.","items":{"$ref":"#/components/schemas/VoiceDurationHistogramBucket"}},"short_call_threshold_ms":{"type":"integer","minimum":1,"maximum":3600000,"readOnly":true,"description":"The threshold this response counted `short_calls` against, echoed from the request or the default of 30 seconds.","example":30000},"total_duration_ms":{"type":"integer","minimum":0,"readOnly":true,"description":"Combined duration of every call leg in the period, in milliseconds, whether or not it was answered. Use it to size the traffic the period carried; `total_billable_ms` is the part of it you are charged for.","example":8520000},"total_billable_ms":{"type":"integer","minimum":0,"readOnly":true,"description":"Combined billable duration in the period, in milliseconds, meaning the duration of the answered calls only. This is the quantity usage-based voice charges are computed from, so it is the figure to reconcile an invoice against.","example":7200000},"period":{"$ref":"#/components/schemas/VoiceStatsSummaryPeriod","description":"The window the response covers (echoed back from the request, day or hour grain), plus `data_as_of`, the freshness boundary the data is current to."},"comparison":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/VoiceStatsComparison"}]}}}]},"VoiceStatsSeriesPeriod":{"type":"object","additionalProperties":false,"description":"The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to.\n","required":["from","to","grain"],"properties":{"from":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive start of the window. A calendar day (`YYYY-MM-DD`) on the day grain, an RFC 3339 instant rounded to the hour on the hour grain.","example":"2026-07-01"},"to":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"Inclusive end of the window. A calendar day (`YYYY-MM-DD`) on the day grain, an RFC 3339 instant rounded to the hour on the hour grain.","example":"2026-07-20"},"grain":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/StatsGrain"}]},"data_as_of":{"$ref":"#/components/schemas/VoiceStatsDataAsOf"}}},"VoiceStatsPoint":{"type":"object","description":"One bucket of the call-statistics series: the same headline metrics as the summary, for a single local day or hour. Buckets with no calls carry zero counts and `null` rates, so charts never interpolate across missing buckets.\n","allOf":[{"$ref":"#/components/schemas/VoiceCallQualityStats"},{"type":"object","required":["bucket"],"properties":{"bucket":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","readOnly":true,"description":"The bucket this row covers, in the requested timezone. Calendar day (`YYYY-MM-DD`) on the day grain, hour start (RFC 3339) on the hour grain.","example":"2026-07-01"}}}]},"VoiceStatsResponse":{"type":"object","additionalProperties":false,"description":"A bucketed call-statistics series over the requested window. The response contains exactly one point per bucket across the window; buckets with no calls are included with zero counts and `null` rates. Expect the most recent buckets to read low until they settle; `data_as_of` on the period marks how far the data is complete.\n","required":["period","short_call_threshold_ms","data"],"properties":{"short_call_threshold_ms":{"type":"integer","minimum":1,"maximum":3600000,"readOnly":true,"description":"The threshold this response counted `short_calls` against, echoed from the request or the default of 30 seconds.","example":30000},"period":{"$ref":"#/components/schemas/VoiceStatsSeriesPeriod","description":"The window and bucket grain the response covers, plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"One point per bucket across the window, in ascending bucket order.","items":{"$ref":"#/components/schemas/VoiceStatsPoint"}}}},"VoiceStatsPeriod":{"type":"object","additionalProperties":false,"description":"The calendar-day window a breakdown response covers, echoed from the request, plus the freshness boundary the data is current to.\n","required":["from","to"],"properties":{"from":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"Inclusive start of the window, as a calendar day (`YYYY-MM-DD`).","example":"2026-07-01"},"to":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"Inclusive end of the window, as a calendar day (`YYYY-MM-DD`).","example":"2026-07-20"},"data_as_of":{"$ref":"#/components/schemas/VoiceStatsDataAsOf"}}},"VoiceResponseCodeStatsPoint":{"type":"object","additionalProperties":false,"description":"One row of the per-response-code call breakdown, keyed by the final SIP response code.","required":["sip_response_code","total_calls","share"],"properties":{"sip_response_code":{"type":["integer","null"],"minimum":100,"maximum":699,"readOnly":true,"description":"The final SIP response code the calls in this row ended with. `null` for calls that received no final response.","example":486},"total_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of call legs that ended with this response code in the period.","example":121},"share":{"type":"number","minimum":0,"maximum":1,"readOnly":true,"description":"This row's fraction of all call legs in the period, between 0 and 1. The denominator is the response's `period_total_calls`, which counts every call leg in the period including those under response codes the `limit` left out.","example":0.0852}}},"VoiceStatsByResponseCodeResponse":{"type":"object","additionalProperties":false,"description":"Per-response-code breakdown for the requested period, ranked by call count descending and capped at the requested `limit` (default 50, max `200`). Every final response code in the period is included, covering successes and failures. A `200` response is a row like any other. The rows therefore show the full distribution of how calls ended rather than only errors.\n","required":["period","data","total","period_total_calls"],"properties":{"period":{"$ref":"#/components/schemas/VoiceStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Response-code rows, ranked by call count descending. Empty when no calls were placed in the period.","items":{"$ref":"#/components/schemas/VoiceResponseCodeStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct final response codes with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to `200`) or narrow the window to see more.\n","example":9},"period_total_calls":{"type":"integer","minimum":0,"readOnly":true,"description":"Total call legs in the period across every response code, including any the `limit` left out. This is the denominator behind each row's `share`, so a client can re-derive the shares itself. Named apart from a row's own `total_calls` because the two count different scopes: this one is the whole period, that one is a single response code.\n","example":1420}}},"VoiceStatsSortMetric":{"type":"string","enum":["total_calls","answered_calls","asr","acd_ms"],"default":"total_calls","description":"Metric used to rank breakdown rows in descending order. `total_calls` and `answered_calls` place the highest-volume rows first. `asr` and `acd_ms` place the best-performing rows first. To find the worst performers, request enough rows to cover the response's `total` and read from the bottom. Rows with a `null` `acd_ms` sort last under `acd_ms`.","example":"asr"},"VoiceCountryStatsPoint":{"type":"object","description":"One row of the per-country call breakdown.","allOf":[{"$ref":"#/components/schemas/VoiceCallQualityStats"},{"type":"object","required":["country"],"properties":{"country":{"readOnly":true,"description":"The destination country this row aggregates, as an ISO 3166-1 alpha-2 code. Countries sharing an international calling code are reported separately, so calls to Canada never appear under the United States. A `null` value identifies the row for destinations whose country could not be determined. This includes numbers outside international format and numbers belonging to a global service rather than a country.\n","oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}]}}}]},"VoiceStatsByCountryResponse":{"type":"object","additionalProperties":false,"description":"Per-country breakdown for the requested period, grouped by destination country, ranked by the `sort` metric (default `total_calls`) descending and capped at the requested `limit` (default 50, max `200`).\n","required":["period","short_call_threshold_ms","data","total"],"properties":{"short_call_threshold_ms":{"type":"integer","minimum":1,"maximum":3600000,"readOnly":true,"description":"The threshold this response counted `short_calls` against, echoed from the request or the default of 30 seconds.","example":30000},"period":{"$ref":"#/components/schemas/VoiceStatsPeriod","description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Country rows, ranked by the `sort` metric (default `total_calls`) descending. Empty when no calls were placed in the period.","items":{"$ref":"#/components/schemas/VoiceCountryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct countries with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to `200`) or narrow the window to see more.\n","example":34}}},"VoiceNumberStatsPoint":{"type":"object","description":"Call-quality statistics for one own number.","allOf":[{"$ref":"#/components/schemas/VoiceCallQualityStats"},{"type":"object","required":["own_number","number_name"],"properties":{"own_number":{"readOnly":true,"type":["string","null"],"minLength":1,"description":"Recorded number on the workspace side of the call, or null when unknown.","example":"+31201234567"},"number_name":{"type":["string","null"],"readOnly":true,"description":"Current friendly name, or null when the number is unnamed or no longer resolves.","example":"Support"}}}]},"VoiceStatsByNumberResponse":{"description":"Call-quality breakdown by own number for the requested window.","type":"object","required":["period","short_call_threshold_ms","data","total","next_cursor","prev_cursor","refresh_cursor"],"properties":{"short_call_threshold_ms":{"type":"integer","minimum":1,"maximum":3600000,"readOnly":true,"description":"Threshold used to count short calls, in milliseconds.","example":30000},"period":{"$ref":"#/components/schemas/VoiceStatsSummaryPeriod","readOnly":true,"description":"Requested window and freshness boundary."},"data":{"type":"array","readOnly":true,"description":"Rows ranked by the requested metric, descending.","items":{"$ref":"#/components/schemas/VoiceNumberStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of matching groups before pagination.","example":2},"next_cursor":{"type":["string","null"],"description":"Cursor for the next page. Pass back as `starting_after` to advance forward. `null` when no next page exists.","example":"eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE0OjAzOjEwWlwiIiwiaSI6IjAxOTJmM2IxLTRjN2UtN2EyYi05ZDYxLThmM2E1YzJlN2I0MCJ9"},"prev_cursor":{"type":["string","null"],"description":"Cursor for the previous page. Pass back as `ending_before` to step backward. `null` when no previous page exists.","example":null},"refresh_cursor":{"type":["string","null"],"description":"Refresh anchor, the first row of this response. Pass back as `ending_before` to fetch what precedes it in the current sort order. On a newest-first sort those are the items that have appeared since; on any other sort they are the items that sort earlier, so refreshing such a list means re-fetching it instead. Non-`null` whenever `data` is non-empty; `null` only on an empty page. Distinct from `prev_cursor`.","example":"eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE2OjQyOjAxWlwiIiwiaSI6IjAxOTJmM2IxLTllMDQtN2NkMy1iODE3LTJhNmY0ZDFjOGUwOSJ9"}},"additionalProperties":false},"VoiceNumberCountryStatsPoint":{"type":"object","description":"Call-quality statistics for one own number country.","allOf":[{"$ref":"#/components/schemas/VoiceCallQualityStats"},{"type":"object","required":["country"],"properties":{"country":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}],"description":"Two-letter country code of the own number, or null when unknown.","example":"NL"}}}]},"VoiceStatsByNumberCountryResponse":{"description":"Call-quality breakdown by own number country for the requested window.","type":"object","required":["period","short_call_threshold_ms","data","total","next_cursor","prev_cursor","refresh_cursor"],"properties":{"short_call_threshold_ms":{"type":"integer","minimum":1,"maximum":3600000,"readOnly":true,"description":"Threshold used to count short calls, in milliseconds.","example":30000},"period":{"$ref":"#/components/schemas/VoiceStatsSummaryPeriod","readOnly":true,"description":"Requested window and freshness boundary."},"data":{"type":"array","readOnly":true,"description":"Rows ranked by the requested metric, descending.","items":{"$ref":"#/components/schemas/VoiceNumberCountryStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Number of matching groups before pagination.","example":2},"next_cursor":{"type":["string","null"],"description":"Cursor for the next page. Pass back as `starting_after` to advance forward. `null` when no next page exists.","example":"eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE0OjAzOjEwWlwiIiwiaSI6IjAxOTJmM2IxLTRjN2UtN2EyYi05ZDYxLThmM2E1YzJlN2I0MCJ9"},"prev_cursor":{"type":["string","null"],"description":"Cursor for the previous page. Pass back as `ending_before` to step backward. `null` when no previous page exists.","example":null},"refresh_cursor":{"type":["string","null"],"description":"Refresh anchor, the first row of this response. Pass back as `ending_before` to fetch what precedes it in the current sort order. On a newest-first sort those are the items that have appeared since; on any other sort they are the items that sort earlier, so refreshing such a list means re-fetching it instead. Non-`null` whenever `data` is non-empty; `null` only on an empty page. Distinct from `prev_cursor`.","example":"eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE2OjQyOjAxWlwiIiwiaSI6IjAxOTJmM2IxLTllMDQtN2NkMy1iODE3LTJhNmY0ZDFjOGUwOSJ9"}},"additionalProperties":false},"VoiceWeekdayStatsPoint":{"type":"object","description":"Call-quality statistics for one weekday.","allOf":[{"$ref":"#/components/schemas/VoiceCallQualityStats"},{"type":"object","required":["weekday"],"properties":{"weekday":{"readOnly":true,"type":"integer","minimum":1,"maximum":7,"description":"Local weekday, from Monday (1) through Sunday (7).","example":1}}}]},"VoiceStatsByWeekdayResponse":{"type":"object","additionalProperties":false,"description":"Call-quality breakdown by weekday for the requested window.","required":["period","short_call_threshold_ms","data","total"],"properties":{"short_call_threshold_ms":{"type":"integer","minimum":1,"maximum":3600000,"readOnly":true,"description":"Threshold used to count short calls, in milliseconds.","example":30000},"period":{"$ref":"#/components/schemas/VoiceStatsSummaryPeriod","readOnly":true,"description":"Requested window and freshness boundary."},"data":{"type":"array","minItems":7,"maxItems":7,"readOnly":true,"description":"Rows ranked by the requested metric, descending.","items":{"$ref":"#/components/schemas/VoiceWeekdayStatsPoint"}},"total":{"type":"integer","minimum":7,"maximum":7,"readOnly":true,"description":"Number of rows in the complete domain.","example":7}}},"VoiceHourOfDayStatsPoint":{"type":"object","description":"Call-quality statistics for one hour of day.","allOf":[{"$ref":"#/components/schemas/VoiceCallQualityStats"},{"type":"object","required":["hour"],"properties":{"hour":{"readOnly":true,"type":"integer","minimum":0,"maximum":23,"description":"Local hour of day, from 0 through 23.","example":1}}}]},"VoiceStatsByHourOfDayResponse":{"type":"object","additionalProperties":false,"description":"Call-quality breakdown by hour of day for the requested window.","required":["period","short_call_threshold_ms","data","total"],"properties":{"short_call_threshold_ms":{"type":"integer","minimum":1,"maximum":3600000,"readOnly":true,"description":"Threshold used to count short calls, in milliseconds.","example":30000},"period":{"$ref":"#/components/schemas/VoiceStatsSummaryPeriod","readOnly":true,"description":"Requested window and freshness boundary."},"data":{"type":"array","minItems":24,"maxItems":24,"readOnly":true,"description":"Rows ranked by the requested metric, descending.","items":{"$ref":"#/components/schemas/VoiceHourOfDayStatsPoint"}},"total":{"type":"integer","minimum":24,"maximum":24,"readOnly":true,"description":"Number of rows in the complete domain.","example":24}}},"VoiceTrunkStatsPoint":{"type":"object","description":"One row of the per-trunk call breakdown.","allOf":[{"$ref":"#/components/schemas/VoiceCallQualityStats"},{"type":"object","required":["trunk_id","trunk_name"],"properties":{"trunk_id":{"readOnly":true,"description":"The SIP trunk this row aggregates. A `null` value identifies the row for calls that did not arrive over a trunk at all, such as a Verify voice call, which is ordinary traffic rather than an unresolved lookup.\n","oneOf":[{"$ref":"#/components/schemas/SIPTrunkID"},{"type":"null"}]},"trunk_name":{"type":["string","null"],"readOnly":true,"description":"The trunk's name as it is configured now, resolved when the response is built rather than recorded when the calls happened, so renaming a trunk renames it here too. It is `null` on the no-trunk row, and on a trunk deleted since the calls it carried, where `trunk_id` is the only handle left on that history.\n","example":"eu-primary"}}}]},"VoiceStatsByTrunkResponse":{"type":"object","additionalProperties":false,"description":"Per-trunk breakdown for the requested period, grouped by SIP trunk, ranked by the `sort` metric (default `total_calls`) descending and capped at the requested `limit` (default 50, max `200`).\n","required":["period","short_call_threshold_ms","data","total"],"properties":{"short_call_threshold_ms":{"type":"integer","minimum":1,"maximum":3600000,"readOnly":true,"description":"The threshold this response counted `short_calls` against, echoed from the request or the default of 30 seconds.","example":30000},"period":{"$ref":"#/components/schemas/VoiceStatsPeriod","readOnly":true,"description":"The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to."},"data":{"type":"array","readOnly":true,"description":"Trunk rows, ranked by the `sort` metric (default `total_calls`) descending. Empty when no calls were placed in the period.","items":{"$ref":"#/components/schemas/VoiceTrunkStatsPoint"}},"total":{"type":"integer","minimum":0,"readOnly":true,"description":"Total number of distinct trunks with activity in the period, counting the no-trunk row, and regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to `200`) or narrow the window to see more.\n","example":7}}},"VoiceDestination":{"type":"object","additionalProperties":false,"required":["country_code","country_name","enabled","status","high_risk_destination"],"properties":{"country_code":{"allOf":[{"$ref":"#/components/schemas/CountryCode"}],"readOnly":true},"country_name":{"type":"string","minLength":1,"description":"Full English country name.","example":"Netherlands","readOnly":true},"dial_code":{"type":"string","description":"International dialling prefix, without the leading plus. Absent for countries that have none.","example":"31","readOnly":true},"region":{"allOf":[{"$ref":"#/components/schemas/DestinationRegion"}],"readOnly":true},"super_region":{"allOf":[{"$ref":"#/components/schemas/DestinationSuperRegion"}],"readOnly":true},"enabled":{"type":"boolean","description":"Whether your workspace has enabled calling to this country."},"status":{"type":"string","minLength":1,"x-extensible-enum":["available","not_supported"],"description":"This country's Voice callability at the destination level, independent of your enabled setting. `available` means we place calls there; `not_supported` means we do not. New values may be added over time, so treat it as open.\n","readOnly":true},"high_risk_destination":{"type":"boolean","description":"Whether we treat this country as a high-risk calling destination.","readOnly":true}}},"VoiceDestinationList":{"type":"object","additionalProperties":false,"required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceDestination"},"description":"The Voice destination countries, each annotated with your workspace's enabled setting."},"total":{"type":"integer","format":"int64","description":"Total number of destination countries."}}},"VoiceDestinationsUpdate":{"type":"object","additionalProperties":false,"required":["destinations"],"properties":{"destinations":{"type":"array","items":{"$ref":"#/components/schemas/DestinationSetting"},"description":"The destination countries to enable or disable. Only the countries listed here change; any country you do not list keeps its current setting.\n","example":[{"country_code":"NL","enabled":true},{"country_code":"GB","enabled":false}]}}},"EsimZoneList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EsimZone"},"description":"Zones, newest first."}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"EsimOfferPhoneInclusion":{"type":"string","minLength":1,"enum":["always","on_request"],"description":"How a plan's phone number is provided. `always` means every eSIM sold from the offer carries a number. `on_request` is declared ahead of the plans that attach one per order, so its arrival is not a breaking change; no current offer answers it.\n","example":"always"},"EsimOfferPhone":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Phone service included with the plan. Every eSIM sold from the offer gets its own phone number; each flag states one direction of service on that number. A false flag means the plan does not include that service.\n","required":["included","voice_inbound","voice_outbound","sms_inbound","sms_outbound"],"properties":{"included":{"$ref":"#/components/schemas/EsimOfferPhoneInclusion"},"voice_inbound":{"type":"boolean","description":"The eSIM can receive calls on its phone number.","example":false},"voice_outbound":{"type":"boolean","description":"The eSIM can place calls.","example":false},"sms_inbound":{"type":"boolean","description":"The eSIM can receive text messages on its phone number.","example":true},"sms_outbound":{"type":"boolean","description":"The eSIM can send text messages.","example":false}},"example":{"included":"always","voice_inbound":false,"voice_outbound":false,"sms_inbound":true,"sms_outbound":false}},"EsimOfferData":{"type":"object","additionalProperties":false,"description":"Data allowance of a bundle.","required":["amount_bytes"],"properties":{"amount_bytes":{"type":"integer","minimum":1,"readOnly":true,"description":"Total data allowance in bytes.","example":10737418240},"throttled_after_bytes":{"type":["integer","null"],"minimum":1,"readOnly":true,"description":"Data amount in bytes after which speed is reduced instead of cut off. Null when the allowance is a hard cap.\n"}}},"EsimOfferValidityType":{"type":"string","minLength":1,"enum":["one_time","recurring"],"description":"How the validity period behaves. one_time runs once and expires; recurring renews for a further period each time it lapses."},"EsimOfferValidityUnit":{"type":"string","minLength":1,"enum":["day","month"],"description":"Unit of the validity period."},"EsimOfferValidity":{"type":"object","additionalProperties":false,"description":"How long a package from this offer stays usable. one_time offers run once for the given period after activation; recurring offers renew for a further period each time it lapses. Match on type and treat an unrecognized value as an offer your integration cannot order.\n","required":["type","unit","value"],"properties":{"type":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferValidityType"}]},"unit":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferValidityUnit"}]},"value":{"type":"integer","minimum":1,"readOnly":true,"description":"Number of units per period.","example":30},"minimum_periods":{"type":["integer","null"],"minimum":1,"readOnly":true,"description":"For recurring offers, the minimum number of periods committed. Null when there is no minimum, and for one_time offers."}}},"EsimOfferBundlePricing":{"type":"object","additionalProperties":false,"description":"Fixed-allowance terms: a data allowance with a validity period, charged once per provisioned package.\n","required":["type","data","validity","price"],"properties":{"type":{"type":"string","minLength":1,"const":"bundle","readOnly":true,"description":"Pricing type."},"data":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferData"}]},"validity":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferValidity"}],"description":"Validity of packages created from this offer. The period starts at activation, which happens on first use in the coverage zone. The effective validity is capped by the eSIM's service period: see the package's expires_at for the real expiry.\n"},"price":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What your workspace is billed per package provisioned from this offer."}}},"EsimOfferSummary":{"type":"object","additionalProperties":false,"description":"Compact offer row for lists: the full offer minus the embedded zone. Fetch the offer or its zone for the country list.\n","required":["id","name","revision","zone_id","speed","stackable","phone","pricing","product","status","created_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferID"}]},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the offer.","example":"Europe 10 GB / 30 days"},"revision":{"type":"integer","minimum":1,"readOnly":true,"description":"Increments whenever the offer's terms change. Orders lock the revision they were quoted at."},"zone_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimZoneID"}],"description":"Coverage zone the offer sells. Offers for the same footprint share one zone."},"speed":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimSpeed"}],"description":"Speed class. For reduced-speed offers, the bundle's data.throttled_after_bytes carries the full-speed allowance."},"stackable":{"type":"boolean","readOnly":true,"description":"Whether packages from this offer can be held alongside packages from other zones on the same eSIM, subject to the eSIM's package_limit."},"phone":{"readOnly":true,"description":"Phone service that comes with the plan, or null when the plan includes no phone number. When present, `included` says how the number is provided and the flags state which call and text directions work.\n","oneOf":[{"$ref":"#/components/schemas/EsimOfferPhone"},{"type":"null"}]},"pricing":{"readOnly":true,"description":"Commercial terms of the offer. Every offer in the current catalog is a bundle: a fixed allowance with a validity period for a fixed price. Match on the pricing type; treat an unrecognized type as an offer your integration cannot order yet.\n","oneOf":[{"$ref":"#/components/schemas/EsimOfferBundlePricing"}]},"product":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/BillingProductSlug"}],"description":"Billing product the offer's charges post under, as it appears on your invoice line items."},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferStatus"}]},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"EsimOfferList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EsimOfferSummary"},"description":"Offers, newest first."}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}]},"EsimOffer":{"type":"object","additionalProperties":false,"description":"A purchasable offer from the curated catalog: the commercial terms for prepaid data in one coverage zone, priced as your workspace is billed. The coverage itself lives on the referenced zone, shared by every offer selling the same footprint.\n","required":["id","name","revision","zone_id","zone","speed","stackable","phone","pricing","product","status","created_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferID"}]},"name":{"type":"string","minLength":1,"readOnly":true,"description":"Display name of the offer.","example":"Europe 10 GB / 30 days"},"revision":{"type":"integer","minimum":1,"readOnly":true,"description":"Increments whenever the offer's terms change. Orders lock the revision they were quoted at."},"zone_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimZoneID"}],"description":"Coverage zone the offer sells. Offers for the same footprint share one zone."},"zone":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimZone"}],"description":"The offer's coverage zone, embedded so one read answers \"where does this work\". The zone resource is authoritative."},"speed":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimSpeed"}],"description":"Speed class. For reduced-speed offers, the bundle's data.throttled_after_bytes carries the full-speed allowance."},"stackable":{"type":"boolean","readOnly":true,"description":"Whether packages from this offer can be held alongside packages from other zones on the same eSIM, subject to the eSIM's package_limit."},"phone":{"readOnly":true,"description":"Phone service that comes with the plan, or null when the plan includes no phone number. When present, `included` says how the number is provided and the flags state which call and text directions work.\n","oneOf":[{"$ref":"#/components/schemas/EsimOfferPhone"},{"type":"null"}]},"pricing":{"readOnly":true,"description":"Commercial terms of the offer. Every offer in the current catalog is a bundle: a fixed allowance with a validity period for a fixed price. Match on the pricing type; treat an unrecognized type as an offer your integration cannot order yet.\n","oneOf":[{"$ref":"#/components/schemas/EsimOfferBundlePricing"}]},"product":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/BillingProductSlug"}],"description":"Billing product the offer's charges post under, as it appears on your invoice line items."},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOfferStatus"}]},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"EsimOrderList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EsimOrder"},"description":"Orders, newest first."}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EsimOrderCreate":{"type":"object","additionalProperties":false,"description":"Request to purchase a data package. Without esim_id, a new eSIM is provisioned carrying the package. With esim_id, the package is added to that eSIM: a new coverage zone stacks alongside existing packages, the same zone adds a further package for it, subject to the eSIM's package_limit and speed class.\n","required":["offer_id"],"properties":{"offer_id":{"allOf":[{"$ref":"#/components/schemas/EsimOfferID"}],"description":"Offer to purchase."},"esim_id":{"allOf":[{"$ref":"#/components/schemas/EsimID"}],"description":"Existing eSIM to add the package to. Omit to provision a new eSIM."},"offer_revision":{"type":"integer","minimum":1,"description":"The offer revision you are quoting from. When set and the offer has since moved to a newer revision, the order is refused with a conflict instead of charging a price you did not see. Omitted, the current revision is used."},"expected_price":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"The price you displayed to the buyer. When set and the workspace's current resolved price differs, the order is refused with a conflict instead of charging a different amount. Catches billing-rate changes, which move independently of the offer revision."},"display_name":{"type":"string","maxLength":120,"description":"Free-text label for the new eSIM, for your own reference. Ignored when esim_id is set.","example":"Amsterdam trip, order 8812"},"tags":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/Tag"},"description":"Tags for the new eSIM, echoed on its lifecycle webhook events. Ignored when esim_id is set."},"metadata":{"type":"object","additionalProperties":true,"description":"Your own key-value data for the new eSIM, echoed on its lifecycle webhook events. Maximum 2 KB serialized. Ignored when esim_id is set."},"delivery":{"allOf":[{"$ref":"#/components/schemas/EsimDelivery"}],"description":"Deliver the install credentials to the traveler by email or SMS as soon as they are available. Rejected when esim_id is set - re-sending an existing eSIM's credentials requires the esim_credentials scope through the deliver endpoint."},"acknowledge_shortened_validity":{"type":"boolean","description":"Set to true to accept a validity cut short by the eSIM's service period. Without it, an order whose package would expire early is refused with a conflict that states the effective validity."}}},"EsimSummary":{"type":"object","additionalProperties":false,"description":"Compact eSIM representation used in lists. Fetch the eSIM by id for the full aggregate with packages, balances, and installation state.\n","required":["id","status","iccid","phone_number","display_name","created_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimID"}]},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimStatus"}]},"iccid":{"type":["string","null"],"readOnly":true,"description":"ICCID of the eSIM profile, or null while none is allocated.","example":"8944500212345678912"},"phone_number":{"type":["string","null"],"readOnly":true,"description":"Phone number attached to this eSIM, in E.164 format, as the supplier reports it. Null while none is on record.\n","example":"+31612345678"},"display_name":{"type":["string","null"],"maxLength":120,"readOnly":true,"description":"Free-text label for your own reference."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true}}},"EsimList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EsimSummary"},"description":"eSIMs, newest first, in compact form; fetch one by id for the full aggregate."}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EsimUpdate":{"type":"object","additionalProperties":false,"description":"Fields that can be updated on an eSIM.","properties":{"display_name":{"type":["string","null"],"maxLength":120,"description":"Free-text label for your own reference. Null clears it."},"tags":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/Tag"},"description":"Replaces the eSIM's tags."},"metadata":{"type":"object","additionalProperties":true,"description":"Replaces the eSIM's metadata. Maximum 2 KB serialized."}}},"EsimInstallationInstructions":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Step-by-step install instructions for the traveler. A step appears only when this response carries the credential it names, so the list varies with the fields beside it. Each step is plain text naming the device's own menu labels, never a screen's appearance or position.\n","required":["language","ios","android"],"properties":{"language":{"$ref":"#/components/schemas/LanguageTag","description":"BCP-47 tag of the language `ios` and `android` are written in. This is the language served, which is the closest match to Accept-Language, or English when nothing closer is available. One list never mixes two languages.\n","example":"en"},"ios":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"Ordered steps for iOS devices."},"android":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"Ordered steps for Android devices."}}},"EsimCredentials":{"type":"object","additionalProperties":false,"description":"Everything needed to install the eSIM on a device, in four forms. The install material is secret: the activation code, its SM-DP+ address and matching ID, the one-tap links, the QR image and the confirmation code each let whoever holds them install this profile, so treat those like passwords.\n","required":["esim_id","ios_install_url","android_install_url","qr_code_url","activation_code","smdp_address","matching_id","apn","data_roaming_required","instructions"],"properties":{"esim_id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimID"}]},"ios_install_url":{"type":["string","null"],"format":"uri","readOnly":true,"x-sensitive":true,"description":"One-tap install link for iOS 17.4 and later, derived from the activation code. Null when a valid link cannot be derived for this eSIM."},"android_install_url":{"type":["string","null"],"format":"uri","readOnly":true,"x-sensitive":true,"description":"One-tap install link for compatible Android devices, derived from the activation code. Null when a valid link cannot be derived for this eSIM."},"qr_code_url":{"type":["string","null"],"format":"uri","readOnly":true,"x-sensitive":true,"description":"Hosted QR code image any eSIM-capable device can scan, rendered from the activation code. The URL is signed and expires; fetch these credentials again for a fresh one. Null when rendering is unavailable."},"activation_code":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Raw activation string for manual entry in device settings.","example":"LPA:1$smdp.example.com$K2-1AbCdE-2FgHiJ"},"smdp_address":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"SM-DP+ server address, for building a custom install flow.","example":"smdp.example.com"},"matching_id":{"type":"string","minLength":1,"readOnly":true,"x-sensitive":true,"description":"Matching ID component of the activation code, for building a custom install flow."},"confirmation_code":{"type":["string","null"],"readOnly":true,"x-sensitive":true,"description":"Confirmation code the device prompts for during install. Null while Bird has no value for it from the issuing supplier, which is the case for every eSIM today. Null is not a statement that the profile needs no code.\n"},"apn":{"type":["string","null"],"readOnly":true,"description":"APN the device must use for data. Null while Bird has no value for it from the issuing supplier, which is the case for every eSIM today. Null is not a statement that the profile configures the APN automatically.\n","example":"internet"},"data_roaming_required":{"type":["boolean","null"],"readOnly":true,"description":"Whether the device must enable data roaming for service to work, as declared by the supplier that issued the profile. Null while Bird has no declaration from that supplier, which is the case for every eSIM today; do not read null as false. When true, `instructions` carries a step for turning roaming on.\n"},"instructions":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimInstallationInstructions"}],"description":"Step-by-step install instructions, covering whichever of the fields in this response carry a value, in the closest language available for the Accept-Language request header. Its `language` field names the one served.\n"}}},"EsimDeliveryStatus":{"type":"string","minLength":1,"enum":["pending","delivered","failed"],"description":"Outcome of a credential delivery.\n\n- `pending`: accepted; the outcome has not settled.\n- `delivered`: the message reached the recipient's provider; terminal.\n- `failed`: the message could not be delivered; `failure_code` says why. Terminal.\n"},"EsimCredentialsDelivery":{"type":"object","additionalProperties":false,"description":"One credential delivery to a traveler. Returned when a delivery is accepted and listed on the eSIM's delivery history; the id reappears on the event that settles it.","required":["id","channel","to","status","failure_code","created_at","settled_at"],"properties":{"id":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimDeliveryID"}]},"channel":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimDeliveryChannel"}]},"to":{"type":"string","minLength":3,"readOnly":true,"description":"Recipient address the message goes to.","example":"traveler@example.com"},"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimDeliveryStatus"}]},"failure_code":{"type":["string","null"],"readOnly":true,"x-extensible-enum":["invalid_recipient","suppressed","blocked","bounced","channel_unavailable","send_failed"],"description":"Why the delivery failed. Null unless status is failed. Open enum: treat unrecognized values as future failure kinds."},"created_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When the delivery was accepted."},"settled_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the outcome became known. Null while pending."}}},"EsimDeliveryList":{"type":"object","additionalProperties":false,"description":"The eSIM's credential deliveries, newest first, most recent 50.","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EsimCredentialsDelivery"}}}},"EsimPackageList":{"type":"object","additionalProperties":false,"description":"The eSIM's data packages. Bounded: an eSIM holds at most package_limit concurrent packages, so the list is returned in full.","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EsimPackage"},"description":"Packages, newest first."}}},"EsimCompatibleOfferList":{"type":"object","additionalProperties":false,"description":"Offers this eSIM can take as a top-up right now, given its mobile network, current packages, and package_limit. A point-in-time answer; re-fetch rather than caching.","required":["data","as_of"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EsimOfferSummary"},"description":"Orderable top-up offers for this eSIM."},"as_of":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When this answer was computed."}}},"EsimUsageRecord":{"type":"object","additionalProperties":false,"description":"One day of data usage for an eSIM in one country.","required":["date","country_code","used_bytes","package_id","updated_at"],"properties":{"date":{"type":"string","format":"date","minLength":1,"readOnly":true,"description":"Calendar day (UTC).","example":"2026-07-29"},"updated_at":{"type":"string","format":"date-time","minLength":1,"readOnly":true,"description":"When this day's figure last changed. Recent days may restate as the network reports late usage; re-read a trailing window rather than assuming a day is final."},"country_code":{"readOnly":true,"description":"Country where the data was used. Null when the network reported the day's usage without a country attribution.","oneOf":[{"$ref":"#/components/schemas/CountryCode"},{"type":"null"}]},"used_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Data used that day in that country, in bytes. Exact, never rounded.","example":734003200},"package_id":{"readOnly":true,"description":"Data package the usage consumed from, or null when it spans packages.","oneOf":[{"$ref":"#/components/schemas/EsimPackageID"},{"type":"null"}]}}},"EsimUsageRecordList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","description":"Page of daily usage records, newest first.","items":{"$ref":"#/components/schemas/EsimUsageRecord"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"EsimOverviewEsimStatusCount":{"type":"object","additionalProperties":false,"description":"Number of the workspace's eSIMs in one lifecycle status.","required":["status","count"],"properties":{"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimStatus"}]},"count":{"type":"integer","minimum":0,"readOnly":true,"description":"eSIMs currently in this status."}}},"EsimOverviewEsims":{"type":"object","additionalProperties":false,"description":"eSIM counts for the workspace, split by lifecycle status.","required":["total","by_status"],"properties":{"total":{"type":"integer","minimum":0,"readOnly":true,"description":"All eSIMs the workspace ever provisioned, in any status."},"by_status":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/EsimOverviewEsimStatusCount"},"description":"Count per lifecycle status; statuses with no eSIMs are omitted."}}},"EsimOverviewDataBalance":{"type":"object","additionalProperties":false,"description":"The combined data balance across the workspace's live packages - the same packages whose balances the per-eSIM reads serve. Zones can overlap in a country, so this is a purchased-allowance figure, not a guarantee that every byte is usable in one place.","required":["total_bytes","used_bytes","remaining_bytes","as_of"],"properties":{"total_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Total purchased data across live packages, in bytes."},"used_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Data used across live packages, in bytes."},"remaining_bytes":{"type":"integer","minimum":0,"readOnly":true,"description":"Data remaining across live packages, in bytes."},"as_of":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Freshness of the combined figure - the oldest balance observation (each package's own `balance.as_of`) among the contributing packages. Null when the workspace has no live packages."}}},"EsimOverviewPackages":{"type":"object","additionalProperties":false,"description":"Package counts for the workspace's live data packages.","required":["live_count","expiring_within_7_days","expiring_within_30_days"],"properties":{"live_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Packages whose balance still counts: provisioning, awaiting first use, active, depleted, or being removed. Expired and removed packages are excluded."},"expiring_within_7_days":{"type":"integer","minimum":0,"readOnly":true,"description":"Active or awaiting-first-use packages whose expiry falls within the next 7 days."},"expiring_within_30_days":{"type":"integer","minimum":0,"readOnly":true,"description":"Active or awaiting-first-use packages whose expiry falls within the next 30 days; includes the 7-day count."}}},"EsimOverviewOrderStatusCount":{"type":"object","additionalProperties":false,"description":"Number of the workspace's orders in one status.","required":["status","count"],"properties":{"status":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOrderStatus"}]},"count":{"type":"integer","minimum":0,"readOnly":true,"description":"Orders currently in this status."}}},"EsimOverviewOrders":{"type":"object","additionalProperties":false,"description":"Order counts for the workspace, split by status.","required":["total","by_status"],"properties":{"total":{"type":"integer","minimum":0,"readOnly":true,"description":"All orders the workspace ever placed, in any status."},"by_status":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/EsimOverviewOrderStatusCount"},"description":"Count per order status; statuses with no orders are omitted."}}},"EsimOverviewSpendLine":{"type":"object","additionalProperties":false,"description":"Completed-order spend in one currency: exact sums of the orders' net prices as quoted at purchase, before any tax billing adds - never converted to another currency and never rounded.","required":["currency_code","total_amount","total_order_count","last_30_days_amount","last_30_days_order_count"],"properties":{"currency_code":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/CurrencyCode"}],"description":"ISO 4217 currency code the orders were priced in."},"total_amount":{"type":"string","minLength":1,"readOnly":true,"description":"All-time completed-order net spend, as a decimal string in major currency units.","example":"124.50"},"total_order_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Completed orders priced in this currency, all time."},"last_30_days_amount":{"type":"string","minLength":1,"readOnly":true,"description":"Net spend on orders completed in the last 30 days, as a decimal string in major currency units.","example":"24.90"},"last_30_days_order_count":{"type":"integer","minimum":0,"readOnly":true,"description":"Orders completed in the last 30 days and priced in this currency."}}},"EsimOverview":{"type":"object","additionalProperties":false,"description":"Fleet-wide eSIM statistics for the workspace, computed at read time from the same eSIMs, packages, and orders the resource lists return.","required":["esims","data_balance","packages","orders","spend"],"properties":{"esims":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOverviewEsims"}]},"data_balance":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOverviewDataBalance"}]},"packages":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOverviewPackages"}]},"orders":{"readOnly":true,"allOf":[{"$ref":"#/components/schemas/EsimOverviewOrders"}]},"spend":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/EsimOverviewSpendLine"},"description":"Completed-order spend, one line per currency. Amounts are exact sums of the orders' net prices; currencies are never combined or converted."}}},"VoiceBuiltinSequence":{"type":"object","additionalProperties":false,"required":["slug","name"],"example":{"slug":"bird_voicemail","name":"Bird voicemail"},"properties":{"slug":{"$ref":"#/components/schemas/VoiceSequenceSlug"},"name":{"type":"string","minLength":1}}},"VoiceBuiltinSequenceList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceBuiltinSequence"}}}},"VoiceSequenceVersionStatus":{"type":"string","minLength":1,"enum":["draft","active","superseded"],"description":"- `draft`: Editable version selected for new calls from matching tester callers.\n- `active`: Published version selected for new calls that do not select the draft. Its sequences are immutable.\n- `superseded`: Previously published version retained for calls already bound to it and as a draft-cloning base. Its sequences are immutable.\n"},"VoiceSequenceVersionNote":{"type":"string","minLength":0,"maxLength":1024,"description":"Version note, limited by the service to 1024 UTF-8 bytes. maxLength also bounds characters; multibyte text can reach the byte limit sooner. Exceeding the byte limit returns 422."},"VoiceSequenceVersion":{"type":"object","additionalProperties":false,"required":["id","number","schema_version","status","note","tester_numbers"],"properties":{"id":{"$ref":"#/components/schemas/VoiceSequenceVersionID"},"number":{"type":"integer","format":"int64","minimum":1},"schema_version":{"type":"integer","minimum":2,"maximum":2},"status":{"$ref":"#/components/schemas/VoiceSequenceVersionStatus"},"note":{"$ref":"#/components/schemas/VoiceSequenceVersionNote"},"tester_numbers":{"type":"array","uniqueItems":true,"items":{"type":"string","minLength":1,"pattern":"^\\+[1-9][0-9]{1,14}$"}}}},"VoiceSequenceVersionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceSequenceVersion"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"VoiceSequenceVersionCreate":{"type":"object","additionalProperties":false,"properties":{"note":{"$ref":"#/components/schemas/VoiceSequenceVersionNote","default":""},"base_version_id":{"$ref":"#/components/schemas/VoiceSequenceVersionID"}}},"VoiceSequenceVersionPatch":{"type":"object","additionalProperties":false,"properties":{"note":{"$ref":"#/components/schemas/VoiceSequenceVersionNote","description":"Omit to preserve the note; an empty string clears it. The service rejects notes over 1024 UTF-8 bytes with 422."},"tester_numbers":{"type":"array","description":"Replaces the draft caller list. Supply phone numbers with a country code starting with +; spaces and phone-number formatting are normalized to E.164 and duplicates are removed. Invalid numbers return 422. An empty list disables draft selection for new calls.","items":{"type":"string","minLength":1}}}},"VoiceSequenceChangeKind":{"type":"string","minLength":1,"enum":["added","removed","changed"],"description":"Difference from the active version captured for this page."},"VoiceSequenceChangeSummary":{"type":"object","additionalProperties":false,"required":["slug","kind"],"properties":{"slug":{"$ref":"#/components/schemas/VoiceSequenceSlug"},"kind":{"$ref":"#/components/schemas/VoiceSequenceChangeKind"}}},"VoiceSequenceDiff":{"description":"Changed-slug summaries against the active version captured for this page. A null active_version_id means the baseline is empty. Fetch each sequence body separately. Cursors refuse if the active version changes; draft edits may change later pages.","unevaluatedProperties":false,"allOf":[{"type":"object","required":["version_id","active_version_id","data"],"properties":{"version_id":{"$ref":"#/components/schemas/VoiceSequenceVersionID"},"active_version_id":{"oneOf":[{"$ref":"#/components/schemas/VoiceSequenceVersionID"},{"type":"null"}]},"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceSequenceChangeSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"VoiceSequenceValidationDiagnosticKind":{"type":"string","minLength":1,"x-extensible-enum":["invalid_json","invalid_sequence","unsupported_schema","missing_sequence","unavailable_asset","invalid_binding","missing_entry"],"description":"Diagnostic category. Open enum: additional categories may be introduced; display their path and message without rejecting an unfamiliar kind.","example":"missing_sequence"},"VoiceSequenceValidationSeverity":{"type":"string","minLength":1,"enum":["error","warning"],"description":"Errors block publication. Warnings describe consequences that do not block publication."},"VoiceSequenceValidationDiagnostic":{"type":"object","additionalProperties":false,"required":["path","kind","message","severity"],"properties":{"slug":{"description":"Sequence containing the issue, or a missing entry used by a phone number. Absent for version-wide issues.","$ref":"#/components/schemas/VoiceSequenceSlug"},"path":{"type":"string","minLength":0,"maxLength":512},"kind":{"$ref":"#/components/schemas/VoiceSequenceValidationDiagnosticKind"},"severity":{"$ref":"#/components/schemas/VoiceSequenceValidationSeverity"},"message":{"type":"string","minLength":1,"description":"Explanation of the issue at this path."}}},"VoiceSequenceValidation":{"description":"Advisory inspection of every saved body and current external references. Each page is a new inspection unless its idempotency key replays an earlier result. Sequence rows share one repeatable-read snapshot; external owner reads are current observations during started_at through finished_at, not one global snapshot. Edits or dependency changes can change later pages. Empty data does not imply validity. Diagnostics sort by version errors, then sequence slug/path/kind, then retained number entries. Publication always validates again while excluding draft writes.","unevaluatedProperties":false,"allOf":[{"type":"object","required":["version_id","started_at","finished_at","sequence_count","diagnostic_count","valid","data"],"properties":{"version_id":{"$ref":"#/components/schemas/VoiceSequenceVersionID"},"started_at":{"type":"string","minLength":1,"format":"date-time"},"finished_at":{"type":"string","minLength":1,"format":"date-time"},"sequence_count":{"type":"integer","format":"int64","minimum":0},"diagnostic_count":{"type":"integer","format":"int64","minimum":0,"description":"Total errors and warnings from this inspection, including those outside this page."},"valid":{"type":"boolean","description":"Whether the complete inspection found no blocking errors. Missing number entry sequences are warnings and do not make the version invalid."},"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceSequenceValidationDiagnostic"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"VoiceSequenceSummary":{"type":"object","additionalProperties":false,"required":["version_id","slug"],"properties":{"version_id":{"$ref":"#/components/schemas/VoiceSequenceVersionID"},"slug":{"$ref":"#/components/schemas/VoiceSequenceSlug"},"name":{"type":"string"}}},"VoiceSequenceList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VoiceSequenceSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"VoiceSequenceCreate":{"type":"object","description":"Ordered call commands and the continuation after normal completion. The server enforces a total of 16 commands including nested gather prompts. Gather and hangup must be the final command; hangup requires next to be null. Violations return 422. Array length alone does not express these combined command and ordering constraints.","additionalProperties":false,"required":["slug","commands","next"],"properties":{"slug":{"$ref":"#/components/schemas/VoiceSequenceSlug"},"name":{"type":"string"},"commands":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/VoiceSequenceCommand"}},"next":{"$ref":"#/components/schemas/VoiceSequenceNext"}},"if":{"properties":{"commands":{"contains":{"properties":{"type":{"const":"hangup"}},"required":["type"]}}},"required":["commands"]},"then":{"properties":{"next":{"type":"null"}}}},"VoiceSequence":{"type":"object","description":"Ordered call commands and the continuation after normal completion. The server enforces a total of 16 commands including nested gather prompts. Gather and hangup must be the final command; hangup requires next to be null. Violations return 422. Array length alone does not express these combined command and ordering constraints.","additionalProperties":false,"required":["version_id","slug","commands","next"],"properties":{"version_id":{"$ref":"#/components/schemas/VoiceSequenceVersionID"},"slug":{"$ref":"#/components/schemas/VoiceSequenceSlug"},"name":{"type":"string"},"commands":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/VoiceSequenceCommand"}},"next":{"$ref":"#/components/schemas/VoiceSequenceNext"}},"if":{"properties":{"commands":{"contains":{"properties":{"type":{"const":"hangup"}},"required":["type"]}}},"required":["commands"]},"then":{"properties":{"next":{"type":"null"}}}},"VoiceSequencePatch":{"type":"object","description":"Ordered call commands and the continuation after normal completion. The server enforces a total of 16 commands including nested gather prompts. Gather and hangup must be the final command; hangup requires next to be null. Violations return 422. Array length alone does not express these combined command and ordering constraints.","additionalProperties":false,"required":["commands","next"],"properties":{"name":{"type":"string"},"commands":{"type":"array","maxItems":16,"items":{"$ref":"#/components/schemas/VoiceSequenceCommand"}},"next":{"$ref":"#/components/schemas/VoiceSequenceNext"}},"if":{"properties":{"commands":{"contains":{"properties":{"type":{"const":"hangup"}},"required":["type"]}}},"required":["commands"]},"then":{"properties":{"next":{"type":"null"}}}},"VoiceSequenceDocument":{"type":"object","additionalProperties":false,"required":["version_id","slug","document","diagnostics"],"properties":{"version_id":{"$ref":"#/components/schemas/VoiceSequenceVersionID"},"slug":{"$ref":"#/components/schemas/VoiceSequenceSlug"},"name":{"type":"string"},"document":{"type":"string","minLength":0,"description":"Exact saved body text, including invalid or incomplete JSON. It is not executable until validation succeeds."},"diagnostics":{"type":"array","description":"Intrinsic body diagnostics from the canonical codec. Publication separately checks references and external resources.","items":{"$ref":"#/components/schemas/VoiceSequenceValidationDiagnostic"}},"sequence":{"description":"The canonical sequence when intrinsic body validation succeeds. Absent while the document is invalid.","$ref":"#/components/schemas/VoiceSequence"}}},"VoiceSequenceDocumentInput":{"type":"object","additionalProperties":false,"required":["document"],"properties":{"slug":{"description":"New slug. Renames the sequence and every reference within this version atomically. Conflicting slugs or documents with malformed JSON refuse the entire change. Number bindings are outside this version and remain unchanged.","$ref":"#/components/schemas/VoiceSequenceSlug"},"name":{"type":"string","description":"Display name. Omit to clear it."},"document":{"type":"string","minLength":0,"maxLength":16384,"description":"Exact UTF-8 sequence body text, limited to 16,384 bytes. Invalid or incomplete JSON is retained with diagnostics. The body contains commands and next; the name is separate."},"create_only":{"type":"boolean","default":false,"description":"Refuse with 409 if this slug already exists. Use when creating a new sequence to avoid replacing another draft entry."}}},"AutomationRuntimeLimits":{"type":"object","additionalProperties":false,"required":["node_data_max_bytes","execution_max_duration_seconds"],"properties":{"node_data_max_bytes":{"type":"integer","minimum":1,"description":"Maximum encoded JSON bytes for one materialized node input or original output. Keys, escaping, and object structure count toward this limit. Channel field character limits also apply."},"execution_max_duration_seconds":{"type":"integer","format":"int64","minimum":1,"description":"Maximum elapsed time from execution admission to completion, in seconds. Queueing, retries, delays, and event waits share this budget. Each execution keeps the deadline assigned when it starts."}}},"AutomationNodeType":{"type":"string","minLength":3,"maxLength":128,"pattern":"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$","description":"Namespaced node type resolved through the automation node catalogue. Trigger types use `trigger.*`, deterministic control and transformation types use `logic.*` or `data.*`, Bird actions use `bird.*`, and external actions use `connector.*`.","example":"bird.sms.send"},"AutomationNodeUpgradeMode":{"type":"string","minLength":1,"enum":["compatible","automatic","manual"],"description":"How a draft can move from the source node type version to the target version. Compatible upgrades only change the exact version, automatic upgrades apply a deterministic transformation, and manual upgrades require authored changes.","example":"compatible"},"AutomationNodeUpgradeFrom":{"type":"object","additionalProperties":false,"required":["version","mode"],"properties":{"version":{"type":"integer","minimum":1,"description":"Immediately preceding version of the same node type."},"mode":{"$ref":"#/components/schemas/AutomationNodeUpgradeMode"}}},"AutomationNodeTypeReference":{"type":"object","additionalProperties":false,"required":["type","version"],"properties":{"type":{"$ref":"#/components/schemas/AutomationNodeType"},"version":{"type":"integer","minimum":1}}},"AutomationAuthoringLevel":{"type":"string","minLength":1,"enum":["primary","advanced"],"description":"Suggested initial visibility. Advanced fields retain all their validation and supported value modes.","example":"primary"},"AutomationAuthoringField":{"type":"object","additionalProperties":false,"required":["schema_pointer"],"properties":{"schema_pointer":{"type":"string","minLength":1,"maxLength":1024,"description":"JSON Pointer from the exact descriptor root to a schema object under input_schema or config_schema. Identifies the physical use-site, including properties and array items, not a path through a $ref. Resolve referenced shapes using schema_resources. Ignore a hint whose use-site is absent from a configured descriptor.","example":"/input_schema/properties/to"},"role":{"type":"string","maxLength":64,"description":"Optional semantic purpose, such as sender, recipient, message_body, template, duration or timestamp. Unknown roles use ordinary schema editing; roles grant no value-source or validation capability. Schema titles and descriptions remain authoritative field copy.","example":"recipient"},"resource_kind":{"type":"string","maxLength":64,"description":"Optional resource identity for an existing product picker, such as sms_template for the canonical template reference object. This is not an endpoint, permission grant, eligibility decision or instruction to fetch. Unknown kinds retain generic editing. Resource resolution uses existing product APIs and their authorization.","example":"sms_template"}}},"AutomationAuthoringAlternative":{"type":"object","additionalProperties":false,"required":["index","label"],"properties":{"index":{"type":"integer","minimum":0,"maximum":15,"description":"Zero-based index in the schema's referenced oneOf or anyOf array.","example":0},"label":{"type":"string","minLength":1,"maxLength":128,"example":"Write a message"}}},"AutomationAuthoringChoice":{"type":"object","additionalProperties":false,"required":["schema_pointer","label","alternatives"],"properties":{"schema_pointer":{"type":"string","minLength":1,"maxLength":1024,"description":"Descriptor-root JSON Pointer to an existing oneOf or anyOf array under input_schema or config_schema. The schema alone defines which combinations are valid. This hint neither persists a mode nor authorizes deleting values when switching alternatives.","example":"/input_schema/oneOf"},"label":{"type":"string","minLength":1,"maxLength":128,"example":"Message content"},"alternatives":{"type":"array","minItems":2,"maxItems":16,"description":"Labels for every schema alternative, in suggested order. Indices are unique and identify the original schema array, not this list's positions.","items":{"$ref":"#/components/schemas/AutomationAuthoringAlternative"}}}},"AutomationAuthoringGroup":{"type":"object","additionalProperties":false,"required":["key","label","level"],"anyOf":[{"required":["fields"]},{"required":["choices"]}],"properties":{"key":{"type":"string","minLength":1,"maxLength":64,"description":"Stable presentation key, unique within this node version's groups.","example":"content"},"label":{"type":"string","minLength":1,"maxLength":128,"example":"Content"},"level":{"$ref":"#/components/schemas/AutomationAuthoringLevel"},"fields":{"type":"array","minItems":1,"maxItems":64,"description":"Fields in suggested order. A schema location appears in at most one group.","items":{"$ref":"#/components/schemas/AutomationAuthoringField"}},"choices":{"type":"array","minItems":1,"maxItems":8,"description":"Labels for existing schema alternatives, suggested before this group's fields. Choices never prescribe mutations.","items":{"$ref":"#/components/schemas/AutomationAuthoringChoice"}}}},"AutomationNodeAuthoring":{"type":"object","additionalProperties":false,"description":"Optional presentation hints for an exact node version. These hints never change validation, value-source permissions, defaults or execution. Unlisted fields remain editable.","required":["groups"],"properties":{"groups":{"type":"array","minItems":1,"maxItems":16,"description":"Logical groups in suggested display order, independent of any editor layout.","items":{"$ref":"#/components/schemas/AutomationAuthoringGroup"}}}},"AutomationPortKey":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z][a-z0-9_]{0,63}$","description":"Stable named input or output declared by a node-type version.","example":"success"},"AutomationNodeExampleOutput":{"type":"object","additionalProperties":false,"required":["name","port","output"],"properties":{"name":{"type":"string","minLength":1,"maxLength":128,"description":"Scenario name shown when choosing an example output."},"port":{"$ref":"#/components/schemas/AutomationPortKey"},"output":{"type":"object","additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Schema-valid output for this example's configuration and the named port."}}},"AutomationNodeExample":{"type":"object","additionalProperties":false,"required":["name","config","input"],"properties":{"name":{"type":"string","minLength":1},"config":{"type":"object","additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Example configuration for this exact node version. References to other nodes require those nodes in your definition."},"input":{"type":"object","additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Example authored input. Replace sample addresses and resource IDs with values from your workspace before testing with actions enabled."},"outputs":{"type":"array","maxItems":8,"items":{"$ref":"#/components/schemas/AutomationNodeExampleOutput"},"description":"Schema-valid outcomes for previewing this exact example without executing the node."}}},"AutomationNodeTypeStatus":{"type":"string","minLength":1,"enum":["preview","available","deprecated"],"description":"Availability of this node-type version:\n\n- `preview` means workspaces with preview access can select and publish it.\n- `available` means workspaces with Automations access can select and publish it.\n- `deprecated` means existing published versions can execute it, but new drafts cannot select or publish it.\n","example":"available"},"AutomationNodeTypeCatalogVersion":{"type":"object","unevaluatedProperties":false,"allOf":[{"if":{"properties":{"version":{"minimum":2}},"required":["version"]},"then":{"required":["upgrade_from"],"properties":{"upgrade_from":{"$ref":"#/components/schemas/AutomationNodeUpgradeFrom"}}}},{"if":{"properties":{"version":{"const":1}},"required":["version"]},"then":{"not":{"properties":{"upgrade_from":{"$ref":"#/components/schemas/AutomationNodeUpgradeFrom"}},"required":["upgrade_from"]}}}],"if":{"properties":{"replacement":{"$ref":"#/components/schemas/AutomationNodeTypeReference"}},"required":["replacement"]},"then":{"properties":{"status":{"const":"deprecated"}}},"required":["version","status"],"properties":{"authoring":{"allOf":[{"$ref":"#/components/schemas/AutomationNodeAuthoring"}],"description":"Optional, mutable catalogue presentation for this exact version. Excluded from execution digests; canonical schemas remain authoritative."},"examples":{"type":"array","items":{"$ref":"#/components/schemas/AutomationNodeExample"},"description":"Authoring examples for this exact node version. Examples do not change the execution contract."},"version":{"type":"integer","minimum":1,"example":1},"status":{"$ref":"#/components/schemas/AutomationNodeTypeStatus"},"replacement":{"allOf":[{"$ref":"#/components/schemas/AutomationNodeTypeReference"}],"description":"Non-deprecated node type version recommended instead of this deprecated version."},"upgrade_from":{"allOf":[{"$ref":"#/components/schemas/AutomationNodeUpgradeFrom"}],"description":"How a draft moves from the immediately preceding version of the same node type to this version."}}},"AutomationNodeTypeCatalog":{"type":"object","additionalProperties":false,"required":["type","recommended_version","display_name","category","documentation_url","versions"],"properties":{"description":{"type":"string","description":"Guidance for choosing and connecting this node, including behavior that cannot be inferred from its input schema."},"type":{"$ref":"#/components/schemas/AutomationNodeType"},"recommended_version":{"type":"integer","minimum":1,"description":"Version selected for newly authored nodes unless the caller chooses another available version.","example":2},"display_name":{"type":"string","minLength":1,"maxLength":128,"description":"Customer-facing name shown by editors, agents, and run inspectors.","example":"Send SMS"},"category":{"type":"string","minLength":1,"maxLength":64,"description":"Catalogue grouping used to organize node choices in editors and agent discovery.","example":"Messaging"},"documentation_url":{"type":"string","minLength":1,"format":"uri","pattern":"^https?://","description":"HTTP or HTTPS documentation for configuring and interpreting this node type.","example":"https://docs.bird.com/automations/nodes/bird.sms.send"},"versions":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/AutomationNodeTypeCatalogVersion"},"description":"Lifecycle and selection policy for every registered execution descriptor."}}},"AutomationNodeTypeList":{"type":"object","additionalProperties":false,"required":["data","limits"],"properties":{"limits":{"$ref":"#/components/schemas/AutomationRuntimeLimits"},"data":{"type":"array","items":{"$ref":"#/components/schemas/AutomationNodeTypeCatalog"}}}},"AutomationOperationID":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[a-z][A-Za-z0-9]*$","description":"Public operation used to derive an action node's request and response contracts.","example":"createSMSMessage"},"AutomationOutputPortSuccessConstraint":{"type":"object","additionalProperties":true,"required":["key"],"properties":{"key":{"const":"success"}}},"AutomationOutputPortFailureConstraint":{"type":"object","additionalProperties":true,"required":["key","unconnected"],"properties":{"key":{"const":"failure"},"unconnected":{"const":"fail"}}},"AutomationNodeKind":{"type":"string","minLength":1,"enum":["trigger","logic","action"],"description":"Execution role of the node type.","example":"action"},"AutomationJSONSchema":{"type":"object","additionalProperties":true,"description":"JSON Schema 2020-12 document used to validate node configuration, input, or output."},"AutomationPortRole":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z][a-z0-9_]{0,63}$","description":"Semantic role used by editors to present a port without interpreting its key.","example":"failure"},"AutomationPortUnconnected":{"type":"string","minLength":1,"enum":["complete","fail"],"description":"Result when the output is selected without a connected edge. Omission means complete."},"AutomationCapability":{"type":"string","minLength":3,"maxLength":128,"pattern":"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$","description":"Namespaced execution context that must be guaranteed along a graph path.","example":"voice.live_call"},"AutomationCapabilityEffects":{"type":"object","additionalProperties":false,"required":["add","remove"],"properties":{"add":{"type":"array","uniqueItems":true,"items":{"$ref":"#/components/schemas/AutomationCapability"},"description":"Capabilities guaranteed after this outcome."},"remove":{"type":"array","uniqueItems":true,"items":{"$ref":"#/components/schemas/AutomationCapability"},"description":"Capabilities no longer guaranteed after this outcome."}}},"AutomationPort":{"type":"object","required":["key"],"properties":{"key":{"$ref":"#/components/schemas/AutomationPortKey"},"role":{"$ref":"#/components/schemas/AutomationPortRole"},"description":{"type":"string","maxLength":512,"description":"Human-readable purpose of the port."},"schema":{"allOf":[{"$ref":"#/components/schemas/AutomationJSONSchema"}],"description":"Value made available when this port is selected or received."},"unconnected":{"$ref":"#/components/schemas/AutomationPortUnconnected"},"capability_effects":{"$ref":"#/components/schemas/AutomationCapabilityEffects"}}},"AutomationPortCapabilityEffectsConstraint":{"type":"object","additionalProperties":true,"required":["capability_effects"],"properties":{"capability_effects":{"$ref":"#/components/schemas/AutomationCapabilityEffects"}}},"AutomationPortSchemaConstraint":{"type":"object","additionalProperties":true,"required":["schema"],"properties":{"schema":{"$ref":"#/components/schemas/AutomationJSONSchema"}}},"AutomationDynamicPort":{"type":"object","required":["source"],"properties":{"source":{"type":"string","minLength":1,"pattern":"^(/([^~]|~[01])*)+$","description":"JSON Pointer to a scalar or array in literal node configuration from which ports are projected."},"key":{"type":"string","pattern":"^$|^(/([^~]|~[01])*)+$","description":"JSON Pointer, evaluated relative to each array element, that selects its port key. Omission uses the selected scalar or each scalar array value."},"role":{"$ref":"#/components/schemas/AutomationPortRole"},"description":{"type":"string","maxLength":512},"schema":{"$ref":"#/components/schemas/AutomationJSONSchema"},"unconnected":{"$ref":"#/components/schemas/AutomationPortUnconnected"},"capability_effects":{"$ref":"#/components/schemas/AutomationCapabilityEffects"}}},"AutomationPermission":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[a-z][a-z0-9_.]*$","description":"Workspace permission checked at publication and draft execution.","example":"sms.write"},"AutomationExecutionTrait":{"type":"string","minLength":1,"x-extensible-enum":["side_effect","billable","external_world","idempotent"],"description":"Runtime behavior used to apply execution policy rather than caller authorization."},"AutomationSecretPathList":{"type":"array","uniqueItems":true,"items":{"type":"string","minLength":1,"pattern":"^(/([^~]|~[01])*)+$"},"description":"JSON Pointers whose resolved values must be removed before every durable write."},"AutomationSecretPaths":{"type":"object","additionalProperties":false,"required":["input","output","error"],"properties":{"input":{"$ref":"#/components/schemas/AutomationSecretPathList"},"output":{"$ref":"#/components/schemas/AutomationSecretPathList"},"error":{"$ref":"#/components/schemas/AutomationSecretPathList"}}},"AutomationOutcomeMetricKey":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$","description":"Stable key for a node outcome returned by automation statistics.","example":"message.delivered"},"AutomationCountOutcomeMetric":{"type":"object","additionalProperties":false,"required":["key","kind"],"properties":{"key":{"$ref":"#/components/schemas/AutomationOutcomeMetricKey"},"kind":{"type":"string","minLength":1,"const":"count","description":"Records a count that can also serve as a rate numerator or denominator.","example":"count"}}},"AutomationRateOutcomeMetric":{"type":"object","additionalProperties":false,"required":["key","kind","numerator","denominator"],"properties":{"key":{"allOf":[{"$ref":"#/components/schemas/AutomationOutcomeMetricKey"}],"description":"Stable metric key calculated from two count metrics.","example":"message.delivery_rate"},"kind":{"type":"string","minLength":1,"const":"rate","description":"Calculates a rate with an explicit denominator.","example":"rate"},"numerator":{"allOf":[{"$ref":"#/components/schemas/AutomationOutcomeMetricKey"}],"description":"Count metric key used as the numerator.","example":"message.delivered"},"denominator":{"allOf":[{"$ref":"#/components/schemas/AutomationOutcomeMetricKey"}],"description":"Count metric key used as the denominator. A zero count produces a null rate.","example":"message.accepted"}}},"AutomationOutcomeMetric":{"description":"Node-specific outcome metric returned by version-scoped automation statistics.","oneOf":[{"$ref":"#/components/schemas/AutomationCountOutcomeMetric"},{"$ref":"#/components/schemas/AutomationRateOutcomeMetric"}],"discriminator":{"propertyName":"kind","mapping":{"count":"#/components/schemas/AutomationCountOutcomeMetric","rate":"#/components/schemas/AutomationRateOutcomeMetric"}}},"AutomationNodeAnalytics":{"type":"object","additionalProperties":false,"required":["outcome_metrics"],"properties":{"outcome_metrics":{"type":"array","items":{"$ref":"#/components/schemas/AutomationOutcomeMetric"},"description":"Node-specific counts and rates returned by version-scoped automation statistics."}}},"AutomationNodeDescriptor":{"type":"object","x-go-type-skip-optional-pointer":true,"unevaluatedProperties":false,"allOf":[{"oneOf":[{"properties":{"type":{"pattern":"^trigger\\."},"kind":{"const":"trigger"}},"required":["type","kind"]},{"properties":{"type":{"pattern":"^(logic|data)\\."},"kind":{"const":"logic"}},"required":["type","kind"]},{"properties":{"type":{"pattern":"^(bird|connector)\\."},"kind":{"const":"action"}},"required":["type","kind"]}]}],"if":{"properties":{"kind":{"const":"action"}},"required":["kind"]},"then":{"properties":{"output_ports":{"allOf":[{"contains":{"$ref":"#/components/schemas/AutomationOutputPortSuccessConstraint"}},{"contains":{"$ref":"#/components/schemas/AutomationOutputPortFailureConstraint"}}]}},"if":{"properties":{"type":{"pattern":"^bird\\."}},"required":["type"]},"then":{"required":["operation_id"],"properties":{"operation_id":{"$ref":"#/components/schemas/AutomationOperationID"}}}},"required":["type","version","kind","config_schema","input_schema","schema_resources","input_ports","output_ports","dynamic_input_ports","dynamic_output_ports","required_capabilities","required_permissions","execution_traits","secret_paths"],"properties":{"type":{"$ref":"#/components/schemas/AutomationNodeType"},"version":{"type":"integer","minimum":1,"description":"Immutable version of the node contract and execution semantics.","example":1},"kind":{"$ref":"#/components/schemas/AutomationNodeKind"},"operation_id":{"allOf":[{"$ref":"#/components/schemas/AutomationOperationID"}],"description":"Nodes without a public operation omit this field."},"config_schema":{"allOf":[{"$ref":"#/components/schemas/AutomationJSONSchema"}],"description":"JSON Schema for configuration interpreted by the trigger or control node."},"input_schema":{"allOf":[{"$ref":"#/components/schemas/AutomationJSONSchema"}],"description":"JSON Schema for values evaluated before the node executes."},"schema_resources":{"type":"object","propertyNames":{"pattern":"^(bird://automation-node-schemas/[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+/[1-9][0-9]*/[A-Za-z][A-Za-z0-9]*|https://bird\\.com/automation-event-schemas/components/schemas/(ids/)?[A-Za-z][A-Za-z0-9]*\\.yaml)$"},"additionalProperties":{"$ref":"#/components/schemas/AutomationJSONSchema"},"description":"Schemas referenced by the configuration, input, or port schemas. Every non-fragment `$ref` uses a key from this map. Node schemas use `bird://automation-node-schemas/{node_type}/{node_version}/{schema_name}`. Configured event nodes also include the event catalogue's schema identifiers. Resolve these references from the map without network requests.\n"},"input_ports":{"type":"array","uniqueItems":true,"items":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AutomationPort"},{"not":{"$ref":"#/components/schemas/AutomationPortCapabilityEffectsConstraint"}}]},"description":"Named ports through which the node can receive control."},"output_ports":{"type":"array","uniqueItems":true,"items":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AutomationPort"},{"$ref":"#/components/schemas/AutomationPortSchemaConstraint"}]},"description":"Named outcomes that can select an outgoing edge."},"dynamic_input_ports":{"type":"array","items":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AutomationDynamicPort"},{"not":{"$ref":"#/components/schemas/AutomationPortCapabilityEffectsConstraint"}}]},"description":"Deterministic projections from literal node configuration into instance-specific input ports."},"dynamic_output_ports":{"type":"array","items":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AutomationDynamicPort"},{"$ref":"#/components/schemas/AutomationPortSchemaConstraint"}]},"description":"Deterministic projections from literal node configuration into instance-specific output ports."},"required_capabilities":{"type":"array","uniqueItems":true,"items":{"$ref":"#/components/schemas/AutomationCapability"},"description":"Execution context that every incoming path must guarantee before this node can run."},"required_permissions":{"type":"array","uniqueItems":true,"items":{"$ref":"#/components/schemas/AutomationPermission"},"description":"Caller permissions checked when a version is published or a draft execution starts."},"execution_traits":{"type":"array","uniqueItems":true,"items":{"$ref":"#/components/schemas/AutomationExecutionTrait"},"description":"Runtime properties used to apply side-effect, billing, and idempotency policy."},"secret_paths":{"$ref":"#/components/schemas/AutomationSecretPaths"},"analytics":{"$ref":"#/components/schemas/AutomationNodeAnalytics"}}},"AutomationEventWaitSupport":{"type":"object","additionalProperties":false,"required":["caused_by"],"properties":{"caused_by":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AutomationNodeTypeReference"},"description":"Exact action-node versions whose outcomes can be correlated with this event."},"source_input_schema":{"$ref":"#/components/schemas/AutomationJSONSchema","description":"Additional constraints on a watched action's input, applied alongside its descriptor schema. Expressions are checked after materialization. Absent when no extra constraint applies.\n"}}},"AutomationEventEnrichment":{"type":"object","additionalProperties":false,"required":["resource_schema","operation_id"],"properties":{"resource_schema":{"$ref":"#/components/schemas/AutomationJSONSchema","description":"Schema used to validate the complete message resource captured alongside the original event. Declared fields retain their constraints; message records accept additional fields."},"operation_id":{"type":"string","minLength":1,"description":"Read operation whose message shape and permissions apply to the snapshot. Automatic enrichment uses your organization's normal API rate limits.","example":"getInboundMessage"},"required_permissions":{"type":"array","items":{"$ref":"#/components/schemas/AutomationPermission"},"description":"Additional permissions required to read the resource snapshot.","example":["emails.read"]}}},"AutomationEventType":{"type":"object","additionalProperties":false,"required":["type","schema","schema_resources","trigger"],"properties":{"type":{"$ref":"#/components/schemas/WebhookEventType","description":"Bird event type used in trigger or wait configuration."},"schema":{"$ref":"#/components/schemas/AutomationJSONSchema","description":"Schema used to validate the public event envelope, including type, timestamp, and data. Declared fields retain their constraints; event records accept additional fields."},"schema_resources":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/AutomationJSONSchema"},"description":"Referenced schemas keyed by absolute URI. Resolve schema references from this mapping; the URIs are identifiers and do not require network requests.\n"},"trigger":{"type":"boolean","description":"Whether this event can start an execution through trigger.bird_event."},"wait":{"$ref":"#/components/schemas/AutomationEventWaitSupport","description":"Action-correlated wait support. Absent when this event cannot resume a wait."},"enrichment":{"$ref":"#/components/schemas/AutomationEventEnrichment","description":"Resource snapshot supplied by a received-message trigger. Absent for events without enrichment."}}},"AutomationEventTypeList":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AutomationEventType"}}}},"AutomationUseCaseTemplateID":{"type":"string","minLength":1,"pattern":"^aut_[0-9a-hjkmnp-tv-z]{26}$","example":"aut_01krdgeqcxet5s7t44vh8rt9mg"},"AutomationUseCaseTemplateBase":{"type":"object","required":["id","key","name","description","category","channels","node_types","trigger_type","node_count"],"properties":{"id":{"$ref":"#/components/schemas/AutomationUseCaseTemplateID"},"key":{"type":"string","minLength":1,"description":"Stable catalogue key for this use case.","example":"order_confirmation_sms"},"name":{"type":"string","minLength":1,"example":"Send an order confirmation by SMS"},"description":{"type":"string","minLength":1,"description":"Customer goal and the behavior implemented by the supplied workflow."},"category":{"type":"string","minLength":1,"description":"Primary customer-goal category. Labels and available values are returned in the list response's facets.","example":"commerce"},"channels":{"type":"array","items":{"type":"string","minLength":1},"description":"Messaging channels used by the definition. An empty array means the workflow requires no messaging channel.","example":["sms"]},"node_types":{"type":"array","items":{"type":"string","minLength":1},"description":"Distinct exact node type names used by the definition. Read the definition for each node's pinned version.","example":["trigger.application_event","bird.sms.send"]},"trigger_type":{"type":"string","minLength":1,"description":"Node type of the definition's single trigger.","example":"trigger.application_event"},"node_count":{"type":"integer","minimum":1,"description":"Number of nodes in the supplied definition.","example":2}}},"AutomationUseCaseTemplateSummary":{"allOf":[{"$ref":"#/components/schemas/AutomationUseCaseTemplateBase"}],"unevaluatedProperties":false},"AutomationUseCaseTemplateFacet":{"type":"object","additionalProperties":false,"required":["value","label","count"],"properties":{"value":{"type":"string","minLength":1,"description":"Value accepted by the corresponding filter.","example":"commerce"},"label":{"type":"string","minLength":1,"example":"Commerce"},"count":{"type":"integer","minimum":0,"description":"Matching templates across all pages after applying every filter except this facet's own selected values.","example":10}}},"AutomationUseCaseTemplateFacets":{"type":"object","additionalProperties":false,"required":["category","channel","trigger_type","node_type"],"properties":{"category":{"type":"array","items":{"$ref":"#/components/schemas/AutomationUseCaseTemplateFacet"}},"channel":{"type":"array","items":{"$ref":"#/components/schemas/AutomationUseCaseTemplateFacet"}},"trigger_type":{"type":"array","items":{"$ref":"#/components/schemas/AutomationUseCaseTemplateFacet"}},"node_type":{"type":"array","items":{"$ref":"#/components/schemas/AutomationUseCaseTemplateFacet"}}}},"AutomationUseCaseTemplateList":{"allOf":[{"type":"object","required":["data","facets"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AutomationUseCaseTemplateSummary"}},"facets":{"$ref":"#/components/schemas/AutomationUseCaseTemplateFacets"}}},{"$ref":"#/components/schemas/_ListEnvelopeWithTotal"}],"unevaluatedProperties":false},"AutomationExpressionEnvironment":{"type":"string","minLength":1,"enum":["bird.cel.v1"],"description":"Versioned CEL environment used by the definition. Roots are `trigger`, `steps`, `variables`, and `execution`. Use `steps.<node_id>.output` for an earlier successful node and `steps.<node_id>.error` on its failure path; validation with `node_id` reports available sources.\n\nApplication-event wait filters use only `candidate.event` and retained `trigger.data`. The candidate is the original schema-valid event; for Bird triggers, retained trigger data exposes only the original event. `candidate` is unavailable outside these filters.\n\n`bird.cel.v1` supports scalar operators, conditionals, conversions, object and array literals, indexing, `size()`, and the `has()` presence macro. Collection macros such as `map`, `filter`, `exists`, and `all` are not enabled. Guard optional fields and array indexes explicitly; for example, `size(items) > 0 ? items[0] : 'none'`.\n","example":"bird.cel.v1"},"AutomationNodePresentation":{"type":"object","additionalProperties":false,"properties":{"label":{"type":"string","maxLength":128},"x":{"type":"number"},"y":{"type":"number"},"collapsed":{"type":"boolean"}}},"AutomationViewport":{"type":"object","additionalProperties":false,"properties":{"x":{"type":"number"},"y":{"type":"number"},"zoom":{"type":"number","minimum":0,"not":{"enum":[0]}}}},"AutomationNodeID":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z][a-z0-9_]{0,63}$","not":{"enum":["false","in","null","true"]},"description":"Stable identifier for a node within one automation definition. It remains unchanged when the node label or canvas position changes.","example":"send_sms"},"AutomationPreviewSample":{"type":"object","additionalProperties":false,"required":["node_id","port","output"],"properties":{"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"port":{"type":"string","minLength":1,"description":"Resolved output port to simulate, such as success, failure, matched, timeout, or next."},"output":{"type":"object","additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Payload for the selected port, including failure payloads. When submitted to preview or evaluation, it is validated against the original resolved port schema before retention rules apply, with a limit of 131072 encoded JSON bytes per payload."}}},"AutomationPreviewRequest":{"type":"object","additionalProperties":false,"required":["trigger_node_id","trigger_data"],"properties":{"node_samples":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/AutomationPreviewSample"},"description":"Optional hypothetical results for action, wait, and delay nodes. Omission supplies no node outcomes. When submitted to preview or evaluation, each node may appear once and all samples are validated, including unused ones; invalid samples return 422. Pure nodes evaluate their real logic. No action or timer is executed. A draft-preview request is limited to 1 MiB."},"trigger_node_id":{"$ref":"#/components/schemas/AutomationNodeID","example":"order_shipped"},"trigger_data":{"type":"object","additionalProperties":true,"x-go-type-skip-optional-pointer":true,"example":{"data":{"order_id":"ord_123"}},"description":"Sample input for the selected trigger. For `trigger.application_event`, provide `data` matching the trigger's configured data schema and an optional `occurred_at` timestamp. Omit `id` and `type`; preview supplies them, just like manual execution.\n\nFor other triggers, provide their complete configured output. A Bird-event preview accepts an `event`/`resource`/`snapshot` sample to evaluate downstream nodes. An original webhook event without enrichment stops at the trigger as `not_executed`. Preview never fetches message content or performs actions.\n"}}},"AutomationPresentation":{"type":"object","additionalProperties":false,"properties":{"nodes":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/AutomationNodePresentation"},"description":"Optional editor state keyed by stable node ID. It has no execution meaning."},"viewport":{"$ref":"#/components/schemas/AutomationViewport"},"preview":{"$ref":"#/components/schemas/AutomationPreviewRequest","description":"Optional authoring samples for workflow and step previews. Omitting node_samples supplies no node outcomes. This snapshot has no execution meaning; preview validates samples against the current definition. The complete definition, including this snapshot, remains limited to 256 KiB."}}},"AutomationNode":{"type":"object","additionalProperties":false,"required":["id","type","type_version"],"properties":{"id":{"$ref":"#/components/schemas/AutomationNodeID"},"type":{"$ref":"#/components/schemas/AutomationNodeType"},"type_version":{"type":"integer","minimum":1,"description":"Exact node-type contract used to validate and execute this node.","example":1},"config":{"type":"object","additionalProperties":true,"description":"Trigger or control-node configuration validated against the node descriptor's configuration schema."},"input":{"type":"object","additionalProperties":true,"description":"Action input validated against the node descriptor's input schema, including any explicit expression or template values it permits."}}},"AutomationEdgeID":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[a-z][a-z0-9_-]{0,127}$","description":"Stable identifier for an edge within one automation definition.","example":"trigger-to-send"},"AutomationPortReference":{"type":"object","additionalProperties":false,"required":["node_id","port"],"properties":{"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"port":{"$ref":"#/components/schemas/AutomationPortKey"}}},"AutomationEdge":{"type":"object","additionalProperties":false,"required":["id","from","to"],"properties":{"id":{"$ref":"#/components/schemas/AutomationEdgeID"},"from":{"allOf":[{"$ref":"#/components/schemas/AutomationPortReference"}],"description":"Output port selected on the source node."},"to":{"allOf":[{"$ref":"#/components/schemas/AutomationPortReference"}],"description":"Input port receiving control on the destination node."}}},"AutomationDefinitionEnvelope":{"type":"object","required":["schema_version","expression_environment","nodes","edges"],"properties":{"schema_version":{"type":"integer","enum":[1],"description":"Version of the automation definition envelope.","example":1},"expression_environment":{"$ref":"#/components/schemas/AutomationExpressionEnvironment"},"presentation":{"$ref":"#/components/schemas/AutomationPresentation"},"nodes":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AutomationNode"},"description":"Nodes in the automation graph. Each referenced descriptor classifies its node as a trigger, logic, or action node. Array order has no execution meaning."},"edges":{"type":"array","items":{"$ref":"#/components/schemas/AutomationEdge"},"description":"Directed connections between named node ports. The publish validator accepts only acyclic graphs."}}},"AutomationExpression":{"type":"object","additionalProperties":false,"required":["$expr"],"properties":{"$expr":{"type":"string","minLength":1,"description":"CEL expression evaluated in the definition's declared expression environment.","example":"trigger.data.from"}}},"key":{"allOf":[{"$ref":"#/components/schemas/AutomationExpression"}],"description":"Optional CEL expression evaluated once from validated trigger data. Must produce a nonblank string of at most 256 characters. Omit to prevent overlap across the automation."},"AutomationReentryPolicy":{"type":"object","additionalProperties":false,"description":"Prevent overlapping executions within the workspace, automation, and mode across published versions. Omit key to skip while any execution is active, or provide a key to prevent overlap within each group.","required":["mode"],"properties":{"mode":{"type":"string","minLength":1,"const":"skip_active","description":"Skip admission while a matching execution is active, including while waiting or retrying.","example":"skip_active"},"key":{"allOf":[{"$ref":"#/components/schemas/AutomationExpression"}],"description":"Optional CEL expression evaluated once from validated trigger data. Must produce a nonblank string of at most 256 characters. Omit to prevent overlap across the automation."}}},"AutomationCancellationScope":{"type":"string","minLength":1,"enum":["automation","business_key"],"description":"Automation considers active runs in the selected mode, without requiring a subject key or overlap policy. Business key addresses the run whose active key matches the event subject_key and requires a keyed reentry policy. Omitted scope preserves business-key routing."},"AutomationCancellationReason":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z][a-z0-9_]*$","description":"Stable reason retained when an application event cancels an execution.","example":"invoice_voided"},"AutomationCancelOnPolicy":{"type":"object","additionalProperties":false,"required":["name","data_schema","reason"],"properties":{"scope":{"$ref":"#/components/schemas/AutomationCancellationScope"},"filter":{"allOf":[{"$ref":"#/components/schemas/AutomationExpression"}],"description":"Optional boolean CEL expression using candidate.event and the run's retained trigger.data. False results and evaluation errors leave the run active."},"name":{"type":"string","minLength":3,"maxLength":128,"pattern":"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$","description":"Customer event name, unique within cancel_on. Shared declarations in the definition must agree on its data schema.","example":"invoice.voided"},"data_schema":{"allOf":[{"$ref":"#/components/schemas/AutomationJSONSchema"}],"description":"JSON Schema for the event data. Uses the same restrictions as application-event trigger schemas."},"reason":{"$ref":"#/components/schemas/AutomationCancellationReason"}}},"AutomationDefinitionSettings":{"type":"object","additionalProperties":false,"properties":{"reentry":{"$ref":"#/components/schemas/AutomationReentryPolicy"},"cancel_on":{"type":"array","minItems":1,"maxItems":10,"items":{"$ref":"#/components/schemas/AutomationCancelOnPolicy"},"description":"Application events that cancel active executions. Each event name must be unique. Cancellation does not undo actions already accepted by a channel."}},"allOf":[{"if":{"required":["cancel_on"],"properties":{"cancel_on":{"contains":{"not":{"required":["scope"],"properties":{"scope":{"const":"automation"}}}}}}},"then":{"required":["reentry"],"properties":{"reentry":{"required":["key"],"properties":{"key":{"$ref":"#/components/schemas/key"}}}}}}]},"AutomationDefinition":{"type":"object","x-go-type-skip-optional-pointer":true,"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AutomationDefinitionEnvelope"},{"type":"object","properties":{"settings":{"$ref":"#/components/schemas/AutomationDefinitionSettings"}}}],"example":{"schema_version":1,"expression_environment":"bird.cel.v1","nodes":[{"id":"profile_requested","type":"trigger.application_event","type_version":1,"config":{"name":"customer.profile_requested","data_schema":{"type":"object","required":["contact_id"],"properties":{"contact_id":{"type":"string","minLength":1}},"additionalProperties":false}}},{"id":"lookup","type":"bird.contacts.get","type_version":1,"config":{},"input":{"contact_id":{"$expr":"trigger.data.data.contact_id"}}},{"id":"result","type":"data.set","type_version":1,"config":{},"input":{"contact_id":{"$expr":"steps.lookup.output.id"}}}],"edges":[{"id":"start_lookup","from":{"node_id":"profile_requested","port":"event"},"to":{"node_id":"lookup","port":"input"}},{"id":"lookup_result","from":{"node_id":"lookup","port":"success"},"to":{"node_id":"result","port":"input"}}]}},"AutomationUseCaseSetupTarget":{"type":"object","additionalProperties":false,"required":["definition_pointer","schema_pointer"],"properties":{"definition_pointer":{"type":"string","minLength":1,"description":"JSON Pointer from the definition root to an input or configuration value. A missing final property can be inserted when its parent exists.","example":"/nodes/1/input/audience_id"},"schema_pointer":{"type":"string","minLength":1,"description":"Physical JSON Pointer on that node's exact descriptor to the schema for the target value. Resolve referenced shapes through the descriptor's schema_resources; its value-source rules remain authoritative.","example":"/input_schema/properties/audience_id"}}},"AutomationUseCaseSetupField":{"type":"object","additionalProperties":false,"required":["key","label","help","targets"],"properties":{"key":{"type":"string","minLength":1,"description":"Name accepted in the configuration request's values map.","example":"sender"},"label":{"type":"string","minLength":1,"example":"SMS sender"},"help":{"type":"string","minLength":1,"description":"What to provide and any use-case-specific requirements."},"resource_kind":{"type":"string","minLength":1,"description":"Optional resource identity for an existing product picker, such as audience or sms_template. Unknown kinds use ordinary schema editing; this grants no resource access.","example":"audience"},"targets":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AutomationUseCaseSetupTarget"},"description":"Locations receiving the same supplied JSON value. Each target names its authoritative node schema."}}},"AutomationUseCaseSetupValues":{"type":"object","additionalProperties":true,"description":"Named setup values copied unchanged to their declared targets. Omitted keys preserve the base value; null, false, zero and empty strings remain distinct. Unknown keys return a field validation error.","example":{"sender":"Bird"}},"AutomationPreviewOutcome":{"type":"string","minLength":1,"enum":["completed","failed","incomplete","invalid"],"description":"Result of the simulated path:\n\n- `completed` means the path ended without an unhandled failure. It does not establish delivery or business success.\n- `failed` means a valid simulated failure selected an unconnected failure port, or an explicit exit chose failed.\n- `incomplete` means the path stopped at a boundary without a sample.\n- `invalid` means definition validation or a visited evaluation failed.\n"},"AutomationUseCasePathStep":{"type":"object","additionalProperties":false,"required":["node_id"],"properties":{"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"port":{"type":"string","minLength":1,"description":"Expected selected port. Omitted when preview stops at an unsampled boundary or finishes at an exit node, neither of which selects an output port.","example":"success"}}},"AutomationUseCaseScenario":{"type":"object","additionalProperties":false,"required":["key","name","description","values","trigger_data","node_samples","expected_outcome","expected_path"],"properties":{"key":{"type":"string","minLength":1,"example":"happy_path"},"name":{"type":"string","minLength":1,"example":"Message accepted"},"description":{"type":"string","minLength":1,"description":"What the synthetic input and hypothetical boundary results demonstrate."},"values":{"$ref":"#/components/schemas/AutomationUseCaseSetupValues"},"trigger_data":{"type":"object","additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Input using the draft preview convention. Application events provide data and optionally occurred_at; other triggers provide their configured output. No sample creates an event or execution."},"node_samples":{"type":"array","items":{"$ref":"#/components/schemas/AutomationPreviewSample"},"description":"Hypothetical results for action, wait and delay nodes, using the ordinary draft preview contract."},"expected_outcome":{"$ref":"#/components/schemas/AutomationPreviewOutcome"},"expected_path":{"type":"array","items":{"$ref":"#/components/schemas/AutomationUseCasePathStep"},"description":"Expected preview steps in visit order. This is a scenario expectation, not an execution trace."}}},"AutomationUseCaseTemplate":{"allOf":[{"$ref":"#/components/schemas/AutomationUseCaseTemplateBase"},{"type":"object","required":["keywords","definition","node_descriptions","setup_fields","prerequisites","scenarios"],"properties":{"keywords":{"type":"array","items":{"type":"string","minLength":1},"description":"Search terms describing this use case."},"definition":{"$ref":"#/components/schemas/AutomationDefinition"},"node_descriptions":{"type":"object","propertyNames":{"$ref":"#/components/schemas/AutomationNodeID"},"additionalProperties":{"type":"string","minLength":1,"maxLength":500},"description":"Workflow-specific plain-text explanation for every node, keyed by its stable ID. Catalogue metadata only; configuration does not copy these explanations into the automation definition.","example":{"check_dispatch":"Checks that the shipment is dispatched and the recipient is eligible. Otherwise, skips the notification."}},"setup_fields":{"type":"array","items":{"$ref":"#/components/schemas/AutomationUseCaseSetupField"},"description":"Ordered configuration questions. Required resource values can be absent from the base definition until these are answered."},"prerequisites":{"type":"array","items":{"type":"string","minLength":1},"description":"Resources, application events and business or consent checks the caller must provide. Configuration does not verify these requirements."},"scenarios":{"type":"array","items":{"$ref":"#/components/schemas/AutomationUseCaseScenario"},"description":"Synthetic configuration and preview inputs with expected paths. These are examples, not real contacts or evidence of resource eligibility."}}}],"unevaluatedProperties":false},"AutomationUseCaseTemplateConfigurationRequest":{"type":"object","additionalProperties":false,"required":["values"],"properties":{"values":{"$ref":"#/components/schemas/AutomationUseCaseSetupValues"}}},"AutomationAuthoringDefinition":{"type":"object","x-go-type-skip-optional-pointer":true,"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AutomationDefinitionEnvelope"},{"type":"object","properties":{"settings":{"type":"object","additionalProperties":true,"description":"Automation-level policies in progress. Incomplete values may be saved; validation returns addressable problems and publication requires valid settings."}}}]},"AutomationAvailableSource":{"type":"object","additionalProperties":false,"required":["expression","types","conditional","uncertain","dynamic_keys"],"properties":{"expression":{"type":"string","minLength":1,"description":"Canonical CEL reference before the selected node. Array suggestions include index zero and its retained fields when their shape is known. Other indices are not expanded. A suggestion does not guarantee that an item exists; check conditional and the parent sources."},"types":{"type":"array","items":{"type":"string"},"description":"Inferred value types. A dynamic type means the declared contract does not determine the type. The resolved descriptor remains the source of complete schema constraints."},"conditional":{"type":"boolean","description":"Whether this path may be absent, an array index may be out of bounds, or a parent may have a different type. Guard parent access and check the array length before indexing. Nullable values also need a null check when the destination does not accept null."},"uncertain":{"type":"boolean","description":"Whether open data or analysis limits prevent a definite type conclusion."},"dynamic_keys":{"type":"boolean","description":"Whether an object permits keys beyond the listed suggestions. Dynamic lookups still require validation and appropriate guards."},"item_types":{"type":"array","items":{"type":"string"},"description":"Possible array element types. Omitted for non-array values or when no common item schema is known."}}},"AutomationScheduleProjection":{"type":"object","additionalProperties":false,"required":["evaluated_at","next_occurrence"],"properties":{"evaluated_at":{"type":"string","format":"date-time","minLength":1,"description":"Server time used to calculate the next occurrence."},"next_occurrence":{"type":"string","format":"date-time","minLength":1,"description":"Next occurrence after evaluated_at, calculated with the automation engine's cron and timezone rules."}}},"AutomationNodeContext":{"type":"object","additionalProperties":false,"required":["node_id","sources","sources_truncated"],"properties":{"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"resolved_descriptor":{"allOf":[{"$ref":"#/components/schemas/AutomationNodeDescriptor"}],"description":"Exact node contract with configuration-derived ports and event schemas resolved for this definition. Omitted when the selected node's configuration cannot yet resolve its contract. Incoming sources may still be available. Use it to inspect this node, not to register a new node type."},"sources":{"type":"array","maxItems":256,"items":{"$ref":"#/components/schemas/AutomationAvailableSource"},"description":"Deterministically ordered, bounded suggestions for retained data available before the node executes. Samples never strengthen these guarantees."},"sources_truncated":{"type":"boolean","description":"Whether the field-count or nesting limit omitted suggestions. Missing suggestions do not imply a field is forbidden; validate the intended expression."},"schedule":{"allOf":[{"$ref":"#/components/schemas/AutomationScheduleProjection"}],"description":"Authoritative next occurrence for a selected, valid schedule trigger. Omitted for other nodes and while the schedule configuration is invalid."}}},"AutomationValidationProblemCode":{"type":"string","minLength":1,"x-extensible-enum":["automation_schema_version_unsupported","automation_expression_environment_unsupported","automation_node_count_invalid","automation_node_id_invalid","automation_node_id_duplicate","automation_node_type_unknown","automation_node_descriptor_invalid","automation_node_config_invalid","automation_node_input_invalid","automation_expression_invalid","automation_expression_reference_invalid","automation_expression_optional_read","automation_expression_type_mismatch","automation_expression_type_uncertain","automation_execution_duration_exceeded","automation_node_runtime_unsupported","automation_settings_unsupported","automation_node_type_deprecated","automation_trigger_count_invalid","automation_edge_id_invalid","automation_edge_id_duplicate","automation_edge_node_unknown","automation_edge_port_unknown","automation_edge_source_duplicate","automation_trigger_has_incoming_edge","automation_cycle_detected","automation_node_unreachable","automation_node_capability_missing","automation_ingress_contract_changed","automation_ingress_compatibility_unknown"],"description":"Stable diagnostic code. New codes may be added without changing the validation response shape."},"AutomationValidationSeverity":{"type":"string","minLength":1,"enum":["error","warning"],"description":"`error` blocks publication. `warning` identifies a condition the runtime will check when the affected path executes.\n"},"AutomationValidationConstraintParameters":{"type":"object","readOnly":true,"additionalProperties":false,"description":"Details established for the failed constraint. Omitted details are unavailable; the problem message and pointer still apply.","properties":{"field_pointers":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string","minLength":1},"description":"Definition-root JSON Pointers to missing fields for required, or unsupported properties for additionalProperties. These are unconditional repairs, not alternative choices."},"required_alternatives":{"type":"array","minItems":1,"maxItems":6,"items":{"type":"array","minItems":1,"maxItems":6,"items":{"type":"string","minLength":1}},"description":"Alternative groups of missing fields, expressed as definition-root JSON Pointers. Supply every field in a chosen group. The keyword determines whether one or more alternatives may match. Other constraints still apply. Omitted unless every failed alternative can be represented."},"expected_types":{"type":"array","minItems":1,"maxItems":7,"items":{"type":"string","minLength":1},"description":"Allowed JSON types for a type constraint."},"format":{"type":"string","minLength":1,"maxLength":1024,"description":"Expected format for a format constraint.","example":"email"},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Item, property, or Unicode character count bound. The keyword determines the unit and whether this is a minimum or maximum.","example":1},"limit":{"type":"string","minLength":1,"maxLength":1024,"description":"Exact numeric bound or multiple, encoded as JSON-number text without rounding. The keyword determines the comparison. Omitted if the bound cannot be represented within the response limits.","example":"9007199254740993.01"},"pattern":{"type":"string","maxLength":1024,"description":"Complete expected regular expression for a pattern constraint. Omitted if it exceeds the response limit."},"reason":{"type":"string","minLength":1,"description":"For oneOf, no_match means no alternative matched; multiple_matches means more than one matched. It does not identify fields to remove."}}},"AutomationValidationConstraint":{"type":"object","readOnly":true,"additionalProperties":false,"required":["keyword","parameters"],"properties":{"keyword":{"type":"string","minLength":1,"description":"JSON Schema keyword that failed, such as required, anyOf, type, or minItems. Use the problem message when the keyword is not recognized.","example":"required"},"parameters":{"$ref":"#/components/schemas/AutomationValidationConstraintParameters"}}},"AutomationValidationProblem":{"type":"object","additionalProperties":false,"required":["code","severity","pointer","message","remediation"],"properties":{"code":{"$ref":"#/components/schemas/AutomationValidationProblemCode"},"severity":{"$ref":"#/components/schemas/AutomationValidationSeverity"},"node_id":{"allOf":[{"$ref":"#/components/schemas/AutomationNodeID"}],"description":"Node related to the problem. Omitted for definition-wide and edge-only problems."},"edge_id":{"allOf":[{"$ref":"#/components/schemas/AutomationEdgeID"}],"description":"Edge related to the problem. Omitted for definition-wide and node-only problems."},"pointer":{"type":"string","minLength":1,"description":"JSON Pointer to the affected value in the definition.","example":"/nodes/2/input/to"},"message":{"type":"string","minLength":1,"description":"Explanation of the problem."},"remediation":{"type":"string","minLength":1,"description":"Specific change that resolves the problem."},"expression_offset":{"type":"integer","minimum":0,"description":"Zero-based Unicode code-point offset in the decoded expression string identified by pointer. For a template segment, the offset starts at that segment's expression, not at the start of the template. Omitted when no precise expression location is available."},"constraint":{"allOf":[{"$ref":"#/components/schemas/AutomationValidationConstraint"}],"description":"Structured details for an authored schema failure. Omitted for other problems or when details cannot be established. Existing code, message, remediation, and pointer remain applicable."}}},"AutomationAdviceCode":{"type":"string","minLength":1,"x-extensible-enum":["literal_interpolation","literal_value_source","email_parameter_missing","unchecked_failure_fallback"],"description":"Authoring suggestions that do not affect validation or publication.\n\n- `literal_interpolation`: Text containing `{{...}}` remains literal.\n- `literal_value_source`: An expression or template object inside literal data is not evaluated.\n- `email_parameter_missing`: Inline email content references a parameter that the step does not provide.\n- `unchecked_failure_fallback`: A message-send failure connects directly to another send. Check that retry or fallback is appropriate; an intentional alert may keep that connection.\n","example":"literal_interpolation"},"AutomationAdvice":{"type":"object","additionalProperties":false,"required":["code","node_id","pointer","message","suggestion"],"properties":{"code":{"$ref":"#/components/schemas/AutomationAdviceCode"},"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"pointer":{"type":"string","minLength":1,"description":"JSON Pointer to the authored value or connection in the definition.","example":"/nodes/1/input/text"},"message":{"type":"string","minLength":1,"description":"Why this part of the definition may deserve another look. It may be intentional."},"suggestion":{"type":"string","minLength":1,"description":"Optional change to consider. No change is applied automatically."}}},"AutomationResourceCheckKind":{"type":"string","minLength":1,"x-extensible-enum":["whatsapp_template"],"description":"The resource assessed by this check. `whatsapp_template` checks the selected template, language and parameter mappings."},"AutomationResourceCheckStatus":{"type":"string","minLength":1,"enum":["compatible","mismatch","unavailable","dynamic"],"description":"- `compatible`: The selected resource resolved and its parameter mappings match. Runtime values and delivery have not been checked.\n- `mismatch`: A confirmed difference between the authored mappings and the selected resource needs attention.\n- `unavailable`: The resource could not be checked, including when access was denied or a lookup failed.\n- `dynamic`: Expressions determine the resource or mapping structure, so compatibility must be checked when the step runs.\n"},"AutomationResourceCheck":{"type":"object","additionalProperties":false,"required":["node_id","pointer","kind","status","code","message"],"properties":{"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"pointer":{"type":"string","minLength":1,"description":"JSON Pointer to the resource reference in the supplied definition.","example":"/nodes/1/input/template"},"kind":{"$ref":"#/components/schemas/AutomationResourceCheckKind"},"status":{"$ref":"#/components/schemas/AutomationResourceCheckStatus"},"code":{"type":"string","minLength":1,"description":"Stable identifier for this assessment. Use `status` to handle an unfamiliar code.","example":"whatsapp_template_parameter_mismatch"},"message":{"type":"string","minLength":1,"description":"What was established about the selected resource and its mappings."},"remediation":{"type":"string","minLength":1,"description":"Suggested action when the check needs attention. No change is applied automatically."}}},"AutomationValidationReport":{"type":"object","additionalProperties":false,"required":["valid","problems","limits"],"properties":{"limits":{"$ref":"#/components/schemas/AutomationRuntimeLimits"},"node_context":{"allOf":[{"$ref":"#/components/schemas/AutomationNodeContext"}],"description":"Incoming sources for the selected node, including when that node's input or configuration is incomplete. Errors still appear in `problems` and keep `valid` false. Omitted when no node was requested or the graph or upstream nodes prevent reliable analysis."},"valid":{"type":"boolean","description":"Whether the definition has no error-severity problems and can proceed to publication checks."},"problems":{"type":"array","items":{"$ref":"#/components/schemas/AutomationValidationProblem"},"description":"Addressable errors and warnings. Validation failures are returned here with `valid` set to false."},"advice":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/AutomationAdvice"},"description":"Optional authoring suggestions, returned only when `include_advice` is true. An empty array means no suggestions were found. Returns up to 100 suggestions. Value suggestions come first in node order, visiting config before input and object fields by name; failure-fallback suggestions follow in edge order. Advice never affects `valid` or blocks publication."},"resource_checks":{"type":"array","items":{"$ref":"#/components/schemas/AutomationResourceCheck"},"description":"WhatsApp template assessments for the supplied definition, returned when `include_resource_checks` is true. An empty array means no applicable template references were found. These results do not affect `valid`, saving or publication and do not establish delivery readiness. Resource availability and parameter values are checked again when a message is sent."}}},"AutomationUseCaseTemplateConfigurationResponse":{"type":"object","additionalProperties":false,"required":["template_id","definition","validation"],"properties":{"template_id":{"$ref":"#/components/schemas/AutomationUseCaseTemplateID"},"definition":{"$ref":"#/components/schemas/AutomationAuthoringDefinition"},"validation":{"$ref":"#/components/schemas/AutomationValidationReport"}}},"AutomationValidateRequest":{"type":"object","additionalProperties":false,"required":["definition"],"properties":{"definition":{"$ref":"#/components/schemas/AutomationAuthoringDefinition"},"include_advice":{"type":"boolean","default":false,"description":"Include optional authoring suggestions in `advice`. Suggestions never affect validity or block publication."},"include_resource_checks":{"type":"boolean","default":false,"description":"Check selected WhatsApp templates and their parameter mappings using your workspace permissions. Results appear in `resource_checks` and do not affect validity or publication."},"node_id":{"allOf":[{"$ref":"#/components/schemas/AutomationNodeID"}],"description":"Request resolved schemas and available data for this node in the supplied definition. An unknown node ID returns a validation error. Omit to receive no node context. Advice, when requested, covers the whole definition."}},"example":{"definition":{"schema_version":1,"expression_environment":"bird.cel.v1","nodes":[{"id":"order_shipped","type":"trigger.application_event","type_version":1,"config":{"name":"order.shipped","data_schema":{"type":"object","additionalProperties":true}}}],"edges":[]}}},"AutomationEvaluationTriggerFilterTarget":{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","minLength":1,"const":"trigger_filter","description":"Evaluates the selected trigger's admission filter against the original event supplied in `trigger_data`."}}},"AutomationEvaluationWaitFilterTarget":{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","minLength":1,"const":"wait_filter","description":"Tests the selected application-event wait filter using `candidate_event` and retained `trigger_data`, without executing upstream nodes."}}},"AutomationEvaluationValueTarget":{"type":"object","additionalProperties":false,"required":["kind","pointer"],"properties":{"kind":{"type":"string","minLength":1,"const":"value","description":"Evaluates one authored value without running the selected node."},"pointer":{"type":"string","minLength":1,"description":"Definition-root JSON Pointer to the complete authored field under the selected node's `config` or `input`."}}},"AutomationEvaluationNodeTarget":{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","minLength":1,"const":"node","description":"Prepares or evaluates the selected node without performing side effects."}}},"AutomationEvaluationTarget":{"description":"Authoring target evaluated for the selected node.","oneOf":[{"$ref":"#/components/schemas/AutomationEvaluationValueTarget"},{"$ref":"#/components/schemas/AutomationEvaluationNodeTarget"},{"$ref":"#/components/schemas/AutomationEvaluationTriggerFilterTarget"},{"$ref":"#/components/schemas/AutomationEvaluationWaitFilterTarget"}],"discriminator":{"propertyName":"kind","mapping":{"value":"#/components/schemas/AutomationEvaluationValueTarget","node":"#/components/schemas/AutomationEvaluationNodeTarget","trigger_filter":"#/components/schemas/AutomationEvaluationTriggerFilterTarget","wait_filter":"#/components/schemas/AutomationEvaluationWaitFilterTarget"}}},"AutomationEvaluationRequest":{"type":"object","additionalProperties":false,"not":{"properties":{"targets":{"minItems":2,"contains":{"oneOf":[{"$ref":"#/components/schemas/AutomationEvaluationTriggerFilterTarget"},{"$ref":"#/components/schemas/AutomationEvaluationWaitFilterTarget"}]}}},"required":["targets"]},"required":["definition","node_id","targets"],"properties":{"definition":{"$ref":"#/components/schemas/AutomationAuthoringDefinition"},"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"targets":{"type":"array","minItems":1,"maxItems":16,"items":{"$ref":"#/components/schemas/AutomationEvaluationTarget"},"description":"Values or node behavior to evaluate, returned in the same order. A `trigger_filter` or `wait_filter` target must be the only target in the request.\n"},"trigger_data":{"type":"object","additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Optional trigger sample used to materialize upstream values. A `trigger_filter` target receives this exact original event rather than a downstream trigger envelope. A `wait_filter` target uses the retained form of this sample; for Bird triggers, it exposes only the original event, without resource enrichment. Omission can make a target unavailable.\n"},"candidate_event":{"type":"object","additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Original candidate application event for a `wait_filter` target, containing `id`, `type`, `occurred_at`, and `data`. It must satisfy the selected wait's event schema and fit within 128 KiB. The filter reads `candidate.event` before sensitive fields are removed from retained data. Omission makes the target unavailable.\n"},"node_samples":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/AutomationPreviewSample"},"description":"Optional hypothetical outcomes for upstream action, wait, and delay nodes. Samples are validated and cannot make an invalid upstream input valid. No action or timer is executed. Omit samples for filter targets."},"client_context":{"type":"string","maxLength":256,"description":"Optional caller-selected correlation value echoed unchanged in the response. The caller is responsible for assigning and interpreting it."}}},"AutomationEvaluationTargetKind":{"type":"string","minLength":1,"enum":["value","node","trigger_filter","wait_filter"],"description":"Authoring behavior to evaluate:\n\n- `value`: Evaluates an authored field.\n- `node`: Prepares or evaluates the selected node.\n- `trigger_filter`: Tests the selected trigger's admission filter against the original event.\n- `wait_filter`: Tests the selected application-event wait filter against a candidate event and retained trigger data.\n"},"AutomationEvaluationTargetStatus":{"type":"string","minLength":1,"enum":["evaluated","prepared","unavailable","invalid","failed"],"description":"Outcome for the requested target:\n\n- `evaluated`: Produced a value, pure-node result, or filter decision.\n- `prepared`: Materialized action, wait, or delay input without performing it.\n- `unavailable`: Required context was absent, a path or condition was not taken, or retention rules prohibit the value.\n- `invalid`: A static authoring problem prevents evaluation.\n- `failed`: Evaluation failed, including when a dynamically produced value violates its destination schema.\n"},"AutomationEvaluationTargetResult":{"type":"object","additionalProperties":false,"required":["kind","status","problems"],"properties":{"kind":{"$ref":"#/components/schemas/AutomationEvaluationTargetKind","description":"Target kind from the corresponding request entry."},"pointer":{"type":"string","minLength":1,"description":"Definition-root JSON Pointer from a value target. Omitted for node and filter targets."},"status":{"$ref":"#/components/schemas/AutomationEvaluationTargetStatus"},"reason":{"type":"string","minLength":1,"description":"Machine-readable classification for a result that did not produce its usual value."},"value_type":{"type":"string","minLength":1,"description":"Resolved type of an evaluated value when the type is available."},"value":{"x-go-type-skip-optional-pointer":true,"description":"Evaluated value. An explicit null is a result; omission means no value was produced."},"input":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Materialized input for a node target. Omitted for other target kinds or when no input was produced."},"output":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Deterministic output for a node target. An explicit null is a result; omission means no output was produced."},"error":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Evaluation error value for a node target. Omitted when evaluation produced no error value."},"selected_port":{"$ref":"#/components/schemas/AutomationPortKey"},"selected_edge_id":{"$ref":"#/components/schemas/AutomationEdgeID"},"problems":{"type":"array","items":{"$ref":"#/components/schemas/AutomationValidationProblem"},"description":"Addressable problems for this target."}}},"AutomationPreviewStepStatus":{"type":"string","minLength":1,"enum":["evaluated","sampled","not_executed","failed"],"description":"Evaluated runs deterministic logic. Sampled consumes a supplied hypothetical outcome. Not executed stops at a boundary without a sample. Failed indicates a preview evaluation problem, distinct from a sampled action failure."},"AutomationPreviewStep":{"type":"object","additionalProperties":false,"required":["node_id","status","input","output","problems"],"properties":{"selected_port":{"type":"string","description":"Output port chosen by evaluation or the supplied sample. Omitted when no outcome was produced."},"selected_edge_id":{"type":"string","description":"Edge followed to the next node. Omitted when evaluation stops or the selected port is unconnected."},"error":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Retained action failure payload or preview evaluation error. Sampled failures populate error instead of output and remain valid scenarios."},"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"status":{"$ref":"#/components/schemas/AutomationPreviewStepStatus"},"input":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Materialized node input. Null when evaluation did not reach the node."},"output":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Retained deterministic or sampled node output. Null for a failure payload or when the node produced no output."},"problems":{"type":"array","items":{"$ref":"#/components/schemas/AutomationValidationProblem"},"description":"Problems found while materializing or evaluating this node."}}},"AutomationEvaluationResult":{"type":"object","additionalProperties":false,"required":["node_id","definition_valid","problems","results","steps"],"properties":{"client_context":{"type":"string","maxLength":256,"description":"Caller-selected correlation value from the request. Omitted when the request did not provide one."},"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"definition_valid":{"type":"boolean","description":"Whether the definition has no error-severity problems. A true value does not guarantee publication or execution will succeed."},"problems":{"type":"array","items":{"$ref":"#/components/schemas/AutomationValidationProblem"},"description":"Definition-wide errors and warnings found while preparing the evaluation."},"results":{"type":"array","minItems":1,"maxItems":16,"items":{"$ref":"#/components/schemas/AutomationEvaluationTargetResult"},"description":"Evaluation results in request target order."},"steps":{"type":"array","items":{"$ref":"#/components/schemas/AutomationPreviewStep"},"description":"Upstream nodes actually traversed to prepare the targets, including sampled, evaluated, and not-executed provenance."}}},"AutomationID":{"type":"string","minLength":1,"pattern":"^atm_[0-9a-hjkmnp-tv-z]{26}$","example":"atm_01krdgeqcxet5s7t44vh8rt9mg"},"Automation":{"type":"object","additionalProperties":false,"required":["id","name","enabled","active_version_id","draft_revision","archived_at","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/AutomationID"},"name":{"type":"string","minLength":1,"maxLength":200,"description":"Name shown when selecting or inspecting the automation.","example":"Order shipped notification"},"description":{"type":"string","maxLength":2000,"description":"Optional explanation of the automation's purpose.","example":"Notifies a customer after an order ships."},"enabled":{"type":"boolean","description":"Whether the active version accepts new production executions."},"active_version_id":{"type":["string","null"],"minLength":1,"pattern":"^atv_[0-9a-hjkmnp-tv-z]{26}$","description":"Active immutable version. Null until the automation is first published.","example":"atv_01krdgeqcxet5s7t44vh8rt9mg"},"draft_revision":{"type":"integer","minimum":1,"description":"Current draft revision to pass as `expected_revision` when changing or publishing the draft."},"archived_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the automation was archived. Null means the automation appears in the default list."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the automation was created."},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the automation or its lifecycle state last changed."}}},"AutomationList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Automation"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AutomationCreateRequest":{"type":"object","additionalProperties":false,"required":["name","definition"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Name shown when selecting or inspecting the automation.","example":"Order shipped notification"},"description":{"type":"string","minLength":0,"maxLength":2000,"default":"","description":"Explanation of the automation's purpose. Defaults to an empty string."},"definition":{"$ref":"#/components/schemas/AutomationAuthoringDefinition"}},"example":{"name":"Order shipped notification","description":"Notifies a customer after an order ships.","definition":{"schema_version":1,"expression_environment":"bird.cel.v1","nodes":[{"id":"order_shipped","type":"trigger.application_event","type_version":1,"config":{"name":"order.shipped","data_schema":{"type":"object","additionalProperties":true}}}],"edges":[]}}},"AutomationDraft":{"type":"object","additionalProperties":false,"required":["automation_id","revision","definition","updated_at"],"properties":{"automation_id":{"$ref":"#/components/schemas/AutomationID"},"revision":{"type":"integer","minimum":1,"description":"Revision to pass as `expected_revision` on the next draft mutation."},"definition":{"$ref":"#/components/schemas/AutomationAuthoringDefinition"},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When this draft revision was saved."}}},"AutomationCreateResult":{"type":"object","additionalProperties":false,"required":["automation","draft"],"properties":{"automation":{"$ref":"#/components/schemas/Automation"},"draft":{"$ref":"#/components/schemas/AutomationDraft"}}},"AutomationExecutionStatus":{"type":"string","minLength":1,"enum":["queued","running","waiting","succeeded","failed","canceled"],"description":"Current execution state. `succeeded`, `failed`, and `canceled` are terminal."},"AutomationExecutionMode":{"type":"string","minLength":1,"enum":["production","test"],"description":"`production` follows normal trigger admission. `test` isolates test executions, which may use an active version or a manually selected draft. Both modes perform real side effects.\n"},"AutomationVersionID":{"type":"string","minLength":1,"pattern":"^atv_[0-9a-hjkmnp-tv-z]{26}$","example":"atv_01krdgeqcxet5s7t44vh8rt9mg"},"AutomationExitReason":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z][a-z0-9_]*$","description":"Authored business outcome from an explicit exit, such as paid or manual_review.","example":"no_matching_contact"},"AutomationExecutionID":{"type":"string","minLength":1,"pattern":"^atx_[0-9a-hjkmnp-tv-z]{26}$","example":"atx_01krdgeqcxet5s7t44vh8rt9mg"},"AutomationTriggerKind":{"type":"string","minLength":1,"x-extensible-enum":["event","schedule","api","webhook","manual"],"description":"How the execution began. Manual test runs use `manual`, independently of the selected trigger node's type."},"AutomationExecutionDetailStatus":{"type":"string","minLength":1,"enum":["live","available","temporarily_unavailable","expired"],"description":"Availability of the execution's detailed data.\n- `live`: detail is available from active execution storage.\n- `available`: terminal detail is available from retained history.\n- `temporarily_unavailable`: retained detail is being prepared or cannot currently be read.\n- `expired`: the detail-retention period ended.\n"},"AutomationExecution":{"type":"object","required":["id","automation_id","version_id","draft_revision","trigger_node_id","mode","trigger_kind","status","current_node_id","cancel_requested","deadline_at","terminal_at","detail_status","detail_expires_at","failure_code","exit_reason","started_by","created_at","updated_at"],"properties":{"id":{"$ref":"#/components/schemas/AutomationExecutionID"},"automation_id":{"$ref":"#/components/schemas/AutomationID"},"version_id":{"type":["string","null"],"pattern":"^atv_[0-9a-hjkmnp-tv-z]{26}$","description":"Immutable version used by the execution. Null for a test run of a mutable draft snapshot."},"draft_revision":{"type":["integer","null"],"minimum":1,"description":"Draft revision snapshotted for a draft test run. Null for an active-version execution."},"trigger_node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"mode":{"$ref":"#/components/schemas/AutomationExecutionMode"},"trigger_kind":{"$ref":"#/components/schemas/AutomationTriggerKind"},"status":{"$ref":"#/components/schemas/AutomationExecutionStatus"},"current_node_id":{"oneOf":[{"$ref":"#/components/schemas/AutomationNodeID"},{"type":"null"}],"description":"Node currently being processed. Null before processing begins and after the execution becomes terminal."},"cancel_requested":{"type":"boolean","description":"Whether cancellation has been requested. An accepted side effect may still settle before execution stops."},"deadline_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Time by which the execution must finish. Null only for retained records created before deadlines were recorded."},"terminal_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When the execution reached a terminal status. Null while it can still advance."},"detail_status":{"$ref":"#/components/schemas/AutomationExecutionDetailStatus"},"detail_expires_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When terminal execution detail expires. Null until the execution becomes terminal."},"failure_code":{"type":["string","null"],"description":"Stable platform failure code. An empty string means retained state proves there was no platform failure; null means metadata is unavailable for an older compacted execution."},"exit_reason":{"type":["string","null"],"description":"Authored business outcome from an explicit exit. An empty string means retained state proves there was no explicit exit; null means metadata is unavailable for an older compacted execution."},"started_by":{"oneOf":[{"$ref":"#/components/schemas/Actor"},{"type":"null"}],"description":"Actor that started a manual execution. Null for event, schedule, API, or webhook ingress."},"created_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the execution was created."},"updated_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When the execution last changed."}}},"AutomationExecutionSummary":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AutomationExecution"}]},"AutomationExecutionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AutomationExecutionSummary"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AutomationStatsBoundary":{"type":"string","minLength":1,"pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2}))?$","description":"Calendar day or RFC 3339 instant identifying a statistics bucket. Hourly response bounds and buckets use UTC (`Z`); the period timezone controls grouping. Request instants may include an explicit offset.","example":"2026-09-11"},"AutomationStatsMode":{"type":"string","enum":["production","test","all"],"default":"production","description":"Execution modes included in the statistics.\n\n- `production` includes production executions across published versions.\n- `test` includes test executions, including draft snapshots unless a version filter excludes them.\n- `all` includes both modes.\n"},"AutomationStatsPeriod":{"type":"object","additionalProperties":false,"readOnly":true,"required":["from","to","timezone","grain","data_as_of"],"properties":{"from":{"$ref":"#/components/schemas/AutomationStatsBoundary","description":"Inclusive first bucket of the normalized reporting period."},"to":{"$ref":"#/components/schemas/AutomationStatsBoundary","description":"Inclusive last bucket of the normalized reporting period."},"timezone":{"type":"string","minLength":1,"description":"IANA timezone used for the period and bucket boundaries.","example":"Europe/Amsterdam"},"grain":{"$ref":"#/components/schemas/StatsGrain"},"data_as_of":{"type":["string","null"],"format":"date-time","description":"Latest reporting freshness boundary. Null when unknown. Coverage describes whether the period is available and complete.","example":"2026-09-11T12:00:00Z"}}},"AutomationStatsCoverageStatus":{"type":"string","minLength":1,"enum":["unavailable","partial","provisional","complete"],"description":"Availability and completeness of the requested statistics.\n\n- `unavailable` means no part of the requested period has reporting coverage.\n- `partial` means coverage excludes part of the requested period. Included execution facts may also be missing or change; partial coverage does not imply settled data.\n- `provisional` means the period has coverage, but execution facts can still be missing or change.\n- `complete` means the period has coverage and its execution facts are settled.\n"},"AutomationStatsCoverage":{"type":"object","additionalProperties":false,"readOnly":true,"required":["status","available_from","available_to"],"properties":{"status":{"$ref":"#/components/schemas/AutomationStatsCoverageStatus"},"available_from":{"type":["string","null"],"format":"date-time","description":"Inclusive start of reporting coverage. Null when coverage is unavailable.","example":"2026-09-01T00:00:00Z"},"available_to":{"type":["string","null"],"format":"date-time","description":"Exclusive end of reporting coverage. Null when coverage is unavailable.","example":"2026-09-11T12:00:00Z"}}},"AutomationStatsCounts":{"type":"object","additionalProperties":false,"readOnly":true,"required":["started","queued","running","waiting","succeeded","failed","canceled","in_progress","provisional"],"properties":{"started":{"type":"integer","format":"int64","minimum":0,"description":"Distinct executions admitted during the selected period.","example":100},"queued":{"type":"integer","format":"int64","minimum":0,"description":"Selected executions awaiting processing.","example":0},"running":{"type":"integer","format":"int64","minimum":0,"description":"Selected executions currently processing.","example":0},"waiting":{"type":"integer","format":"int64","minimum":0,"description":"Selected executions suspended for a delay or event.","example":10},"succeeded":{"type":"integer","format":"int64","minimum":0,"description":"Selected executions that finished successfully, including handled node failures.","example":80},"failed":{"type":"integer","format":"int64","minimum":0,"description":"Selected executions that ended in failure.","example":5},"canceled":{"type":"integer","format":"int64","minimum":0,"description":"Selected executions that were canceled.","example":5},"in_progress":{"type":"integer","format":"int64","minimum":0,"description":"Sum of queued, running, and waiting executions in the selected period.","example":10},"provisional":{"type":"integer","format":"int64","minimum":0,"description":"Selected executions whose reporting facts are not settled.","example":10}}},"AutomationStatsRate":{"type":"object","additionalProperties":false,"readOnly":true,"required":["numerator","denominator","value"],"properties":{"numerator":{"type":"integer","format":"int64","minimum":0,"description":"Count of observations satisfying the metric.","example":80},"denominator":{"type":"integer","format":"int64","minimum":0,"description":"Count of observations eligible for the metric.","example":100},"value":{"type":["number","null"],"format":"double","minimum":0,"maximum":1,"description":"Numerator divided by denominator. Null when the denominator is zero.","example":0.8}}},"AutomationStatsDuration":{"type":"object","additionalProperties":false,"readOnly":true,"required":["samples","p50_ms","p95_ms","p99_ms"],"properties":{"samples":{"type":"integer","format":"int64","minimum":0,"description":"Completed observations included in the duration distribution.","example":90},"p50_ms":{"type":["number","null"],"format":"double","minimum":0,"description":"Median elapsed duration in milliseconds. Null when no completed observations are available.","example":1000},"p95_ms":{"type":["number","null"],"format":"double","minimum":0,"description":"95th-percentile elapsed duration in milliseconds. Null when no completed observations are available.","example":5000},"p99_ms":{"type":["number","null"],"format":"double","minimum":0,"description":"99th-percentile elapsed duration in milliseconds. Null when no completed observations are available.","example":8000}}},"AutomationStatsTotals":{"type":"object","additionalProperties":false,"readOnly":true,"required":["counts","completion_rate","success_rate","failure_rate","cancellation_rate","elapsed"],"properties":{"counts":{"$ref":"#/components/schemas/AutomationStatsCounts"},"completion_rate":{"$ref":"#/components/schemas/AutomationStatsRate","description":"Succeeded, failed, and canceled executions divided by started executions."},"success_rate":{"$ref":"#/components/schemas/AutomationStatsRate","description":"Succeeded executions divided by succeeded plus failed executions. Canceled and unfinished executions are excluded."},"failure_rate":{"$ref":"#/components/schemas/AutomationStatsRate","description":"Failed executions divided by succeeded plus failed executions. Canceled and unfinished executions are excluded."},"cancellation_rate":{"$ref":"#/components/schemas/AutomationStatsRate","description":"Canceled executions divided by started executions."},"elapsed":{"$ref":"#/components/schemas/AutomationStatsDuration","description":"Admission-to-terminal duration across completed executions, including queueing, retries, and intentional waits."}}},"AutomationStatsComparisonDelta":{"type":"object","additionalProperties":false,"readOnly":true,"description":"Changes from the previous period. A `*_pct_change` value is the signed\nrelative change `(current - previous) / previous`, null when the previous\ncount is zero. A `*_rate_pp` value is the signed difference between rate\nfractions, null when either rate is undefined; multiply by 100 for percentage points.\n","required":["started_pct_change","queued_pct_change","running_pct_change","waiting_pct_change","succeeded_pct_change","failed_pct_change","canceled_pct_change","in_progress_pct_change","completion_rate_pp","success_rate_pp","failure_rate_pp","cancellation_rate_pp"],"properties":{"started_pct_change":{"type":["number","null"],"format":"double","description":"Relative change in started executions.","example":0.5},"queued_pct_change":{"type":["number","null"],"format":"double","description":"Relative change in executions currently queued.","example":-0.1},"running_pct_change":{"type":["number","null"],"format":"double","description":"Relative change in executions currently running.","example":0.25},"waiting_pct_change":{"type":["number","null"],"format":"double","description":"Relative change in executions currently waiting.","example":0.1},"succeeded_pct_change":{"type":["number","null"],"format":"double","description":"Relative change in succeeded executions.","example":0.5},"failed_pct_change":{"type":["number","null"],"format":"double","description":"Relative change in failed executions.","example":-0.2},"canceled_pct_change":{"type":["number","null"],"format":"double","description":"Relative change in canceled executions.","example":0},"in_progress_pct_change":{"type":["number","null"],"format":"double","description":"Relative change in executions currently queued, running, or waiting.","example":0.1},"completion_rate_pp":{"type":["number","null"],"format":"double","minimum":-1,"maximum":1,"description":"Difference between completion-rate fractions.","example":0.02},"success_rate_pp":{"type":["number","null"],"format":"double","minimum":-1,"maximum":1,"description":"Difference between success-rate fractions.","example":0.02},"failure_rate_pp":{"type":["number","null"],"format":"double","minimum":-1,"maximum":1,"description":"Difference between failure-rate fractions.","example":-0.02},"cancellation_rate_pp":{"type":["number","null"],"format":"double","minimum":-1,"maximum":1,"description":"Difference between cancellation-rate fractions.","example":0}}},"AutomationStatsComparison":{"type":"object","additionalProperties":false,"readOnly":true,"required":["period","coverage","summary","delta"],"properties":{"period":{"$ref":"#/components/schemas/AutomationStatsPeriod"},"coverage":{"$ref":"#/components/schemas/AutomationStatsCoverage"},"summary":{"description":"Summary for the preceding equal-length period with the same filters. Null when that period is unavailable.","oneOf":[{"$ref":"#/components/schemas/AutomationStatsTotals"},{"type":"null"}]},"delta":{"description":"Changes from the preceding period. Null when either period has unavailable or partial coverage. Values from provisional periods remain provisional.","oneOf":[{"$ref":"#/components/schemas/AutomationStatsComparisonDelta"},{"type":"null"}]}}},"AutomationStatsSummary":{"type":"object","additionalProperties":false,"readOnly":true,"required":["period","coverage","summary"],"properties":{"period":{"$ref":"#/components/schemas/AutomationStatsPeriod"},"coverage":{"$ref":"#/components/schemas/AutomationStatsCoverage"},"summary":{"description":"Statistics for the covered portion of the requested period. Null when the whole period is unavailable.","oneOf":[{"$ref":"#/components/schemas/AutomationStatsTotals"},{"type":"null"}]},"comparison":{"$ref":"#/components/schemas/AutomationStatsComparison","description":"Present only when `compare=previous_period` is requested."}}},"AutomationStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"required":["bucket","counts"],"properties":{"bucket":{"$ref":"#/components/schemas/AutomationStatsBoundary","description":"Local calendar day or RFC 3339 hour boundary for the point."},"counts":{"description":"Execution counts for this bucket. Null when coverage is unavailable; covered buckets without activity contain zero counts.","oneOf":[{"$ref":"#/components/schemas/AutomationStatsCounts"},{"type":"null"}]}}},"AutomationStatsResponse":{"type":"object","additionalProperties":false,"readOnly":true,"required":["period","coverage","data"],"properties":{"period":{"$ref":"#/components/schemas/AutomationStatsPeriod"},"coverage":{"$ref":"#/components/schemas/AutomationStatsCoverage"},"data":{"type":"array","description":"One point per requested bucket in chronological order, including buckets with unavailable counts.","items":{"$ref":"#/components/schemas/AutomationStatsPoint"}}}},"AutomationStatsSortMetric":{"type":"string","enum":["started","queued","running","waiting","succeeded","failed","canceled","in_progress"],"description":"Execution count used to rank breakdown rows in descending order."},"AutomationStatsAutomationPoint":{"type":"object","additionalProperties":false,"readOnly":true,"required":["automation_id","summary"],"properties":{"automation_id":{"$ref":"#/components/schemas/AutomationID","description":"Automation whose executions are summarized."},"summary":{"$ref":"#/components/schemas/AutomationStatsTotals"},"trend":{"type":"array","description":"Per-bucket counts, present only when `include_trend=true`. Unavailable buckets have null counts.","items":{"$ref":"#/components/schemas/AutomationStatsPoint"}}}},"AutomationStatsByAutomationResponse":{"type":"object","additionalProperties":false,"readOnly":true,"required":["period","coverage","data","total"],"properties":{"period":{"$ref":"#/components/schemas/AutomationStatsPeriod"},"coverage":{"$ref":"#/components/schemas/AutomationStatsCoverage"},"data":{"type":["array","null"],"description":"Ranked breakdown rows. Null when the whole period is unavailable; an empty array means the covered period has no matching activity.","items":{"$ref":"#/components/schemas/AutomationStatsAutomationPoint"}},"total":{"type":["integer","null"],"format":"int64","minimum":0,"description":"Number of matching groups before the row limit. Null when the whole period is unavailable.","example":2}}},"AutomationErrorCodeStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"required":["error_code","summary"],"properties":{"error_code":{"type":["string","null"],"minLength":1,"example":"automation_node_output_invalid","description":"Stable code for a terminal execution failure, or null when no code was retained."},"summary":{"$ref":"#/components/schemas/AutomationStatsTotals"},"trend":{"type":"array","description":"Per-bucket counts, present only when `include_trend=true`. Unavailable buckets have null counts.","items":{"$ref":"#/components/schemas/AutomationStatsPoint"}}}},"AutomationStatsByErrorCodeResponse":{"type":"object","additionalProperties":false,"readOnly":true,"required":["period","coverage","data","total"],"properties":{"period":{"$ref":"#/components/schemas/AutomationStatsPeriod"},"coverage":{"$ref":"#/components/schemas/AutomationStatsCoverage"},"data":{"type":["array","null"],"description":"Ranked breakdown rows. Null when the whole period is unavailable; an empty array means the covered period has no matching activity.","items":{"$ref":"#/components/schemas/AutomationErrorCodeStatsPoint"}},"total":{"type":["integer","null"],"format":"int64","minimum":0,"description":"Number of matching groups before the row limit. Null when the whole period is unavailable.","example":2}}},"AutomationExitReasonStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"required":["reason","summary"],"properties":{"reason":{"$ref":"#/components/schemas/AutomationExitReason"},"summary":{"$ref":"#/components/schemas/AutomationStatsTotals"},"trend":{"type":"array","description":"Per-bucket counts, present only when include_trend is true. Unavailable buckets have null counts.","items":{"$ref":"#/components/schemas/AutomationStatsPoint"}}}},"AutomationStatsByExitReasonResponse":{"type":"object","additionalProperties":false,"readOnly":true,"required":["period","coverage","data","total"],"properties":{"period":{"$ref":"#/components/schemas/AutomationStatsPeriod"},"coverage":{"$ref":"#/components/schemas/AutomationStatsCoverage"},"data":{"type":["array","null"],"description":"Ranked exit reasons. Null when the whole period is unavailable; an empty array means the covered period has no explicit exits.","items":{"$ref":"#/components/schemas/AutomationExitReasonStatsPoint"}},"total":{"type":["integer","null"],"format":"int64","minimum":0,"description":"Number of matching reasons before the row limit. Null when the whole period is unavailable.","example":2}}},"AutomationVersionStatsPoint":{"type":"object","additionalProperties":false,"readOnly":true,"required":["version_id","summary"],"properties":{"version_id":{"$ref":"#/components/schemas/AutomationVersionID","description":"Immutable published version whose executions are summarized."},"summary":{"$ref":"#/components/schemas/AutomationStatsTotals"},"trend":{"type":"array","description":"Per-bucket counts, present only when `include_trend=true`. Unavailable buckets have null counts.","items":{"$ref":"#/components/schemas/AutomationStatsPoint"}}}},"AutomationStatsByVersionResponse":{"type":"object","additionalProperties":false,"readOnly":true,"required":["period","coverage","data","total"],"properties":{"period":{"$ref":"#/components/schemas/AutomationStatsPeriod"},"coverage":{"$ref":"#/components/schemas/AutomationStatsCoverage"},"data":{"type":["array","null"],"description":"Ranked breakdown rows. Null when the whole period is unavailable; an empty array means the covered period has no matching activity.","items":{"$ref":"#/components/schemas/AutomationVersionStatsPoint"}},"total":{"type":["integer","null"],"format":"int64","minimum":0,"description":"Number of matching groups before the row limit. Null when the whole period is unavailable.","example":2}}},"AutomationUpdateRequest":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Replacement name for the automation."},"description":{"type":"string","minLength":0,"maxLength":2000,"description":"Replacement explanation of the automation's purpose. Send an empty string to clear it."}},"example":{"name":"Order shipped notifications"}},"AutomationDuplicateRequest":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Name for the new automation. Omit to keep the source automation's name.","example":"Order shipped notification copy"}}},"AutomationDraftUpdateRequest":{"type":"object","additionalProperties":false,"required":["expected_revision","definition"],"properties":{"expected_revision":{"type":"integer","minimum":1,"example":1,"description":"Current draft revision. A stale revision returns `409` without changing the draft."},"definition":{"$ref":"#/components/schemas/AutomationAuthoringDefinition"}}},"AutomationNodeUpgradeTarget":{"type":"object","additionalProperties":false,"required":["node_id","version"],"properties":{"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"version":{"type":"integer","minimum":2,"example":2,"description":"Exact newer node-type version to reach by following each declared upgrade hop in order."}}},"AutomationDraftUpgradeRequest":{"type":"object","additionalProperties":false,"required":["expected_revision","targets"],"properties":{"expected_revision":{"type":"integer","minimum":1,"example":1,"description":"Current draft revision. A stale revision returns `409` without changing the draft."},"targets":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/AutomationNodeUpgradeTarget"},"description":"Nodes and exact versions to upgrade transactionally."}}},"AutomationAppliedNodeUpgrade":{"type":"object","additionalProperties":false,"required":["node_id","type","from_version","to_version","mode"],"properties":{"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"type":{"$ref":"#/components/schemas/AutomationNodeType"},"from_version":{"type":"integer","minimum":1,"description":"Node-type version before this hop."},"to_version":{"type":"integer","minimum":2,"description":"Node-type version after this hop."},"mode":{"$ref":"#/components/schemas/AutomationNodeUpgradeMode"}}},"AutomationDraftUpgradeResult":{"type":"object","additionalProperties":false,"required":["draft","applied","problems"],"properties":{"draft":{"$ref":"#/components/schemas/AutomationDraft"},"applied":{"type":"array","items":{"$ref":"#/components/schemas/AutomationAppliedNodeUpgrade"},"description":"Every version hop applied, in execution order."},"problems":{"type":"array","items":{"$ref":"#/components/schemas/AutomationValidationProblem"},"description":"Errors and warnings found in the upgraded draft. Drafts may be saved with error-severity problems."}}},"AutomationExecutionExit":{"type":"object","additionalProperties":false,"readOnly":true,"required":["node_id","reason"],"properties":{"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"reason":{"$ref":"#/components/schemas/AutomationExitReason"}}},"AutomationPreviewResult":{"type":"object","additionalProperties":false,"required":["valid","steps","problems","revision","outcome","unused_sample_node_ids","output","exit"],"properties":{"output":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Sanitized terminal output for the previewed path, including a deliberately failed exit. Null when the preview has not reached a terminal result or produced no output."},"exit":{"oneOf":[{"$ref":"#/components/schemas/AutomationExecutionExit"},{"type":"null"}],"description":"Authored exit reached on the previewed path. Null when no explicit exit was evaluated."},"revision":{"type":"integer","minimum":1,"description":"Draft revision used for this preview."},"outcome":{"$ref":"#/components/schemas/AutomationPreviewOutcome"},"unused_sample_node_ids":{"type":"array","items":{"$ref":"#/components/schemas/AutomationNodeID"},"description":"Supplied samples not reached on this path, in request order. This includes samples beyond an unsampled boundary; it does not imply those nodes can never execute."},"valid":{"type":"boolean","description":"Whether validation and preview evaluation succeeded. A valid sampled action failure or explicitly failed exit can produce a failed outcome without making this false."},"steps":{"type":"array","items":{"$ref":"#/components/schemas/AutomationPreviewStep"},"description":"Traversed nodes in execution order. Preview stops at an action, wait, or delay without a sample. Retained trace data is bounded to 1 MiB; exceeding the budget returns 422 rather than truncating data."},"problems":{"type":"array","items":{"$ref":"#/components/schemas/AutomationValidationProblem"},"description":"Definition-wide preview errors and warnings."}}},"AutomationPublishRequest":{"type":"object","additionalProperties":false,"required":["expected_revision"],"properties":{"expected_revision":{"type":"integer","minimum":1,"example":1,"description":"Current draft revision. A stale revision returns `409` without changing the automation."},"confirm_ingress_changes":{"type":"boolean","default":false,"description":"Confirm a reported restrictive or uncertain change to the active application-event contract."},"description":{"type":"string","maxLength":2000,"pattern":"^[^\\x00]*$","example":"Use the French reply template and handle failed contact lookups.","description":"Publication note saved with the new version. Immutable after publication. Omit it to publish without a note."}}},"AutomationVersionSummary":{"type":"object","required":["id","automation_id","version_number","description","required_permissions","requires_preview","published_by","published_at"],"properties":{"id":{"$ref":"#/components/schemas/AutomationVersionID"},"automation_id":{"$ref":"#/components/schemas/AutomationID"},"version_number":{"type":"integer","minimum":1,"description":"Monotonically increasing publication number within the automation."},"description":{"type":["string","null"],"maxLength":2000,"description":"Immutable publication note. Null when no note was supplied. Rollback preserves the original note."},"required_permissions":{"type":"array","uniqueItems":true,"items":{"$ref":"#/components/schemas/AutomationPermission"},"description":"Workspace permissions frozen with this version and checked when it is activated or run from a draft."},"requires_preview":{"type":"boolean","description":"Whether this version contains a node type that requires workspace preview access."},"published_by":{"$ref":"#/components/schemas/Actor"},"published_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When this immutable version was first published."}}},"AutomationVersion":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AutomationVersionSummary"},{"type":"object","required":["definition"],"properties":{"definition":{"$ref":"#/components/schemas/AutomationDefinition"}}}]},"AutomationLifecycleResult":{"type":"object","additionalProperties":false,"required":["automation","draft","version","problems"],"properties":{"automation":{"$ref":"#/components/schemas/Automation"},"draft":{"$ref":"#/components/schemas/AutomationDraft"},"version":{"oneOf":[{"$ref":"#/components/schemas/AutomationVersion"},{"type":"null"}],"description":"Published or restored version. Null when a reported problem blocks the lifecycle change."},"problems":{"type":"array","items":{"$ref":"#/components/schemas/AutomationValidationProblem"},"description":"Validation and ingress-compatibility findings. Blocking findings leave `version` null."}}},"AutomationVersionListItem":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AutomationVersionSummary"}]},"AutomationVersionList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AutomationVersionListItem"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]},"AutomationLifecycleRequest":{"type":"object","additionalProperties":false,"required":["expected_revision"],"properties":{"expected_revision":{"type":"integer","minimum":1,"example":1,"description":"Current draft revision. A stale revision returns `409` without changing the automation."},"confirm_ingress_changes":{"type":"boolean","default":false,"description":"Confirm a reported restrictive or uncertain change to the active application-event contract."}}},"AutomationExecutionVersionSelector":{"type":"string","minLength":1,"enum":["active","draft"],"description":"Automation version to run. Production mode accepts only `active`; test mode also accepts `draft`."},"AutomationExecutionCreateRequest":{"type":"object","additionalProperties":false,"required":["version","mode","trigger_node_id","trigger_data"],"properties":{"version":{"$ref":"#/components/schemas/AutomationExecutionVersionSelector"},"expected_revision":{"type":"integer","minimum":1,"description":"Draft revision to execute. Required when `version` is `draft` and rejected when `version` is `active`; a stale revision returns `409`."},"mode":{"$ref":"#/components/schemas/AutomationExecutionMode"},"trigger_node_id":{"$ref":"#/components/schemas/AutomationNodeID","example":"order_shipped"},"trigger_data":{"type":"object","additionalProperties":true,"x-go-type-skip-optional-pointer":true,"example":{"data":{"order_id":"ord_123"}},"description":"Trigger sample validated before the execution is created. For `trigger.application_event`, send an object with `data` and optional `occurred_at`; the service supplies the event ID and configured type. For `trigger.bird_event` in test mode, send the original public webhook event; the worker enriches it before downstream nodes run. Other trigger types in test mode accept their complete output shape.\n"}}},"AutomationExecutionSkipped":{"type":"object","additionalProperties":false,"required":["outcome"],"properties":{"outcome":{"type":"string","minLength":1,"const":"skipped_active","description":"An execution already occupies the configured business key in this mode. No execution or billable run was created."}}},"AutomationApplicationEventRequest":{"type":"object","additionalProperties":false,"required":["id","type","data"],"properties":{"id":{"type":"string","minLength":1,"maxLength":128,"pattern":"\\S","description":"Caller-supplied event identity, retained as provenance. Use Idempotency-Key for request retries.","example":"payment_123"},"type":{"type":"string","minLength":3,"maxLength":128,"pattern":"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$","description":"Name declared by the automation's application-event trigger, wait or cancellation policy.","example":"invoice.paid"},"occurred_at":{"type":"string","format":"date-time","description":"Event time supplied by the caller. Defaults to intake time and does not change wait deadlines."},"subject_key":{"type":"string","minLength":1,"maxLength":256,"pattern":"\\S","description":"Exact active business key. Required for waits and business-key cancellation. Optional for triggers and automation-scoped cancellation.","example":"invoice_123"},"data":{"type":"object","additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Payload validated independently against each consumer's pinned schema.","example":{"receipt_id":"receipt_456"}}}},"AutomationApplicationEventTriggerStatus":{"type":"string","minLength":1,"enum":["started","skipped_active","not_subscribed","inactive","invalid_data","key_mismatch"],"description":"Outcome of admission against the active published version. Only started creates a run."},"AutomationApplicationEventTriggerResult":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"$ref":"#/components/schemas/AutomationApplicationEventTriggerStatus","readOnly":true},"execution_id":{"$ref":"#/components/schemas/AutomationExecutionID","readOnly":true,"description":"Present only when a run was started."}}},"AutomationApplicationEventConsumerStatus":{"type":"string","minLength":1,"enum":["captured","already_captured","cancellation_requested","key_required","no_target","not_subscribed","inactive","invalid_data","payload_too_large","capture_limit","filtered","filter_error"],"description":"Outcome for an existing keyed run. Captured means retained for a declared wait, not that the wait has completed. Already captured preserves the first event. Capture limit means the run's retained application events reached their size budget. Filtered means no wait selected the candidate. Filter error means a filter could not be evaluated. The per-wait outcomes report mixed results; this summary prioritizes captured, already captured, size limits, filter errors, then filtered.\n"},"AutomationApplicationEventWaitStatus":{"type":"string","minLength":1,"enum":["captured","already_captured","filtered","filter_error","payload_too_large","capture_limit"],"description":"Outcome for one declared application-event wait. Filtered and filter error leave the wait open. Already captured preserves its first accepted event. Capture limit rejects all new matching captures in this request for the run.\n"},"AutomationApplicationEventWaitResult":{"type":"object","additionalProperties":false,"required":["node_id","status","problems"],"properties":{"node_id":{"$ref":"#/components/schemas/AutomationNodeID","readOnly":true},"status":{"$ref":"#/components/schemas/AutomationApplicationEventWaitStatus","readOnly":true},"problems":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AutomationValidationProblem"},"description":"Addressable filter evaluation problems. Empty for other outcomes."}}},"AutomationApplicationEventConsumerResult":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"$ref":"#/components/schemas/AutomationApplicationEventConsumerStatus","readOnly":true},"execution_id":{"$ref":"#/components/schemas/AutomationExecutionID","readOnly":true,"description":"Present when an existing keyed run was found."},"waits":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AutomationApplicationEventWaitResult"},"description":"Outcomes for declared waits, in definition order. A valid match can be captured while another wait reports a filter error and remains open.\n"}}},"AutomationApplicationEventCancellationsResult":{"type":"object","additionalProperties":false,"required":["requested","filtered","filter_errors","invalid_data"],"description":"Counts for cancellation policies considered across existing runs. Omitted when no cancellation policy matched the event name and scope. Each run uses its pinned policy and payload schema.","properties":{"requested":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Runs for which cancellation was requested."},"filtered":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Runs left active because the cancellation filter returned false."},"filter_errors":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Runs left active because the cancellation filter could not return a boolean."},"invalid_data":{"type":"integer","format":"int64","minimum":0,"readOnly":true,"description":"Runs whose cancellation payload schema rejected the event data."}}},"AutomationApplicationEventResult":{"type":"object","additionalProperties":false,"required":["event_id","received_at","trigger","consumer"],"properties":{"event_id":{"type":"string","minLength":1,"maxLength":128,"readOnly":true,"description":"Caller-supplied identity of the received event."},"received_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"Intake time used for event ordering and wait deadlines."},"trigger":{"$ref":"#/components/schemas/AutomationApplicationEventTriggerResult","readOnly":true},"consumer":{"$ref":"#/components/schemas/AutomationApplicationEventConsumerResult","readOnly":true},"cancellations":{"$ref":"#/components/schemas/AutomationApplicationEventCancellationsResult","readOnly":true}}},"AutomationExecutionCancellation":{"type":"object","additionalProperties":false,"readOnly":true,"required":["reason","event_id","event_type"],"properties":{"reason":{"$ref":"#/components/schemas/AutomationCancellationReason"},"event_id":{"type":"string","minLength":1,"maxLength":128,"description":"Caller-provided identifier of the application event that requested cancellation.","example":"cancellation_123"},"event_type":{"type":"string","minLength":3,"maxLength":128,"pattern":"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$","description":"Customer event name that matched the cancellation policy.","example":"invoice.voided"}}},"AutomationExecutionPresentationNode":{"type":"object","additionalProperties":false,"required":["id","type","type_version","display_name","label"],"properties":{"id":{"$ref":"#/components/schemas/AutomationNodeID"},"type":{"$ref":"#/components/schemas/AutomationNodeType"},"type_version":{"type":"integer","minimum":1,"description":"Exact node version stored in the pinned definition."},"display_name":{"type":"string","minLength":1,"description":"Current catalogue title for this node type, falling back to the type string when unavailable. Not an admission-time snapshot.","example":"Send SMS"},"label":{"type":["string","null"],"description":"Customer label from presentation.nodes[id].label in the pinned definition. Null when no string label was supplied.","example":"Confirm the appointment"}}},"AutomationExecutionPresentation":{"type":"object","additionalProperties":false,"required":["nodes","edges"],"properties":{"nodes":{"type":"array","items":{"$ref":"#/components/schemas/AutomationExecutionPresentationNode"},"description":"Nodes in the pinned definition, including nodes that this execution did not visit. Contains no authored input or configuration."},"edges":{"type":"array","items":{"$ref":"#/components/schemas/AutomationEdge"},"description":"Connections in the pinned definition, not a trace of the path taken by this execution."}}},"AutomationExecutionFailure":{"type":"object","additionalProperties":false,"required":["code","category","node_id","message"],"properties":{"code":{"type":"string","minLength":1,"description":"Stable platform failure code.","example":"automation_execution_deadline_exceeded"},"category":{"type":"string","minLength":1,"description":"Runtime failure category used to decide whether work can be retried.","example":"timeout"},"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"port":{"allOf":[{"$ref":"#/components/schemas/AutomationPortKey"}],"description":"Output port involved in the failure, when one was selected."},"message":{"type":"string","minLength":1,"description":"Explanation of why the execution stopped.","example":"The execution deadline was reached."}}},"AutomationExecutionDetail":{"unevaluatedProperties":false,"allOf":[{"$ref":"#/components/schemas/AutomationExecution"},{"type":"object","required":["trigger_data","output","failure","presentation","exit","cancellation","wait_until"],"properties":{"exit":{"oneOf":[{"$ref":"#/components/schemas/AutomationExecutionExit"},{"type":"null"}],"description":"Authored exit that ended the execution. Its returned data is in output, including for a failed exit. Null for ordinary completion, platform failure, cancellation or unavailable detail."},"cancellation":{"oneOf":[{"$ref":"#/components/schemas/AutomationExecutionCancellation"},{"type":"null"}],"description":"Application event that canceled the execution. Null unless a declared cancellation event ended the execution or detailed data is unavailable."},"wait_until":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"Due time for the current delay, or timeout for the current event wait. Null when the execution is not waiting or its current wait has no retained deadline. This is not a promised execution time."},"presentation":{"readOnly":true,"oneOf":[{"$ref":"#/components/schemas/AutomationExecutionPresentation"},{"type":"null"}],"description":"Node labels and connections from the retained version or draft snapshot that ran. Null when that definition is absent or execution detail has expired. Never reconstructed from the current draft."},"trigger_data":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Sanitized trigger output. Null before a Bird-event snapshot is committed, or when detailed data is unavailable or expired."},"output":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Sanitized terminal output. Null while the execution is active, when it produced no output, or when detail is unavailable."},"failure":{"oneOf":[{"$ref":"#/components/schemas/AutomationExecutionFailure"},{"type":"null"}],"description":"Platform failure that stopped the execution. Null when no platform failure occurred or detail is unavailable."}}}]},"AutomationExecutionStepStatus":{"type":"string","minLength":1,"enum":["processing","succeeded","failed","skipped"],"description":"Current or terminal status of one node occurrence."},"AutomationExecutionStep":{"type":"object","additionalProperties":false,"required":["node_id","occurrence","type","type_version","status","input","output","error","selected_port","selected_edge_id","started_at","completed_at"],"properties":{"node_id":{"$ref":"#/components/schemas/AutomationNodeID"},"occurrence":{"type":"integer","minimum":1,"description":"One-based occurrence number for repeated visits to the same node."},"type":{"$ref":"#/components/schemas/AutomationNodeType"},"type_version":{"type":"integer","minimum":1,"description":"Exact node contract used for this occurrence."},"status":{"$ref":"#/components/schemas/AutomationExecutionStepStatus"},"input":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Sanitized materialized input. Null when detail is unavailable or the node did not materialize input."},"output":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Sanitized node output. Null when no output was produced or detail is unavailable."},"error":{"type":["object","null"],"additionalProperties":true,"x-go-type-skip-optional-pointer":true,"description":"Sanitized structured node error. Null when the node did not fail or detail is unavailable."},"selected_port":{"oneOf":[{"$ref":"#/components/schemas/AutomationPortKey"},{"type":"null"}],"description":"Output port selected after evaluation. Null until the occurrence settles or when none was selected."},"selected_edge_id":{"oneOf":[{"$ref":"#/components/schemas/AutomationEdgeID"},{"type":"null"}],"description":"Edge followed from the selected port. Null when the port has no connected edge."},"started_at":{"type":"string","minLength":1,"format":"date-time","readOnly":true,"description":"When processing of this occurrence began."},"completed_at":{"type":["string","null"],"format":"date-time","readOnly":true,"description":"When this occurrence settled. Null while it is processing."}}},"AutomationExecutionStepList":{"allOf":[{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AutomationExecutionStep"}}}},{"$ref":"#/components/schemas/_ListEnvelope"}]}},"responses":{"Unprocessable":{"description":"The request has invalid field values, violates a business rule, or carries a query parameter the endpoint does not declare. Field validation errors use `type: validation_error` and include the affected fields in `details`. Business-rule errors identify the failed rule in `type`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"Insufficient permissions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"Resource conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Gone":{"description":"The resource existed but is no longer available.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServiceUnavailable":{"description":"The service is temporarily unavailable. If `Retry-After` is present, wait for that delay before retrying; otherwise, retry with exponential backoff. Reuse the same idempotency key and request when retrying a mutation.\n","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"MisdirectedRequest":{"description":"The request reached the wrong regional endpoint. The organization or API key belongs to a different region; reissue the request against the regional host named in the error message.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"Insufficient balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PayloadTooLarge":{"description":"Request body or message size exceeds the allowed limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooEarly":{"description":"The resource is not available yet. Retry shortly.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ValidationError":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"NotImplemented":{"description":"The operation is not supported","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PreconditionFailed":{"description":"Precondition failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"headers":{"RateLimit":{"description":"Remaining capacity for the request's rate-limit policy as an IETF Structured Field. Format: `\"<policy>\";r=<remaining>;t=<seconds_until_reset>`.\n","schema":{"type":"string"},"example":"\"email_send\";r=842;t=35"},"RateLimit-Policy":{"description":"Effective quota for the request's rate-limit policy as an IETF Structured Field. Format: `\"<policy>\";q=<quota>;w=<window_seconds>`.\n","schema":{"type":"string"},"example":"\"email_send\";q=1000;w=60"},"RetryAfter":{"description":"Number of seconds to wait before retrying the request.\n","schema":{"type":"integer","minimum":0},"example":35},"IdempotencyReplay":{"description":"The API includes this header when it replays the response for an earlier request that used the same `Idempotency-Key`. The API does not process the request again.","schema":{"type":"string","enum":["true"]}},"CacheControl":{"description":"Caching directives for the response, for example `public, max-age=3600`.","schema":{"type":"string"}},"ContentDisposition":{"description":"Indicates the response is a file attachment and carries the suggested\ndownload filename, for example `attachment; filename=\"invoice-2600042.pdf\"`.\n","schema":{"type":"string"}}},"parameters":{"BirdAcquisition":{"name":"Bird-Acquisition","in":"header","required":false,"description":"Percent-encoded JSON with consent set to granted and first and last touch objects. Each touch requires source, captured_at (RFC 3339), and a root-relative landing_path of at most 512 UTF-8 bytes without a query or fragment. Optional fields are medium, campaign, campaign_id, adgroup_id, content, term, locale, referrer_host and click identifiers. The first touch expires after 30 days. A null last touch uses the first touch. Missing, malformed, unconsented or expired context is recorded as unknown and never blocks authentication.","schema":{"type":"string","maxLength":12000},"x-sensitive":true},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Client-supplied key. On operations supporting request deduplication, a retained\nresponse is replayed for duplicate requests with the same key within the\nidempotency window (3 hours by default). This protection requires a workspace,\norganization, or staff-account scope. User-only and unauthenticated operations,\nstreams, and operations with a separate replay contract do not use this\nresponse replay.\n\nOn a supported operation, if idempotency protection is unavailable before execution, the API returns\n`503 IdempotencyUnavailable` (E01033) without executing this attempt. Retry with\nbackoff using the same key and request. An operation that takes effect before\nits response is retained can still execute again on retry.\n\nTwo distinct 409 errors signal misuse:\n\n- `request_in_progress` (E01004): The same key is currently being\n  processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds.\n- `idempotency_key_reuse` (E01005): The same key has already completed\n  against a different request body or method. Generate a new key.\n\nRecommended key format is `<event-type>/<entity-id>` (for example `welcome-user/usr_abc123`).\n","schema":{"type":"string","maxLength":255}},"ConfirmationToken":{"name":"Confirmation-Token","in":"header","required":true,"description":"The confirmation token from the emailed closure link. Minted by the matching closure-request endpoint and valid for 24 hours. A missing, expired, invalid, or already-used token is rejected.\n","x-sensitive":true,"schema":{"type":"string","minLength":1}},"XWorkspaceIdRequired":{"name":"X-Workspace-Id","in":"header","required":true,"description":"Workspace context for this session-authenticated request. The caller must have access to this workspace.","schema":{"$ref":"#/components/schemas/WorkspaceID"}},"OrderAsc":{"name":"order","in":"query","required":false,"description":"Sort direction. Defaults to `asc`, which sorts alphabetically or from oldest to newest, depending on the selected sort field.\n","schema":{"$ref":"#/components/schemas/SortOrder","default":"asc"}},"PaginationLimit":{"name":"limit","in":"query","required":false,"description":"Maximum number of items to return per page.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},"StartingAfter":{"name":"starting_after","in":"query","required":false,"description":"Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order.","schema":{"type":"string"}},"EndingBefore":{"name":"ending_before","in":"query","required":false,"description":"Cursor from the `prev_cursor` or `refresh_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. `prev_cursor` returns the preceding page. `refresh_cursor` anchors at the first row of that response, which on a newest-first sort is how to fetch the items that have appeared since.","schema":{"type":"string"}},"IncludeTotal":{"name":"include_total","in":"query","required":false,"description":"When true, the response includes a `total` field with the total number of items matching the request's filters across all pages.","schema":{"type":"boolean","default":false}},"OrderDesc":{"name":"order","in":"query","required":false,"description":"Sort direction. Defaults to `desc`, which sorts from newest to oldest or largest to smallest, depending on the selected sort field.\n","schema":{"$ref":"#/components/schemas/SortOrder","default":"desc"}},"AssistantAuthorizationVersion":{"name":"X-Assistant-Authorization-Version","in":"header","required":true,"description":"Current staff Ask Bird authorization version returned by capabilities.","schema":{"type":"string","minLength":64,"maxLength":64,"pattern":"^[a-f0-9]{64}$"}},"XOrganizationId":{"name":"X-Organization-Id","in":"header","required":false,"description":"Organization context for the request. Required for dashboard authentication. An API key or access token carries its own organization, so send either that organization or no header at all; a different one is rejected.","schema":{"type":"string","pattern":"^org_[0-9a-hjkmnp-tv-z]{26}$"}},"XWorkspaceId":{"name":"X-Workspace-Id","in":"header","required":false,"description":"Workspace context for the request. Required for dashboard authentication. An API key or access token carries its own workspace, so send either that workspace or no header at all; a different one is rejected.","schema":{"type":"string","pattern":"^ws_[0-9a-hjkmnp-tv-z]{26}$"}},"CreatedAfter":{"name":"created_after","in":"query","required":false,"description":"Limits the response to resources created at or after this timestamp. Combine it with `created_before` to select a time window. Use an RFC 3339 timestamp with a timezone offset.","schema":{"type":"string","format":"date-time","example":"2026-05-01T00:00:00Z"}},"CreatedBefore":{"name":"created_before","in":"query","required":false,"description":"Limits the response to resources created before this timestamp. Combine it with `created_after` to select a time window. Use an RFC 3339 timestamp with a timezone offset.","schema":{"type":"string","format":"date-time","example":"2026-06-01T00:00:00Z"}},"TagFilter":{"name":"tag","in":"query","required":false,"description":"Filter by tag. Accepts `name` to match any record carrying that tag name, or `name:value` to match a specific tag pair (for example `category:welcome`). Repeat the parameter to add more tags. A record must match every tag listed to be returned.\n","schema":{"type":"array","items":{"type":"string"}}},"EmailBroadcastStatusFilter":{"name":"status","in":"query","required":false,"description":"Filter by lifecycle status. Repeat the parameter to match more than one status, for example `?status=accepted&status=sending`.\n","style":"form","explode":true,"schema":{"type":"array","items":{"$ref":"#/components/schemas/EmailBroadcastStatus"}}},"EmailBroadcastAudienceFilter":{"name":"audience_id","in":"query","required":false,"description":"Filter by audience. Only broadcasts that use this audience are returned.","schema":{"$ref":"#/components/schemas/AudienceID"}},"EmailBroadcastTagFilter":{"name":"tag","in":"query","required":false,"description":"Filter by tag. Pass `name` to match any broadcast that has that tag name, or pass `name:value` to match a specific tag pair, for example `campaign:spring_launch`.\n","schema":{"type":"string"}},"EmailBroadcastSearchFilter":{"name":"q","in":"query","required":false,"description":"Case-insensitive substring match against the broadcast's tag names and values, or the referenced template's name.\n","schema":{"type":"string","maxLength":200}},"StatsTimezone":{"name":"timezone","in":"query","required":false,"description":"IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets.\n","schema":{"type":"string","minLength":1,"example":"Asia/Kathmandu"}},"WhatsAppStatsTemplateFilter":{"name":"template","in":"query","required":false,"description":"Restricts the statistics to one template, identified by its ID (`wat_…`) or slug. Mutually exclusive with the other dimension filters (`category`, `phone_number`, `tag`); only one may be set per request. An ID matches the `template_id` key on a row of the per-template breakdown; a slug is accepted for callers that predate that key and resolves to the same messages.\n","schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^(wat_[0-9a-hjkmnp-tv-z]{26}|[a-z0-9]([a-z0-9_-]*[a-z0-9])?)$","example":"wat_01krdgeqcxet5s7t44vh8rt9mg"}},"EmailStatsTemplateFilter":{"name":"template","in":"query","required":false,"description":"Restricts the statistics to one template, identified by its ID (`emt_…`) or name. This parameter is mutually exclusive with other dimension filters.\n","schema":{"type":"string","minLength":1,"maxLength":63,"pattern":"^(emt_[0-9a-hjkmnp-tv-z]{26}|[a-z0-9]([a-z0-9_-]*[a-z0-9])?)$","example":"welcome-email"}},"EmailCompetitiveRange":{"name":"range","in":"query","required":false,"description":"How many days back the response covers, counting from now. One of three fixed trend windows rather than an open date range, matching how a competitive-intelligence chart is read. Defaults to 30.\n","schema":{"type":"integer","enum":[7,30,90],"default":30,"example":30}},"EmailCompetitiveTimezone":{"name":"timezone","in":"query","required":false,"description":"IANA timezone identifier to report send times in; defaults to UTC. The grid is folded into this zone before it is summed, so a send lands on the weekday and hour it happened at locally rather than the one it happened at in UTC. A zone this API does not know returns 422 rather than falling back to UTC, so an axis is never labelled with a zone the figures were not folded into.\n","schema":{"$ref":"#/components/schemas/Timezone"}},"AdminCrmObjectParam":{"name":"object","in":"path","required":true,"description":"Attio collection to manage.","schema":{"$ref":"#/components/schemas/AdminCrmObject"}},"AdminCrmAttributeParam":{"name":"attribute","in":"path","required":true,"description":"Attribute API slug from this object’s field definitions.","schema":{"type":"string","minLength":1,"maxLength":200}},"RequiredIdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"description":"Required client-supplied deduplication key for this write. Reuse it for retries of the same intent. Successful results replay within the configured idempotency window (three hours by default); selected connected-app writes also replay uncertain-write conflicts. Use a new key only after confirming the prior result and beginning a different action.","schema":{"type":"string","minLength":1,"maxLength":255}},"AdminCrmRecordIdParam":{"name":"record_id","in":"path","required":true,"description":"External Attio record identifier.","schema":{"$ref":"#/components/schemas/AdminCrmIdentifier"}},"AdminSupportWorkbenchThreadIdParam":{"name":"thread_id","in":"path","required":true,"description":"Exact Plain thread identifier returned by the support queue.","schema":{"type":"string","minLength":1,"maxLength":128}},"AdminSupportCommandKeyPath":{"name":"command_key","in":"path","required":true,"description":"Original staff command key.","schema":{"$ref":"#/components/schemas/AdminSupportCommandKey"}},"AdminSupportWorkItemIdPath":{"name":"work_item_id","in":"path","required":true,"description":"Exact work item id.","schema":{"$ref":"#/components/schemas/SupportWorkItemID"}},"AdminSupportCaseIdPath":{"name":"case_id","in":"path","required":true,"description":"Exact case id.","schema":{"$ref":"#/components/schemas/SupportCaseID"}},"AdminSupportCommandRecoveryKey":{"name":"Idempotency-Key","in":"header","required":true,"description":"Original command key for this staff member, operation and case or Plain conversation. Recovery finds the saved result or permanently cancels the original command before a replacement can be submitted.","schema":{"$ref":"#/components/schemas/AdminSupportCommandKey"}},"AdminSupportOperationsSince":{"name":"since","in":"query","description":"Start of the history report window, up to 90 days ago. Defaults to 30 days ago. Keep the same value while paging.","schema":{"type":"string","format":"date-time"}},"AdminSupportOwnerUserIdFilter":{"name":"owner_user_id","in":"query","required":false,"description":"Exact owner user id.","schema":{"$ref":"#/components/schemas/UserID"}},"AdminSupportTeamFilter":{"name":"team","in":"query","required":false,"description":"Select work assigned to this specialist team.","schema":{"type":"string","maxLength":128}},"AdminSupportSpecialistHandoffIdPath":{"name":"handoff_id","in":"path","required":true,"description":"Assigned support handoff identifier.","schema":{"$ref":"#/components/schemas/SupportWorkItemID"}},"AdminSupportUnassignedFilter":{"name":"unassigned","in":"query","required":false,"description":"When true, only work without an owner. Cannot combine true with owner_user_id. Omitted or false includes all owners.","schema":{"type":"boolean"}},"AdminSupportOrganizationIdFilter":{"name":"organization_id","in":"query","required":false,"description":"Exact organization id.","schema":{"$ref":"#/components/schemas/OrganizationID"}},"AdminSupportCaseIdFilter":{"name":"case_id","in":"query","required":false,"description":"Exact case id.","schema":{"$ref":"#/components/schemas/SupportCaseID"}},"AdminSupportKindFilter":{"name":"kind","in":"query","required":false,"description":"Exact kind.","schema":{"$ref":"#/components/schemas/AdminSupportWorkKind"}},"AdminSupportStatusFilter":{"name":"status","in":"query","required":false,"description":"Exact status.","schema":{"$ref":"#/components/schemas/AdminSupportWorkStatus"}},"AdminSupportDueBeforeFilter":{"name":"due_before","in":"query","required":false,"description":"Include only dated obligations due strictly before this instant; all other supplied filters also apply.","schema":{"type":"string","format":"date-time"}},"AdminSupportUnfinishedFilter":{"name":"unfinished","in":"query","required":false,"description":"Select unfinished obligations, including handoffs already accepted. False or omission includes all statuses.","schema":{"type":"boolean"}},"AdminSupportSearchFilter":{"name":"search","in":"query","required":false,"description":"Find work whose title contains this text, ignoring case.","schema":{"type":"string","maxLength":200}},"AdminSupportCreatedBeforeFilter":{"name":"created_before","in":"query","required":false,"description":"Select work created before this instant to review ageing obligations.","schema":{"type":"string","format":"date-time"}},"AdminSupportTargetIdPath":{"name":"target_id","in":"path","required":true,"description":"Stable case target identifier.","schema":{"$ref":"#/components/schemas/SupportCaseTargetID"}},"IncludeRemoved":{"name":"include_removed","in":"query","required":false,"description":"Include banned and deleted organizations in the figures. Off by default, so the results reflect only active organizations.","schema":{"type":"boolean","default":false}},"AdminEmailStatsFields":{"name":"fields","in":"query","required":false,"description":"Metric groups to compute and return, comma-separated. Defaults to every group. A group left out is omitted from every point and from `totals`, and its aggregate states are not merged, which is what makes a narrow read cheap: pass `delivery` alone for a volume chart.\n- `delivery`: the recipient lifecycle counts and rates (accepted through bounces and complaints). - `engagement`: opens, clicks, unsubscribes and their rates. - `latency`: the processing, delivery and end-to-end percentiles. - `block`: messages blocked by enforcement. Its `block_rate` is denominated on accepted recipients, a `delivery` count, so it is null unless `delivery` is requested too.\n","style":"form","explode":false,"schema":{"type":"array","minItems":1,"uniqueItems":true,"items":{"type":"string","enum":["delivery","engagement","latency","block"]},"example":["delivery","block"]}},"AdminContractAsOf":{"name":"as_of","in":"query","required":false,"description":"Evaluate status, effect, and current version at this instant, using UTC for contract start and end dates. Defaults to the current time.","schema":{"type":"string","format":"date-time"}},"AdminTrustInquiryId":{"name":"inquiry_id","in":"path","required":true,"description":"The Persona inquiry identifier, as it appears on the verification record.","schema":{"type":"string","minLength":1},"example":"inq_4kL9mNpQrStUvWxYz2345678"},"AccountingBookId":{"name":"book_id","in":"path","required":true,"description":"Accounting book identifier.","schema":{"$ref":"#/components/schemas/AccountingBookID"}},"AccountingPeriodFilter":{"name":"period_id","in":"query","required":false,"description":"Only return records from this accounting period.","schema":{"$ref":"#/components/schemas/AccountingPeriodID"}},"AccountingExceptionId":{"name":"exception_id","in":"path","required":true,"description":"Accounting exception identifier.","schema":{"$ref":"#/components/schemas/AccountingExceptionID"}},"AccountingSourceId":{"name":"source_id","in":"path","required":true,"description":"Accounting source identifier.","schema":{"$ref":"#/components/schemas/AccountingSourceID"}},"AccountingJournalId":{"name":"journal_id","in":"path","required":true,"description":"Accounting journal identifier.","schema":{"$ref":"#/components/schemas/AccountingJournalID"}},"AccountingExportId":{"name":"export_id","in":"path","required":true,"description":"Accounting export identifier.","schema":{"$ref":"#/components/schemas/AccountingExportID"}},"AccountingEvidenceId":{"name":"evidence_id","in":"path","required":true,"description":"Accounting evidence identifier.","schema":{"$ref":"#/components/schemas/AccountingEvidenceID"}},"AccountingPeriodId":{"name":"period_id","in":"path","required":true,"description":"Accounting period identifier.","schema":{"$ref":"#/components/schemas/AccountingPeriodID"}},"AdminBillingReportFrom":{"name":"report_from","in":"query","required":false,"description":"Start of the optional financial report, inclusive. Supply with report_to. Both boundaries must be midnight UTC and cover at most 92 complete days. Omit both to leave the report out of the response.","schema":{"type":"string","format":"date-time"}},"AdminBillingReportTo":{"name":"report_to","in":"query","required":false,"description":"End of the optional financial report, exclusive. Supply with report_from. An incomplete pair, partial UTC day, future day or window longer than 92 days returns 422. Reporting dates do not change the list's pagination or filters.","schema":{"type":"string","format":"date-time"}},"AdminRevenueFrom":{"name":"from","in":"query","required":true,"description":"Inclusive start at midnight UTC. The report accepts at most 92 complete days.","schema":{"type":"string","minLength":1,"format":"date-time"}},"AdminRevenueTo":{"name":"to","in":"query","required":true,"description":"Exclusive end at midnight UTC, no later than the start of today.","schema":{"type":"string","minLength":1,"format":"date-time"}},"SalesDocumentIdParam":{"name":"document_id","in":"path","required":true,"description":"Private sales document identifier.","schema":{"$ref":"#/components/schemas/SalesDocumentID"}},"SalesDocumentRevisionFilter":{"name":"revision","in":"query","required":false,"description":"Only return entries for this content revision. Omit to include all revisions.","schema":{"type":"integer","minimum":1,"maximum":2147483647}},"AdminNumbersHolderOfRecordNumber":{"name":"number","in":"query","required":true,"description":"The number to look up, in E.164 format. The leading `+` is required; a bare-digits or otherwise formatted number is refused.","schema":{"type":"string"}},"AdminAcquisitionModel":{"name":"model","in":"query","description":"First saved touch or last saved touch. Last falls back to first when the supplied last touch is missing, invalid or earlier than first; either can be direct.","schema":{"type":"string","enum":["first","last"],"default":"first"}},"AdminAcquisitionFrom":{"name":"from","in":"query","required":true,"description":"Inclusive UTC cohort start.","schema":{"type":"string","format":"date-time"}},"AdminAcquisitionTo":{"name":"to","in":"query","required":true,"description":"Exclusive UTC cohort end, at most 92 days after the start.","schema":{"type":"string","format":"date-time"}},"AdminAcquisitionSource":{"name":"source","in":"query","description":"Exact source label, including unknown.","schema":{"type":"string","maxLength":256}},"AdminAcquisitionCampaignFilter":{"name":"campaign","in":"query","description":"Exact campaign ID or label.","schema":{"type":"string","maxLength":256}},"AdminAcquisitionProduct":{"name":"product","in":"query","description":"Product selected on the sales inquiry.","schema":{"type":"string","maxLength":100}},"AdminAcquisitionIncludeExcluded":{"name":"include_excluded","in":"query","description":"Include internal and test acquisitions.","schema":{"type":"boolean","default":false}},"AdminAcquisitionIncludeOrganizationIDs":{"name":"include_organization_ids","in":"query","description":"Include founding organization IDs and unresolved organization links for each campaign cohort.","schema":{"type":"boolean","default":false}},"DemoCaptchaToken":{"name":"X-Captcha-Token","in":"header","required":true,"description":"Fresh CAPTCHA token for the demo form. A successful validation or submission consumes the challenge.","schema":{"type":"string","minLength":1,"maxLength":8192,"x-sensitive":true}},"DemoSubmissionToken":{"name":"X-Demo-Submission-Token","in":"header","required":true,"description":"Private 32-byte submission proof encoded as unpadded base64url. Keep the same secret for retries of one idempotency key, and keep it separate from that key and the submitted contact details.","schema":{"type":"string","minLength":43,"maxLength":43,"pattern":"^[A-Za-z0-9_-]{43}$","x-sensitive":true}},"DemoToken":{"name":"X-Demo-Token","in":"header","required":true,"x-sensitive":true,"description":"Private capability returned when this demo inquiry was accepted.","schema":{"type":"string","minLength":1,"maxLength":1024}},"VoiceShortCallThreshold":{"name":"short_call_threshold_ms","in":"query","required":false,"description":"Duration in milliseconds below which an answered call counts toward `short_calls`. The default of 30 seconds is a convention rather than a property of the network, so set it to whatever your own definition of a call too short to have been useful is. Every response echoes the value it used, so a stored figure can always be read against the definition that produced it.","schema":{"type":"integer","minimum":1,"maximum":3600000,"default":30000,"example":30000}},"VoiceStatsDestination":{"name":"destination","in":"query","required":false,"description":"When set, limits the statistics to calls to this destination country, as an ISO 3166-1 alpha-2 code. This is the drill-down from a row of the destination breakdown, and it matches the same country that breakdown reports for a call.","schema":{"$ref":"#/components/schemas/CountryCode"}},"VoiceStatsTrunkID":{"name":"trunk_id","in":"query","required":false,"description":"When set, limits the statistics to calls carried by this SIP trunk. This is the drill-down from a row of the trunk breakdown. Calls that used no trunk, such as Verify voice calls, match no value here and are excluded whenever it is set.","schema":{"$ref":"#/components/schemas/SIPTrunkID"}},"VoiceStatsOwnNumber":{"name":"own_number","in":"query","required":false,"description":"When set, limits the statistics to calls on this number of yours, in E.164 form. It matches your own end of the call, which is the caller on an outbound leg and the recipient on an inbound one. It is named apart from the call log's `number` because that one matches either party, where this one is always your end. A number that has no standard form matches exactly as the network presented it, so an unusual one is still searchable. A value that matches no call gives an empty result rather than an error, as any unmatched filter does. To narrow by the party you called instead, use `destination`.","schema":{"type":"string","minLength":1,"maxLength":20,"example":"+31201234567"}},"VoiceStatsStatus":{"name":"status","in":"query","required":false,"description":"When set, limits the statistics to calls with one of these statuses, comma-separated. Four of the vocabulary's values match nothing here, for three different reasons: these statistics cover ended calls only, so `ringing` and `in_progress` are outside their scope; `busy` and `canceled` are not emitted yet and arrive as `failed` today; and no call is stored as `other`, which the outcome split uses for a status it does not name. The five that do match are `answered`, `no_answer`, `failed`, `rejected` and `unknown`.","style":"form","explode":false,"schema":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/VoiceCallStatus"}}},"VoiceStatsBreakdownLimit":{"name":"limit","in":"query","required":false,"description":"Maximum groups per page, ranked by the requested metric. The response total counts all matching groups before pagination.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"EsimTagFilter":{"name":"tag","in":"query","required":false,"description":"Filter by tag. Accepts `name` to match any eSIM carrying that tag name, or `name:value` to match a specific tag pair (e.g. `trip:summer`). A trailing colon (`name:`) matches the name alone, the same as `name`. A term with an empty name is rejected. Repeat the parameter to AND-combine several tag filters.\n","schema":{"type":"array","items":{"type":"string"}}},"AutomationExecutionStatusFilter":{"name":"status","in":"query","required":false,"description":"Limits the response to executions in this state.","schema":{"$ref":"#/components/schemas/AutomationExecutionStatus"}},"AutomationExecutionModeFilter":{"name":"mode","in":"query","required":false,"description":"Limits the response to production or test executions.","schema":{"$ref":"#/components/schemas/AutomationExecutionMode"}},"AutomationExecutionVersionIDFilter":{"name":"version_id","in":"query","required":false,"description":"Limits the response to executions of this immutable published version. Draft executions do not match.","schema":{"$ref":"#/components/schemas/AutomationVersionID"}},"AutomationExecutionFailureCodeFilter":{"name":"failure_code","in":"query","required":false,"description":"Limits the response to executions with this stable platform failure code. Older compacted executions with unavailable metadata do not match.","schema":{"type":"string","minLength":1,"maxLength":128}},"AutomationExecutionExitReasonFilter":{"name":"exit_reason","in":"query","required":false,"description":"Limits the response to executions with this authored explicit-exit reason. Older compacted executions with unavailable metadata do not match.","schema":{"$ref":"#/components/schemas/AutomationExitReason"}},"AutomationStatsModeFilter":{"name":"mode","in":"query","required":false,"description":"Execution modes to include. Defaults to `production`.","schema":{"$ref":"#/components/schemas/AutomationStatsMode"}},"AutomationStatsAutomationID":{"name":"automation_id","in":"query","required":false,"description":"Restricts statistics to one automation. Omission includes all automations in the workspace.","schema":{"$ref":"#/components/schemas/AutomationID"}},"AutomationStatsVersionID":{"name":"version_id","in":"query","required":false,"description":"Restricts statistics to one immutable published version. Omitting its owning `automation_id` returns 422. A version not found under that automation in the current workspace returns 404.","schema":{"$ref":"#/components/schemas/AutomationVersionID"}},"AutomationStatsAutomationIDs":{"name":"automation_ids","in":"query","required":false,"style":"form","explode":false,"description":"Comma-separated automation IDs whose rows should be returned. Use this or automation_id, not both. Omission includes all matching automations; an empty selector returns 422. The selector does not change independently requested workspace summaries.","schema":{"type":"array","minItems":1,"maxItems":100,"uniqueItems":true,"items":{"$ref":"#/components/schemas/AutomationID"}}},"AutomationStatsRequiredAutomationID":{"name":"automation_id","in":"query","required":true,"description":"Automation whose published versions are compared.","schema":{"$ref":"#/components/schemas/AutomationID"}},"AutomationId":{"name":"automation_id","in":"path","required":true,"description":"Automation ID.","schema":{"$ref":"#/components/schemas/AutomationID"}}}}}