openapi: 3.2.0 info: title: Bird Sms Keyword Rules 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-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. paths: /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. Use 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. Default 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.' 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. ' 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. ' 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`. ' 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. ' 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. Your 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.' 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 every keyword that matches it and the reply it sends.' 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 changed. To replace one, create a rule with the same operation and country and yours takes precedence. What the rule applies to is fixed once created, so this changes the reply and the keywords only.' 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. 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 components: schemas: 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. ' 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' 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. ' 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. ' 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. ' 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. ' example: true 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. ' 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. ' 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. ' 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. ' 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. ' 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. ' 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. ' 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. ' 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. ' 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' 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. ' 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. ' 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. ' 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. ' 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. ' 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. ' 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. ' example: true 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. ' Error: type: object additionalProperties: false required: - error properties: error: $ref: '#/components/schemas/ErrorBody' 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 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. ' items: $ref: '#/components/schemas/SMSKeywordRule' 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' Conflict: description: Resource conflict 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' NotFound: description: Resource not found 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' headers: RetryAfter: description: 'Number of seconds to wait before retrying the request. ' schema: type: integer minimum: 0 example: 35 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 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}$ 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. '