openapi: 3.2.0 info: title: Bird Sms Suppressions API version: 1.0.0 description: 'The Bird API: one REST API for email, SMS, WhatsApp, verification, and Realtime.' 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. ' 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: sms-suppressions description: Sender and subscriber pairs that block SMS delivery. paths: /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. A 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. Ended suppressions are excluded. A subscriber who opted back in is reachable again and does not appear in this list.' 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. ' 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: - `keyword_stop`: The subscriber texted a stop keyword to the sender. - `carrier_opted_out`: Their carrier reported the opt-out. - `manual`: Added through the API or dashboard. ' 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. Adding 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.' 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. ' 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. This 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`.' 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. **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`. Ending 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`.' 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 components: schemas: SMSMessageID: type: string minLength: 1 pattern: ^sms_[0-9a-hjkmnp-tv-z]{26}$ example: sms_01krdgeqcxet5s7t44vh8rt9mg 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. ' 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. ' items: $ref: '#/components/schemas/NextAction' SMSSuppressionReason: type: string minLength: 1 x-extensible-enum: - keyword_stop - carrier_opted_out - manual description: 'Reason this sender cannot message the subscriber: - `keyword_stop`: The subscriber sent a stop keyword. A start keyword clears it. - `carrier_opted_out`: The carrier reported the opt-out. - `manual`: Your workspace added the suppression through the API or dashboard. This is an open enum. Accept unrecognized values. ' SMSSuppressionReasonFilter: type: string enum: - keyword_stop - carrier_opted_out - manual 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' SMSSuppressionOrigin: type: string minLength: 1 x-extensible-enum: - keyword - dlr_event - api_key - user description: 'How the opt-out reached us: - `keyword`: an inbound message from the subscriber. - `dlr_event`: a carrier delivery report. - `api_key`: an API call. - `user`: someone acting in the dashboard. Kept beside the reason because one reason can arrive by more than one route. This list grows over time, so treat an unknown value as informational rather than rejecting the record. ' 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. ' 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. ' example: '+15557654321' 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. ' _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 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. ' 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. ' 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. ' 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. ' SMSSuppressionEndReason: type: string minLength: 1 x-extensible-enum: - keyword_start - api_key - user - carrier_cleared description: 'What ended it: - `keyword_start`: the subscriber texting a start keyword to the same sender. - `api_key`: an API call. - `user`: someone acting in the dashboard. - `carrier_cleared`: the carrier reporting its own opt-out cleared. This list grows over time, so treat an unknown value as informational rather than rejecting the record. ' Error: type: object additionalProperties: false required: - error properties: error: $ref: '#/components/schemas/ErrorBody' 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. ' 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. ' 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. ' 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. ' 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. ' 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. ' 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. ' SMSSuppressionID: type: string minLength: 1 pattern: ^ssu_[0-9a-hjkmnp-tv-z]{26}$ example: ssu_01krdgeqcxet5s7t44vh8rt9mg responses: InternalError: description: Internal server error 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' Forbidden: description: Insufficient permissions 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. ' headers: Retry-After: $ref: '#/components/headers/RetryAfter' content: application/json: schema: $ref: '#/components/schemas/Error' 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`. ' content: application/json: schema: $ref: '#/components/schemas/Error' BadRequest: description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Authentication required content: application/json: schema: $ref: '#/components/schemas/Error' headers: RetryAfter: description: 'Number of seconds to wait before retrying the request. ' 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' RateLimit: description: 'Remaining capacity for the request''s rate-limit policy as an IETF Structured Field. Format: `"";r=;t=`. ' 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: `"";q=;w=`. ' schema: type: string example: '"email_send";q=1000;w=60' parameters: 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 `/` (for example `welcome-user/usr_abc123`).\n" schema: type: string maxLength: 255 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 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 securitySchemes: BearerAuth: type: http scheme: bearer description: 'Pass the API key as a bearer token in the `Authorization` header. Keys use the format `bk_{region}_*`. The prefix identifies the region and selects the API endpoint. Official Bird SDKs and the CLI derive the region from the key. ' CookieAuth: type: apiKey in: cookie name: bird_session description: 'Session cookie set after signing in to the Bird dashboard. The cookie value is an opaque session token; no session data is stored in the cookie itself. ' RealtimeKey: type: apiKey in: header name: X-Realtime-Key description: 'The Realtime app key. Together with `X-Realtime-Secret`, it authenticates a request to the Realtime API in addition to the workspace credential. Both values come from the app''s credentials and must belong to the calling workspace. Official Bird SDKs accept the pair as client configuration. ' RealtimeSecret: type: apiKey in: header name: X-Realtime-Secret description: 'The Realtime app secret paired with `X-Realtime-Key`. The API returns the secret only when the key is created and does not store it. Create a new key and revoke the current key if you lose the secret. Official Bird SDKs accept the pair as client configuration. '