{"openapi":"3.1.0","info":{"title":"Sintalk API","description":"Send SMS, RCS and voice messages to your contacts, from templates you control, and follow each message to its final delivery state.\n\nA typical integration does four things: upload any media a template needs, create the template, send it, then track delivery — by polling the message or by receiving the `message_update` webhook.\n\nSend either way: name the recipients inline for a one-off, or group contacts into a list and run a campaign against it when you want the whole send tracked as one thing.\n\n## Authentication\n\nEvery route is authenticated with your integration API key. Send it either way:\n\n```\nX-API-Key: st_xxx\nAuthorization: Bearer st_xxx\n```\n\nA key is the string `st_` followed by 64 hex characters. Each key is bound to one account, so no account id appears in any path. A missing or unreadable key is refused with `401 unauthorized`; a valid key that may not call the route, or that lacks the scope the route requires, is refused with `403 forbidden`. Each operation names its required scope as `x-required-scope`. Treat a key as a secret — it belongs on your server, never in a browser or a mobile app.\n\n## Base URL\n\n| Environment | Base URL |\n| --- | --- |\n| Production | `https://api.sintalk.com.br/v3` |\n\n## Conventions\n\nIdentifiers are UUIDv7 strings, so they sort chronologically. Timestamps are RFC 3339 in UTC (`2026-07-01T12:30:45Z`). Every field name is `lower_snake_case`. Phone numbers are returned in E.164 (`+5511900000000`); on input, common Brazilian formats are accepted and normalized. An unknown field in a request body is rejected rather than ignored.\n\n## Pagination\n\nList routes are offset-paginated and return a `{data, pagination}` envelope. Pass `limit` (default 25, maximum 1000) and `offset`; read `pagination.total` for the full count and `pagination.has_more` to decide whether to advance. Lists come back newest first.\n\n## Errors\n\nEvery failure uses one JSON envelope:\n\n```json\n{\n  \"error\": {\n    \"code\": \"validation_failed\",\n    \"message\": \"phone must be a valid Brazilian mobile number\",\n    \"fields\": [\n      { \"field\": \"phone\", \"message\": \"must be a valid Brazilian mobile number\" }\n    ]\n  }\n}\n```\n\nBranch on `error.code` — it is a stable string (see the `ErrorCode` schema). The `message` is for your logs, not for your users, and may be reworded. `fields` is present when a request failed validation.\n\n## Idempotency\n\nSending messages accepts an `Idempotency-Key` header, so a timeout or a dropped connection cannot send — or bill — twice. Generate one key per logical send and replay it on retry: the original response is returned instead of the write being repeated. Reusing a key with a different body is refused with `409 conflict`. Keys are scoped to your account and retained for 24 hours.\n\n## Rate limits\n\nRequests are rate-limited per account. An exhausted limit returns `429` with `Retry-After` alongside `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. Back off using `Retry-After` rather than retrying immediately.","contact":{"name":"Sintalk API Support","email":"sintalk@sintalk.com.br"},"license":{"name":"Proprietary","identifier":"LicenseRef-Proprietary"},"version":"3.0.0"},"servers":[{"url":"https://api.sintalk.com.br/v3","description":"Production"}],"tags":[{"name":"Campaigns","description":"Sending one template to a whole contact list, as a single tracked run.\n\nA campaign is created as a draft against a template, a list and a channel — all three fixed for its lifetime. Launch it to start sending immediately, or schedule it for later and cancel that schedule while it is still pending."},{"name":"Contact Lists","description":"Named groups of contacts, and the memberships that fill them.\n\nA list is what a campaign sends to. Add contacts you already have by id, or upsert by phone to create and add in one call. A list typed `blacklist` works the other way: any recipient on one is suppressed for that list's channels, so sends to them come back `blocked` — never dispatched, never charged."},{"name":"Contacts","description":"The people you send to. A contact is identified by its phone number: creating one that already exists updates it instead of duplicating it, so a bulk load can be replayed safely. Alongside the name and number you can attach free-form string `attributes`, which templates read as variables at send time."},{"name":"Files","description":"Media your templates play or display — audio for voice, images and video for RCS — uploaded straight to storage rather than through this API.\n\nUpload is three steps: ask for a presigned URL, `PUT` the bytes to it, then confirm. Check `GET /files/constraints` first for the accepted MIME types and size limit per kind; the upload is rejected at confirm time if it does not match what was declared."},{"name":"Messages","description":"Sending, and everything that happens after. One send targets many recipients with a single body — inline content or a saved template — substituting per-recipient variables, and returns a result for each — some accepted, some immediately terminal.\n\nSending is asynchronous: a `202` means queued, not delivered. Track the outcome by reading the message back, or subscribe to the `message_update` webhook and be told. Retry a send with the same `Idempotency-Key` and it will not be sent twice."},{"name":"Templates","description":"A reusable message you save once and send many times. A template is bound to one channel and carries the content for it: a text, card, or carousel for RCS, plain text for SMS, or an audio file for voice.\n\nContent may contain `{{variable}}` placeholders; the detected names come back in `variables`, and a send supplies a value for each per recipient. A template can also declare follow-up rules that fire another template on a given delivery outcome."},{"name":"Webhooks","description":"Events we send to you. Rather than polling a message to its final state, register an endpoint and receive `message_update` as each status changes.\n\nDelivery is at-least-once and batched — one request carries a JSON array of events — so make your handler idempotent on each event's `id`. Verify the `X-Sintalk-Signature` HMAC before trusting a body, and respond `2xx` quickly; a non-2xx or a timeout is retried with backoff and abandoned after 5 attempts."}],"paths":{"/campaigns":{"get":{"tags":["Campaigns"],"summary":"List campaigns","description":"Returns your campaigns newest first, ordered by `created_at` descending.\n\nArray-valued filters (`channel`, `status`, `template_id`) are repeatable query parameters (e.g. `status=draft&status=running`): values are OR-combined within a dimension and AND-combined across dimensions; each also accepts a single value.","operationId":"listCampaigns","parameters":[{"name":"channel","in":"query","description":"Keep only campaigns sending over one of these channels.","required":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/Channel"}}},{"name":"status","in":"query","description":"Keep only campaigns currently in one of these lifecycle states.","required":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/CampaignStatus"}}},{"name":"template_id","in":"query","description":"Keep only campaigns sending one of these templates; up to 100 ids.","required":false,"schema":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100,"minItems":0}},{"name":"search","in":"query","description":"Case-insensitive substring filter on the campaign name; minimum 3 characters.","required":false,"schema":{"type":"string","maxLength":120,"minLength":3},"example":"July"},{"name":"created_from","in":"query","description":"Keep only campaigns whose `created_at` is at or after this instant.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-07-01T00:00:00Z"},{"name":"created_to","in":"query","description":"Keep only campaigns whose `created_at` is at or before this instant.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-07-31T23:59:59Z"},{"name":"limit","in":"query","description":"Page size; defaults to 25, max 1000 (larger values are rejected).","required":false,"schema":{"type":"integer","format":"int32","default":25,"maximum":1000,"minimum":1},"example":25},{"name":"offset","in":"query","description":"Rows to skip before this page; defaults to 0.","required":false,"schema":{"type":"integer","format":"int32","minimum":0},"example":0}],"responses":{"200":{"description":"A page of campaigns, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageCampaignResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"campaigns:read"},"post":{"tags":["Campaigns"],"summary":"Create a campaign","description":"Create a campaign that sends one template to every contact of one list. The campaign's channel is the template's channel; the list must not be a blacklist-type list, which is a suppression set rather than an audience.\n\nThe campaign starts in `draft`: nothing is sent until it is launched, and until then it can be renamed, scheduled, or deleted. Template and contact list are fixed at creation — only `name` can be changed afterwards.","operationId":"createCampaign","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCreateRequest"}}},"required":true},"responses":{"201":{"description":"The campaign as created, in `draft`, having produced no messages yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"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/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"campaigns:write"}},"/campaigns/{campaign_id}":{"get":{"tags":["Campaigns"],"summary":"Get a campaign","description":"Return a single campaign: the template and contact list it was created against, the channel it sends over, its lifecycle state, and the timestamps of each transition it has made.","operationId":"getCampaign","parameters":[{"name":"campaign_id","in":"path","description":"Identifier of the campaign to read.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970005-0000-7000-8000-000000000001"}],"responses":{"200":{"description":"The campaign, in its current lifecycle state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"campaigns:read"},"delete":{"tags":["Campaigns"],"summary":"Delete a campaign","description":"Removes a campaign that has not run. Only a `draft` or `scheduled` campaign can be deleted — deleting one that is `running`, `completed`, or `error` returns `409 conflict` with `error.code = \"invalid_state\"`. Deleting a `scheduled` campaign also cancels its pending launch.","operationId":"deleteCampaign","parameters":[{"name":"campaign_id","in":"path","description":"Identifier of the campaign to delete.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970005-0000-7000-8000-000000000001"}],"responses":{"204":{"description":"Campaign deleted; no body is returned."},"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"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"campaigns:write"},"patch":{"tags":["Campaigns"],"summary":"Update a campaign","description":"Updates are limited to `name`, and it can be renamed in any lifecycle state. The template, contact list, and channel chosen at creation are immutable; sending a different one of those means creating a new campaign.","operationId":"updateCampaign","parameters":[{"name":"campaign_id","in":"path","description":"Identifier of the campaign to rename.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970005-0000-7000-8000-000000000001"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignUpdateRequest"}}},"required":true},"responses":{"200":{"description":"The campaign after the rename.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"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"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"campaigns:write"}},"/campaigns/{campaign_id}/launch":{"post":{"tags":["Campaigns"],"summary":"Launch a campaign","description":"Start sending now. The campaign must be `draft` or `error`; it moves to `running`, `started_at` is stamped, and the contact list is fixed as of that instant — contacts added to the list afterwards are not messaged by this run.\n\nSending then proceeds in the background, one message per contact in that snapshot. It reaches `completed` once every message it created has settled, or `error` if the run stops first. Launching an `error` campaign again retries the run without messaging a recipient it already messaged.\n\nReturns `409 conflict` with `error.code = \"invalid_state\"` when the campaign is not in a launchable state, or `error.code = \"import_in_progress\"` while a bulk contact load is still running on the target list.","operationId":"launchCampaign","parameters":[{"name":"campaign_id","in":"path","description":"Identifier of the campaign to launch.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970005-0000-7000-8000-000000000001"}],"responses":{"202":{"description":"Launch accepted: the campaign is now `running` and sending proceeds in the background; no body is returned. Accepted means started, not sent."},"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"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"campaigns:launch"}},"/campaigns/{campaign_id}/schedule":{"put":{"tags":["Campaigns"],"summary":"Schedule a campaign","description":"Set the instant the campaign launches itself at. `scheduled_at` must be strictly in the future, otherwise `422 unprocessable_entity`. The campaign must be `draft`, `error`, or already `scheduled` — that last case reschedules it — otherwise `409 conflict`.\n\nThe campaign moves to `scheduled` and, at `scheduled_at`, launches exactly as an explicit launch would: same snapshot rule, same `running` to `completed` path. A schedule that comes due while a bulk contact load is still running on the target list waits for the load, and the campaign moves to `error` if it has not finished in time.","operationId":"scheduleCampaign","parameters":[{"name":"campaign_id","in":"path","description":"Identifier of the campaign to schedule.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970005-0000-7000-8000-000000000001"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignScheduleRequest"}}},"required":true},"responses":{"200":{"description":"The campaign in `scheduled`, carrying the `scheduled_at` it will launch at.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"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/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"campaigns:launch"},"delete":{"tags":["Campaigns"],"summary":"Cancel a campaign schedule","description":"Cancel a pending schedule. The campaign returns to `draft` with `scheduled_at` cleared and no longer launches by itself; it can then be rescheduled, launched now, or deleted. Only a `scheduled` campaign can be unscheduled.","operationId":"unscheduleCampaign","parameters":[{"name":"campaign_id","in":"path","description":"Identifier of the campaign whose schedule is cancelled.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970005-0000-7000-8000-000000000001"}],"responses":{"204":{"description":"Schedule cancelled and the campaign returned to `draft`; no body is returned."},"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"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"campaigns:write"}},"/contact-lists":{"get":{"tags":["Contact Lists"],"summary":"List contact lists","description":"Returns your contact lists newest first, by creation time. Narrow the page with `type` or with `search` (case-insensitive substring of the name); the two filters combine.","operationId":"listContactLists","parameters":[{"name":"type","in":"query","description":"Keep only lists of this type.","required":false,"schema":{"$ref":"#/components/schemas/ContactListType"},"example":"blacklist"},{"name":"search","in":"query","description":"Case-insensitive substring filter on the list name; minimum 3 characters.","required":false,"schema":{"type":"string","maxLength":120,"minLength":3},"example":"Blocked"},{"name":"limit","in":"query","description":"Page size; defaults to 25, max 1000 (larger values are rejected).","required":false,"schema":{"type":"integer","format":"int32","default":25,"maximum":1000,"minimum":1},"example":25},{"name":"offset","in":"query","description":"Rows to skip before this page; defaults to 0.","required":false,"schema":{"type":"integer","format":"int32","minimum":0},"example":0}],"responses":{"200":{"description":"A page of contact lists, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageContactListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:read"},"post":{"tags":["Contact Lists"],"summary":"Create a contact list","description":"Creates an empty list under a name no other list of yours holds. `type` decides what the list is for and is fixed for its lifetime: `default` for a group you send to, `blacklist` for a suppression set covering the channels in `channels`. Fill it afterwards with `POST /contact-lists/{list_id}/contacts` or `PUT /contact-lists/{list_id}/contacts/{contact_id}`.","operationId":"createContactList","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListCreateRequest"}}},"required":true},"responses":{"201":{"description":"The list was created and is empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:write"}},"/contact-lists/{list_id}":{"get":{"tags":["Contact Lists"],"summary":"Get a contact list","description":"Return a single contact list by id, with the number of contacts currently in it.","operationId":"getContactList","parameters":[{"name":"list_id","in":"path","description":"Identifier of the contact list to return.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970006-0000-7000-8000-000000000001"}],"responses":{"200":{"description":"The requested contact list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:read"},"delete":{"tags":["Contact Lists"],"summary":"Delete a contact list","description":"Deletes the list: it stops being returned by reads, drops out of the `list_ids` of the contacts that were in it, and, if it was a `blacklist`, stops suppressing sends. The contacts themselves are kept and stay reachable on their own. The name becomes free for a new list.\n\n`Sintalk` is provisioned for you and cannot be deleted; deleting it is rejected with `409`.","operationId":"deleteContactList","parameters":[{"name":"list_id","in":"path","description":"Identifier of the contact list to delete.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970006-0000-7000-8000-000000000001"}],"responses":{"204":{"description":"Contact list deleted; no body is returned."},"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"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:write"},"patch":{"tags":["Contact Lists"],"summary":"Update a contact list","description":"Renames the list and, on a `blacklist` list, replaces the channels it suppresses on. `type` is fixed at creation and cannot be patched. Omitting `channels` leaves them as they are; `[]` clears them, which stops the list suppressing anything. Setting a non-empty `channels` on a `default` list is rejected with `422`, and taking a name another list already holds with `409`. Which contacts are in the list is changed through the membership endpoints, not here.\n\n`Sintalk` is provisioned for you and cannot be renamed; renaming it is rejected with `409`. Its channels can still be changed.","operationId":"updateContactList","parameters":[{"name":"list_id","in":"path","description":"Identifier of the contact list to update.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970006-0000-7000-8000-000000000001"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListPatchRequest"}}},"required":true},"responses":{"200":{"description":"The contact list after the update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListResponse"}}}},"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/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:write"}},"/contact-lists/{list_id}/contacts":{"get":{"tags":["Contact Lists"],"summary":"List the contacts in a list","description":"Returns the contacts that belong to this list, newest first by the contact's own creation time. Each entry is the full contact, so `list_ids` also shows the other lists it belongs to.","operationId":"listContactListContacts","parameters":[{"name":"list_id","in":"path","description":"Identifier of the contact list to read the contacts of.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970006-0000-7000-8000-000000000001"},{"name":"search","in":"query","description":"Case-insensitive substring filter on name or phone; minimum 3 characters.","required":false,"schema":{"type":"string","maxLength":120,"minLength":3},"example":"Ana"},{"name":"limit","in":"query","description":"Page size; defaults to 25, max 1000 (larger values are rejected).","required":false,"schema":{"type":"integer","format":"int32","default":25,"maximum":1000,"minimum":1},"example":25},{"name":"offset","in":"query","description":"Rows to skip before this page; defaults to 0.","required":false,"schema":{"type":"integer","format":"int32","minimum":0},"example":0}],"responses":{"200":{"description":"A page of the contacts in the list, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageContactResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:read"},"post":{"tags":["Contact Lists"],"summary":"Add a contact to the list by phone number","description":"Use this when you hold a phone number rather than a contact id. The number is normalized to canonical E.164 and matched against your contacts: the contact it finds is updated, and when there is none a contact is created — either way it ends up in this list, in one call. Omitted fields keep their stored value; `attributes`, when present, replaces the whole map. Repeating the call for the same number does not add a second membership.\n\nTo add a contact you already have the id of, without touching its fields, use `PUT /contact-lists/{list_id}/contacts/{contact_id}` instead.","operationId":"upsertContactInList","parameters":[{"name":"list_id","in":"path","description":"Identifier of the contact list to add the contact to.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970006-0000-7000-8000-000000000001"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpsertRequest"}}},"required":true},"responses":{"200":{"description":"A contact already held this phone number; it was updated and is now in the list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"201":{"description":"No contact held this phone number and one now does, added to the list. A contact deleted earlier under the same number is restored with its original id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:write"}},"/contact-lists/{list_id}/contacts/{contact_id}":{"put":{"tags":["Contact Lists"],"summary":"Add an existing contact to the list","description":"Adds a contact you already have, by id, to this list. There is no request body: only the membership is created, the contact's own fields are left untouched, and repeating the call changes nothing. Both the list and the contact must already exist, or the call is `404`.\n\nWhen you hold a phone number instead of an id, or want the contact created if it does not exist yet, use `POST /contact-lists/{list_id}/contacts`.","operationId":"addContactToList","parameters":[{"name":"list_id","in":"path","description":"Identifier of the contact list to add to.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970006-0000-7000-8000-000000000001"},{"name":"contact_id","in":"path","description":"Identifier of the contact to add.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970000-0000-7000-8000-000000000001"}],"responses":{"204":{"description":"The contact is in the list; no body is returned."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:write"},"delete":{"tags":["Contact Lists"],"summary":"Remove a contact from the list","description":"Drops the contact's membership of this list. The contact is kept, stays in every other list it belongs to, and remains reachable on its own. A contact that is not in the list is reported as `404`.","operationId":"removeContactFromList","parameters":[{"name":"list_id","in":"path","description":"Identifier of the contact list to remove from.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970006-0000-7000-8000-000000000001"},{"name":"contact_id","in":"path","description":"Identifier of the contact to remove.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970000-0000-7000-8000-000000000001"}],"responses":{"204":{"description":"The contact is no longer in the list; no body is returned."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:write"}},"/contacts":{"get":{"tags":["Contacts"],"summary":"List contacts","description":"Returns your contacts newest first, by creation time. Narrow the page with `search` (case-insensitive substring of name or phone) or with `phone` (exact match after normalization); the two filters combine.","operationId":"listContacts","parameters":[{"name":"search","in":"query","description":"Case-insensitive substring filter on name or phone; minimum 3 characters.","required":false,"schema":{"type":"string","maxLength":120,"minLength":3},"example":"Ana"},{"name":"phone","in":"query","description":"Exact-match phone filter. Common Brazilian formats are accepted and normalized before matching.","required":false,"schema":{"type":"string"},"example":"+5511900000000"},{"name":"limit","in":"query","description":"Page size; defaults to 25, max 1000 (larger values are rejected).","required":false,"schema":{"type":"integer","format":"int32","default":25,"maximum":1000,"minimum":1},"example":25},{"name":"offset","in":"query","description":"Rows to skip before this page; defaults to 0.","required":false,"schema":{"type":"integer","format":"int32","minimum":0},"example":0}],"responses":{"200":{"description":"A page of contacts, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageContactResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:read"},"post":{"tags":["Contacts"],"summary":"Create a contact","description":"Creates a contact, or updates the one already holding the same phone number. The submitted phone is normalized to canonical E.164 before matching, so every accepted format resolves to the same contact. Omitted fields keep their stored value; `attributes`, when present, replaces the whole map.","operationId":"upsertContact","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpsertRequest"}}},"required":true},"responses":{"200":{"description":"A contact with this phone number already existed and was updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"201":{"description":"No contact held this phone number and one now does. A contact deleted earlier under the same number is restored with its original id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:write"}},"/contacts/{contact_id}":{"get":{"tags":["Contacts"],"summary":"Get a contact","description":"Return a single contact by id.","operationId":"getContact","parameters":[{"name":"contact_id","in":"path","description":"Identifier of the contact to return.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970000-0000-7000-8000-000000000001"}],"responses":{"200":{"description":"The requested contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:read"},"delete":{"tags":["Contacts"],"summary":"Delete a contact","description":"Deletes the contact: it stops being returned by reads and is removed from every contact list it belonged to. Upserting the same phone number afterwards restores it with its original id.","operationId":"deleteContact","parameters":[{"name":"contact_id","in":"path","description":"Identifier of the contact to delete.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970000-0000-7000-8000-000000000001"}],"responses":{"204":{"description":"Contact deleted; no body is returned."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:write"},"patch":{"tags":["Contacts"],"summary":"Update a contact","description":"Updates the fields present in the body and leaves the omitted ones untouched. `attributes` is not merged: when present it replaces the whole map, and `{}` clears it. Changing `phone` to a number another contact already holds is rejected with `409`.","operationId":"updateContact","parameters":[{"name":"contact_id","in":"path","description":"Identifier of the contact to update.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970000-0000-7000-8000-000000000001"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactPatchRequest"}}},"required":true},"responses":{"200":{"description":"The contact after the update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"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/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"contacts:write"}},"/files/constraints":{"get":{"tags":["Files"],"summary":"Get file upload constraints","description":"Return the limits every upload is checked against: for each file kind the content types it accepts and its maximum size, plus the kinds each `usage` allows.\n\nRead it before requesting an upload URL: pick a `content_type` listed for a kind the intended `usage` permits, and keep `size_bytes` at or below that kind's `max_size_bytes`. Anything else is rejected by `POST /files/upload-url` with 415 or 413.","operationId":"getFileConstraints","responses":{"200":{"description":"Accepted content types and size limits, per kind and per usage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileConstraintsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"files:read"}},"/files/upload-url":{"post":{"tags":["Files"],"summary":"Request a presigned upload URL","description":"Step 1 of the three-step direct upload. File bytes never travel through this API; they go straight to storage.\n\n1. `POST /files/upload-url` — declare `usage`, `content_type` and `size_bytes`. The response carries the new `file_id` and a presigned `url`, and the file starts in status `pending`.\n2. `PUT` the bytes to that `url`, with `Content-Type` equal to the declared `content_type` and `Content-Length` equal to `size_bytes`. Both are signed into the URL, so storage rejects a `PUT` that deviates from either. Send no API credentials on this request.\n3. `POST /files/{file_id}/confirm` — the file flips to `uploaded` and can be used.\n\n`kind` is derived from `content_type` and must be one the declared `usage` allows; `size_bytes` must fit that kind's limit. `GET /files/constraints` lists both. Past `expires_at` the URL no longer works — request a new one, which creates a new file.","operationId":"createUploadUrl","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadUrlRequest"}}},"required":true},"responses":{"200":{"description":"Presigned upload URL and the id of the file it will hold.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadUrlResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"description":"size_bytes exceeds the limit for the derived kind.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"content_type unsupported or not allowed for the usage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"files:write"}},"/files/{file_id}/confirm":{"post":{"tags":["Files"],"summary":"Confirm a completed upload","description":"Step 3 of the three-step direct upload: once the `PUT` to the presigned URL succeeds, confirm the file so it can be used. `status` flips from `pending` to `uploaded`.\n\nConfirming a file that is already `uploaded` returns the same result, so a retry is safe. Confirming before the bytes reach storage returns 409 and leaves the file `pending`, so it can be confirmed again after the upload completes.","operationId":"confirmUpload","parameters":[{"name":"file_id","in":"path","description":"Identifier returned by `POST /files/upload-url`.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970001-0000-7000-8000-000000000001"}],"responses":{"200":{"description":"Upload confirmed; the file is `uploaded` and ready to use.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmUploadResponse"}}}},"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"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"files:write"}},"/files":{"get":{"tags":["Files"],"summary":"List files","description":"List your files, newest first. Filter by `kind` or `status`.","operationId":"listFiles","parameters":[{"name":"kind","in":"query","description":"Return only files of this kind.","required":false,"schema":{"$ref":"#/components/schemas/FileKind"},"example":"audio"},{"name":"status","in":"query","description":"Return only files in this status.","required":false,"schema":{"$ref":"#/components/schemas/FileStatus"},"example":"uploaded"},{"name":"limit","in":"query","description":"Page size; defaults to 25, max 1000 (larger values are rejected).","required":false,"schema":{"type":"integer","format":"int32","default":25,"maximum":1000,"minimum":1},"example":25},{"name":"offset","in":"query","description":"Rows to skip before this page; defaults to 0.","required":false,"schema":{"type":"integer","format":"int32","minimum":0},"example":0}],"responses":{"200":{"description":"Page of files, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageFile"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"files:read"}},"/files/{file_id}":{"get":{"tags":["Files"],"summary":"Read a file","description":"Returns one file's metadata and upload status.","operationId":"getFile","parameters":[{"name":"file_id","in":"path","description":"Identifier of the file to return.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970001-0000-7000-8000-000000000001"}],"responses":{"200":{"description":"The file.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"files:read"}},"/files/{file_id}/download-url":{"get":{"tags":["Files"],"summary":"Get a download URL","description":"Return a URL that serves the file's bytes. Files of kind `csv` are private: their URL is presigned and stops working at `expires_at`. Every other kind is served from a stable URL, with `expires_at` null.\n\nThe file must be confirmed first — a file still `pending` returns 409.","operationId":"getDownloadUrl","parameters":[{"name":"file_id","in":"path","description":"Identifier of the file to download.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970001-0000-7000-8000-000000000001"}],"responses":{"200":{"description":"URL serving the file's bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DownloadUrlResponse"}}}},"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"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"files:read"}},"/inbound-messages":{"get":{"tags":["Inbound Messages"],"summary":"List inbound messages","description":"Inbound messages are returned newest first, ordered by `received_at` descending. There is no implicit time window. Array-valued filters (`sender`, `paired_message_id`, `campaign_id`, `content_type`) are repeatable query parameters (e.g. `content_type=text&content_type=file`): values are OR-combined within a dimension and AND-combined across dimensions; each also accepts a single value.\n","operationId":"listInboundMessages","parameters":[{"name":"sender","in":"query","description":"Keep only messages sent from one of these phone numbers.","required":false,"schema":{"type":"array","items":{"type":"string"},"maxItems":100,"minItems":0},"example":["+5511900000000"]},{"name":"paired_message_id","in":"query","description":"Keep only messages replying to one of these messages you sent; up to 100 ids.","required":false,"schema":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100,"minItems":0},"example":["01970002-0000-7000-8000-000000000001"]},{"name":"campaign_id","in":"query","description":"Keep only messages replying to one of these campaigns; up to 100 ids.","required":false,"schema":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100,"minItems":0},"example":["01970005-0000-7000-8000-000000000001"]},{"name":"content_type","in":"query","description":"Keep only messages carrying one of these content shapes.","required":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/InboundContentType"}},"example":["text"]},{"name":"search","in":"query","description":"Case-insensitive substring filter over the message text; minimum 3 characters. Only text and suggestion messages carry text.","required":false,"schema":{"type":"string","maxLength":120,"minLength":3},"example":"confirmar"},{"name":"from","in":"query","description":"Keep only messages whose `received_at` is at or after this instant.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-07-01T12:30:45Z"},{"name":"to","in":"query","description":"Keep only messages whose `received_at` is at or before this instant.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-07-02T08:15:00Z"},{"name":"limit","in":"query","description":"Page size; defaults to 25, max 1000 (larger values are rejected).","required":false,"schema":{"type":"integer","format":"int32","default":25,"maximum":1000,"minimum":1},"example":25},{"name":"offset","in":"query","description":"Rows to skip before this page; defaults to 0.","required":false,"schema":{"type":"integer","format":"int32","minimum":0},"example":0}],"responses":{"200":{"description":"A page of inbound messages, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageInboundMessage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"messages:read"}},"/inbound-messages/{inbound_message_id}":{"get":{"tags":["Inbound Messages"],"summary":"Get an inbound message","description":"Returns a single inbound message with its content.","operationId":"getInboundMessage","parameters":[{"name":"inbound_message_id","in":"path","description":"Id of the inbound message to read.","required":true,"schema":{"type":"string","format":"uuid"},"example":"0197000a-0000-7000-8000-000000000001"}],"responses":{"200":{"description":"The inbound message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundMessage"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"messages:read"}},"/inbound-rcs-events":{"get":{"tags":["Inbound RCS Events"],"summary":"List inbound RCS events","description":"Inbound events are returned newest first, ordered by `received_at` descending. There is no implicit time window. Array-valued filters (`sender`, `paired_message_id`, `campaign_id`) are repeatable query parameters: values are OR-combined within a dimension and AND-combined across dimensions; each also accepts a single value.\n","operationId":"listInboundRcsEvents","parameters":[{"name":"sender","in":"query","description":"Keep only events raised from one of these phone numbers.","required":false,"schema":{"type":"array","items":{"type":"string"},"maxItems":100,"minItems":0},"example":["+5511900000000"]},{"name":"paired_message_id","in":"query","description":"Keep only events acting on one of these messages you sent; up to 100 ids.","required":false,"schema":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100,"minItems":0},"example":["01970002-0000-7000-8000-000000000001"]},{"name":"campaign_id","in":"query","description":"Keep only events acting on one of these campaigns; up to 100 ids.","required":false,"schema":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100,"minItems":0},"example":["01970005-0000-7000-8000-000000000001"]},{"name":"search","in":"query","description":"Case-insensitive substring filter over the action label; minimum 3 characters.","required":false,"schema":{"type":"string","maxLength":120,"minLength":3},"example":"fatura"},{"name":"from","in":"query","description":"Keep only events whose `received_at` is at or after this instant.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-07-01T12:30:45Z"},{"name":"to","in":"query","description":"Keep only events whose `received_at` is at or before this instant.","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-07-02T08:15:00Z"},{"name":"limit","in":"query","description":"Page size; defaults to 25, max 1000 (larger values are rejected).","required":false,"schema":{"type":"integer","format":"int32","default":25,"maximum":1000,"minimum":1},"example":25},{"name":"offset","in":"query","description":"Rows to skip before this page; defaults to 0.","required":false,"schema":{"type":"integer","format":"int32","minimum":0},"example":0}],"responses":{"200":{"description":"A page of inbound events, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageInboundRcsEvent"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"messages:read"}},"/inbound-rcs-events/{inbound_rcs_event_id}":{"get":{"tags":["Inbound RCS Events"],"summary":"Get an inbound RCS event","description":"Returns a single inbound event.","operationId":"getInboundRcsEvent","parameters":[{"name":"inbound_rcs_event_id","in":"path","description":"Id of the inbound event to read.","required":true,"schema":{"type":"string","format":"uuid"},"example":"0197000c-0000-7000-8000-000000000001"}],"responses":{"200":{"description":"The inbound event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRcsEvent"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"messages:read"}},"/messages":{"get":{"tags":["Messages"],"summary":"List messages","description":"Messages are returned newest first, ordered by `created_at` descending. There is no implicit default time window. Array-valued filters (`channel`, `status`, `template_id`, `campaign_id`) are repeatable query parameters (e.g. `status=sent&status=delivered`): values are OR-combined within a dimension and AND-combined across dimensions; each also accepts a single value. `status_history` is empty on list items — read a single message to get it.\n","operationId":"listMessages","parameters":[{"name":"channel","in":"query","description":"Keep only messages sent over one of these channels.","required":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/Channel"}}},{"name":"status","in":"query","description":"Keep only messages currently in one of these delivery states.","required":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/MessageStatus"}}},{"name":"template_id","in":"query","description":"Keep only messages rendered from one of these templates; up to 100 ids.","required":false,"schema":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100,"minItems":0}},{"name":"campaign_id","in":"query","description":"Keep only messages sent as part of one of these campaigns; up to 100 ids.","required":false,"schema":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100,"minItems":0}},{"name":"search","in":"query","description":"Case-insensitive substring filter; minimum 3 characters.","required":false,"schema":{"type":"string","maxLength":120,"minLength":3}},{"name":"from","in":"query","description":"Keep only messages whose `created_at` is at or after this instant.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","description":"Keep only messages whose `created_at` is at or before this instant.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","description":"Page size; defaults to 25, max 1000 (larger values are rejected).","required":false,"schema":{"type":"integer","format":"int32","default":25,"maximum":1000,"minimum":1},"example":25},{"name":"offset","in":"query","description":"Rows to skip before this page; defaults to 0.","required":false,"schema":{"type":"integer","format":"int32","minimum":0},"example":0}],"responses":{"200":{"description":"A page of messages, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageMessageResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"messages:read"},"post":{"tags":["Messages"],"summary":"Send messages","description":"Send one body to many recipients in a single request. The `body` is either inline content (`channel` + `content`) or a saved template (`template_id`); its channel applies to the whole batch, and each entry in `recipients` carries its own destination and its own `variables`, substituted into the body for that recipient alone.\n\nThe response carries one result per recipient, in request order, each with the message id created for it and the status it was created with. A mixed outcome is normal:\n\n- `pending` — accepted and queued for dispatch.\n- `blocked` — the recipient is suppressed on this channel, so it was never dispatched and was not charged.\n- `error` — it never reached a carrier: no sender configured for the channel, referenced media unavailable, a variable the body references missing for that recipient, or a body that rendered empty.\n\nOnly `pending` results go on to a carrier; the other two are already terminal. Send an `Idempotency-Key` to make a retry safe: replaying the key returns the original results instead of sending — and charging — a second time.","operationId":"sendMessages","parameters":[{"name":"Idempotency-Key","in":"header","description":"A client-generated key that makes this request safe to retry. Replaying a key returns the original response instead of repeating the write, so a timeout or a network failure cannot send twice or charge twice. Reusing a key with a different request body is refused with `409 conflict`. Keys are scoped to your account and retained for 24 hours.","required":false,"schema":{"type":"string","maxLength":255}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageSendRequest"}}},"required":true},"responses":{"202":{"description":"Batch accepted: one result per recipient, with the message id created for it and its initial status. Accepted means queued, not delivered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageSendResponse"}}}},"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/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"messages:send"}},"/messages/{message_id}":{"get":{"tags":["Messages"],"summary":"Get a message","description":"Return a single message with `status_history`, the ordered record of every status it has entered.","operationId":"getMessage","parameters":[{"name":"message_id","in":"path","description":"Id of the message to read, as returned by a send.","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The message and its full status history.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"messages:read"}},"/templates":{"get":{"tags":["Templates"],"summary":"List templates","description":"Returns your templates newest first, by creation time. Narrow the page with `channel` or `search`; the two filters combine.","operationId":"listTemplates","parameters":[{"name":"channel","in":"query","description":"Keep only templates for this channel.","required":false,"schema":{"$ref":"#/components/schemas/Channel"},"example":"rcs"},{"name":"search","in":"query","description":"Case-insensitive substring filter on the template name; minimum 3 characters.","required":false,"schema":{"type":"string","maxLength":120,"minLength":3},"example":"Promo"},{"name":"presign","in":"query","description":"When true, resolve the read-only URL fields of file-backed content — `audio_url` on voice content and `card.media_url` on an RCS card — so the referenced media can be fetched without a separate download-URL call. They are null otherwise.","required":false,"schema":{"type":"boolean"},"example":true},{"name":"limit","in":"query","description":"Page size; defaults to 25, max 1000 (larger values are rejected).","required":false,"schema":{"type":"integer","format":"int32","default":25,"maximum":1000,"minimum":1},"example":25},{"name":"offset","in":"query","description":"Rows to skip before this page; defaults to 0.","required":false,"schema":{"type":"integer","format":"int32","minimum":0},"example":0}],"responses":{"200":{"description":"A page of templates, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageTemplateResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"templates:read"},"post":{"tags":["Templates"],"summary":"Create a template","description":"Create a message template. `content` is read against the schema `channel` selects: text for `sms`, a `file_id` reference for `voice`, and for `rcs` one of the three shapes identified by their fields — text (`text`), card (`card`), or carousel (`cards`). Every file `content` references must already be uploaded and of a kind the slot accepts.\n\nThe `{{variable}}` placeholders found in `content` are extracted into `variables`; each send then supplies a value for every one of them, per recipient. `name` is unique per channel among live templates. Content, channel, and follow-up rules are fixed at creation — only `name` can be changed afterwards.","operationId":"createTemplate","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateCreateRequest"}}},"required":true},"responses":{"201":{"description":"The template as stored, with the variables extracted from its content.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateResponse"}}}},"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/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"templates:write"}},"/templates/{template_id}":{"get":{"tags":["Templates"],"summary":"Get a template","description":"Return a single template by id, with its variables and follow-up rules.","operationId":"getTemplate","parameters":[{"name":"template_id","in":"path","description":"Identifier of the template to return.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970003-0000-7000-8000-000000000001"},{"name":"presign","in":"query","description":"When true, resolve the read-only URL fields of file-backed content — `audio_url` on voice content and `card.media_url` on an RCS card — so the referenced media can be fetched without a separate download-URL call. They are null otherwise.","required":false,"schema":{"type":"boolean"},"example":true}],"responses":{"200":{"description":"The requested template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"templates:read"},"delete":{"tags":["Templates"],"summary":"Delete a template","description":"Archives the template: it stops being returned by reads and can no longer be named as the target of a new follow-up rule. Messages already created from it remain readable, and its name is free to reuse on the same channel.","operationId":"deleteTemplate","parameters":[{"name":"template_id","in":"path","description":"Identifier of the template to delete.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970003-0000-7000-8000-000000000001"}],"responses":{"204":{"description":"Template deleted; no body is returned."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"templates:write"},"patch":{"tags":["Templates"],"summary":"Update a template","description":"Updates are limited to `name`; content, channel, and follow-up rules are fixed at creation. The new name must be unique per channel among live templates.","operationId":"updateTemplate","parameters":[{"name":"template_id","in":"path","description":"Identifier of the template to rename.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970003-0000-7000-8000-000000000001"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateRenameRequest"}}},"required":true},"responses":{"200":{"description":"The template after the rename.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateResponse"}}}},"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/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"templates:write"}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhook endpoints","description":"Returns your webhook endpoints, newest first. Narrow with `event` or `enabled`; the filters combine.","operationId":"listWebhooks","parameters":[{"name":"event","in":"query","description":"Keep only endpoints subscribed to this event type.","required":false,"schema":{"$ref":"#/components/schemas/WebhookEventType"},"example":"message_update"},{"name":"enabled","in":"query","description":"Keep only enabled (true) or disabled (false) endpoints.","required":false,"schema":{"type":"boolean"},"example":true},{"name":"limit","in":"query","description":"Page size; defaults to 25, max 1000 (larger values are rejected).","required":false,"schema":{"type":"integer","format":"int32","default":25,"maximum":1000,"minimum":1},"example":25},{"name":"offset","in":"query","description":"Rows to skip before this page; defaults to 0.","required":false,"schema":{"type":"integer","format":"int32","minimum":0},"example":0}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageWebhookEndpointResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"webhooks:read"},"post":{"tags":["Webhooks"],"summary":"Create a webhook endpoint","description":"Registers a webhook endpoint. Deliveries carry recipients' phone numbers in full, so registering one also requires the `phones:view` scope. The response is the only place the signing secret ever appears — store it before doing anything else.","operationId":"createWebhook","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreateRequest"}}},"required":true},"responses":{"201":{"description":"The endpoint was registered; this is the only response that carries the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecretResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"webhooks:write"}},"/webhooks/{webhook_id}":{"get":{"tags":["Webhooks"],"summary":"Get a webhook endpoint","description":"Returns a single webhook endpoint by id; the signing secret is not included.","operationId":"getWebhook","parameters":[{"name":"webhook_id","in":"path","description":"Identifier of the webhook endpoint to return.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970004-0000-7000-8000-000000000001"}],"responses":{"200":{"description":"The requested webhook endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"webhooks:read"},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook endpoint","description":"Deletes the endpoint and stops its deliveries.","operationId":"deleteWebhook","parameters":[{"name":"webhook_id","in":"path","description":"Identifier of the webhook endpoint to delete.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970004-0000-7000-8000-000000000001"}],"responses":{"204":{"description":"Webhook endpoint deleted; no body is returned."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"webhooks:write"},"patch":{"tags":["Webhooks"],"summary":"Update a webhook endpoint","description":"Changes the endpoint's settings; omitted fields keep their value.","operationId":"updateWebhook","parameters":[{"name":"webhook_id","in":"path","description":"Identifier of the webhook endpoint to update.","required":true,"schema":{"type":"string","format":"uuid"},"example":"01970004-0000-7000-8000-000000000001"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPatchRequest"}}},"required":true},"responses":{"200":{"description":"The webhook endpoint after the update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/UnexpectedError"}},"security":[{"ApiKeyAuth":[]},{"ApiKeyBearerAuth":[]}],"x-required-scope":"webhooks:write"}}},"webhooks":{"message_update":{"post":{"tags":["Webhooks"],"summary":"Message status update","description":"Sent to every enabled webhook endpoint when a message's status changes — on the interim `sent` transition and on the terminal status (`delivered`/`not_delivered`/`error`). The body is a JSON array of one or more events, so a single request may batch several status changes for your account; iterate it. Delivery is at-least-once: each event's `id` is stable across retries of the same event, so it can be used to discard a duplicate. A non-2xx response or a timeout is retried with backoff; a request is abandoned after 5 attempts.\n\nVerify `X-Sintalk-Signature` before trusting the body. It is `v1=` followed by the hex HMAC-SHA256, keyed with the endpoint's signing secret, over `X-Sintalk-Timestamp` + `.` + the raw request body (the whole array). Compare with a constant-time equality check, and reject a timestamp too far from your clock to blunt replay.","operationId":"messageUpdateEvent","parameters":[{"name":"X-Sintalk-Timestamp","in":"header","description":"Unix seconds at signing; part of the signed payload.","required":true,"schema":{"type":"string"}},{"name":"X-Sintalk-Signature","in":"header","description":"`v1=<hex>` HMAC-SHA256 of `timestamp + \".\" + body`, keyed with the endpoint's signing secret.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MessageUpdateEvent"}}}},"required":true},"responses":{"2XX":{"description":"Any 2xx acknowledges receipt. Anything else is retried."}},"security":[]}}},"components":{"schemas":{"CampaignScheduleRequest":{"type":"object","description":"The instant a campaign is to launch itself at.","properties":{"scheduled_at":{"type":"string","format":"date-time","description":"When the campaign launches, RFC 3339 timestamp. Must be strictly in the future; sending a new value for an already scheduled campaign reschedules it.","example":"2026-08-01T13:00:00Z"}},"required":["scheduled_at"]},"CampaignResponse":{"type":"object","description":"A campaign: one template sent to every contact of one list over one channel, with its lifecycle state and the timestamps of each transition it has made.","properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the campaign.","example":"01970005-0000-7000-8000-000000000001"},"name":{"type":"string","description":"Campaign name, as given at creation or by a later rename.","example":"July Promo"},"channel":{"$ref":"#/components/schemas/Channel","description":"Channel the campaign sends over, the same one its template targets.","example":"sms"},"contact_list":{"$ref":"#/components/schemas/ContactListRef","description":"Contact list the campaign sends to. Fixed at creation."},"template":{"$ref":"#/components/schemas/TemplateRef","description":"Template rendered for every recipient. Fixed at creation."},"status":{"$ref":"#/components/schemas/CampaignStatus","description":"Current lifecycle state of the campaign.","example":"completed"},"scheduled_at":{"type":["string","null"],"format":"date-time","description":"Instant the campaign launches itself at; set while it is `scheduled` and cleared when the schedule is cancelled.","example":"2026-07-01T13:00:00Z"},"started_at":{"type":["string","null"],"format":"date-time","description":"Instant the launch was claimed, which is also the instant the contact list was fixed for this run; null until the campaign first runs.","example":"2026-07-01T13:00:00Z"},"completed_at":{"type":["string","null"],"format":"date-time","description":"Instant the last message settled; null until the campaign is `completed`.","example":"2026-07-01T14:00:00Z"},"created_at":{"type":"string","format":"date-time","description":"When the campaign was created, which is what the `created_from`/`created_to` filters bound and what the list is ordered by.","example":"2026-07-01T12:30:45Z"},"updated_at":{"type":"string","format":"date-time","description":"When the campaign last changed, including each lifecycle transition.","example":"2026-07-02T08:15:00Z"}},"required":["channel","created_at","id","name","status","updated_at"]},"CampaignStatus":{"type":"string","description":"Lifecycle state of a campaign, distinct from the delivery status of each message it sends.\n\n- `draft` — created but never launched, and no messages exist for it yet. It can be renamed, scheduled, launched, or deleted.\n- `scheduled` — `scheduled_at` holds a future instant and the campaign launches itself then. Cancelling the schedule returns it to `draft`.\n- `running` — the launch was claimed and `started_at` stamped; the contact list is fixed as of that instant and one message per member is being created and sent.\n- `completed` — every message the campaign created reached a terminal delivery state, and `completed_at` is stamped.\n- `error` — the run stopped before every message was created. Launching it again retries the run without messaging a recipient it already messaged.\n\n`draft` and `error` are the only launchable states; scheduling accepts those two plus `scheduled`, which reschedules. Only `draft` and `scheduled` campaigns can be deleted.","enum":["draft","scheduled","running","completed","error"],"example":"completed"},"Channel":{"type":"string","description":"Delivery channel a message is sent over; it fixes the content shape and the sender used.\n\n- `rcs` — rich cards, carousels and suggested actions on supported handsets.\n- `sms` — plain text, billed per segment: 160 characters for a single-part GSM-7 message, 153 per part once it splits. Any character outside GSM-7 forces UCS-2 and drops those limits to 70 and 67 — note that lowercase `ç á í ó ú ã õ ê ô` are not GSM-7, so most Portuguese text bills as UCS-2.\n- `voice` — an audio file played to the recipient.","enum":["rcs","sms","voice"],"example":"rcs"},"ContactListRef":{"type":"object","description":"Identity of the contact list a campaign sends to.","properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the contact list.","example":"01970006-0000-7000-8000-000000000001"},"name":{"type":"string","description":"Name of the contact list.","example":"VIP Customers"}},"required":["id","name"]},"TemplateRef":{"type":"object","description":"Identity of the template a campaign sends.","properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the template.","example":"01970003-0000-7000-8000-000000000001"},"name":{"type":"string","description":"Name of the template.","example":"Welcome Template"}},"required":["id","name"]},"WebhookCreateRequest":{"type":"object","description":"Registers a webhook endpoint to receive event deliveries.","properties":{"url":{"type":"string","format":"uri","description":"HTTPS URL deliveries are POSTed to.","example":"https://example.com/webhooks/sintalk","pattern":"^https://.*"},"enabled":{"type":"boolean","default":false,"description":"Whether the endpoint starts receiving deliveries immediately; defaults to false.","example":true},"events":{"type":"array","description":"Event types delivered to this endpoint; at least one.","items":{"$ref":"#/components/schemas/WebhookEventType"},"maxItems":2147483647,"minItems":1,"uniqueItems":true}},"required":["events","url"]},"WebhookEventType":{"type":"string","description":"Event types an endpoint can subscribe to. `message_update` fires when a message's delivery status changes.","enum":["message_update"]},"WebhookEndpointWithSecretResponse":{"type":"object","description":"A webhook endpoint plus its signing secret, returned only on creation.","properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the webhook endpoint.","example":"01970004-0000-7000-8000-000000000001"},"url":{"type":"string","format":"uri","description":"HTTPS URL deliveries are POSTed to.","example":"https://example.com/webhooks/sintalk"},"enabled":{"type":"boolean","description":"Whether deliveries are currently sent.","example":true},"events":{"type":"array","description":"Event types delivered to this endpoint.","items":{"$ref":"#/components/schemas/WebhookEventType"},"uniqueItems":true},"secret":{"type":"string","description":"Secret that signs each delivery in the `X-Sintalk-Signature` header. Returned only in this response; store it now — it cannot be retrieved again.","example":"whsec_xxx"},"created_at":{"type":"string","format":"date-time","description":"Instant the endpoint was created, UTC.","example":"2026-07-01T12:30:45Z"},"updated_at":{"type":"string","format":"date-time","description":"Instant the endpoint's settings last changed, UTC.","example":"2026-07-02T08:15:00Z"}},"required":["created_at","enabled","events","id","secret","updated_at","url"]},"Content":{"description":"Channel-specific message content. The shape is fixed by the `channel`, and content whose fields do not fit that channel's schema is rejected:\n\n- `rcs` → `RCS` — a text, card, or carousel, told apart by its fields.\n- `sms` → `SMS` — `{ \"text\": \"…\" }`.\n- `voice` → `Voice` — `{ \"file_id\": \"…\" }`.\n\nAny string in the content may carry `{{variable}}` placeholders. A variable name starts with a letter or underscore and continues with letters, digits, or underscores, up to 255 characters; anything else is rejected. Each send supplies a value for every one of them, per recipient.","oneOf":[{"$ref":"#/components/schemas/SMS"},{"$ref":"#/components/schemas/Voice"},{"$ref":"#/components/schemas/RCS"}]},"FollowUpRuleCreate":{"type":"object","description":"Rule that sends a second template when a send of the template being created reaches `trigger_status`. The follow-up goes to the same recipient with the same variable values, and does not itself fire further rules.","properties":{"trigger_status":{"$ref":"#/components/schemas/TriggerStatus","description":"Send outcome that fires this rule. A template carries at most one rule per outcome.","example":"delivered"},"target_template_id":{"type":"string","format":"uuid","description":"Identifier of the template to send when the outcome occurs. Must be an existing template other than the one being created.","example":"01970003-0000-7000-8000-000000000004"}},"required":["target_template_id","trigger_status"]},"RCS":{"type":"object","description":"Message content for the `rcs` channel. There is no discriminator property — the variant is identified by the fields present:\n\n- `RCSText` — a text bubble; carries `text`.\n- `RCSCard` — one rich card; carries `card`.\n- `RCSCarousel` — 2 to 10 cards swiped horizontally; carries `cards`.\n\nEvery variant accepts `suggestions`, the chips shown under the message.","oneOf":[{"$ref":"#/components/schemas/RCSText"},{"$ref":"#/components/schemas/RCSCard"},{"$ref":"#/components/schemas/RCSCarousel"}],"title":"RCS"},"RCSCard":{"type":"object","description":"RCS content carrying one rich card. Identified by its `card` field. The card's own chips are set on `card.suggestions` (up to 4); the top-level `suggestions` are the message-level chips shown under the message (up to 11).","properties":{"card":{"$ref":"#/components/schemas/RCSCardContent","description":"The card's content and its own suggestion chips."},"suggestions":{"type":"array","description":"Suggestion chips, up to 11, shown under the message. This is the message-level list, distinct from any suggestions carried inside the content itself.","items":{"$ref":"#/components/schemas/RCSSuggestion"},"maxItems":11,"minItems":0}},"required":["card"],"title":"Card"},"RCSCardAlignment":{"type":"string","description":"Horizontal alignment of the media on an RCS card.","enum":["left","right"],"example":"left"},"RCSCardContent":{"type":"object","description":"A rich card's content: text and media, plus the card's own suggestion chips. At least one of `title`, `description`, or `media_file_id` must be present — content with none of the three is rejected. Blank text counts as absent.","properties":{"title":{"type":"string","description":"Card heading, up to 200 characters, including any `{{variable}}` placeholders.","example":"Promo card","maxLength":200,"minLength":0},"description":{"type":"string","description":"Card body text, up to 2000 characters, including any `{{variable}}` placeholders.","example":"Enjoy {{discount}}% off","maxLength":2000,"minLength":0},"media_file_id":{"type":"string","format":"uuid","description":"Identifier of the media shown on the card. Must be an uploaded file of kind `image` or `video`.","example":"01970001-0000-7000-8000-000000000001"},"media_url":{"type":["string","null"],"description":"Download URL for `media_file_id`. Server-populated and rejected on input; returned when `presign=true`, null otherwise.","example":"https://files.example.com/01970003-0000-7000-8000-000000000005","readOnly":true},"alignment":{"$ref":"#/components/schemas/RCSCardAlignment","description":"Horizontal image alignment. Orients the card horizontally when the card also carries media; otherwise the card is oriented vertically and the alignment has no visible effect. Defaults to left.","example":"left"},"height":{"$ref":"#/components/schemas/RCSMediaHeight","description":"Media height. Applies only when the card carries media; defaults to medium.","example":"medium"},"suggestions":{"type":"array","description":"The card's own suggestion chips, up to 4, shown on the card.","items":{"$ref":"#/components/schemas/RCSSuggestion"},"maxItems":4,"minItems":0}},"title":"Card content"},"RCSCarousel":{"type":"object","description":"RCS content carrying a horizontally swiped set of cards. Identified by its `cards` field; every card is rendered at the same width.","properties":{"width":{"$ref":"#/components/schemas/RCSCarouselWidth","description":"Card width applied to every card in the carousel. Defaults to medium.","example":"medium"},"cards":{"type":"array","description":"The cards in the carousel; 2 to 10 `RCSCardContent` entries.","items":{"$ref":"#/components/schemas/RCSCardContent"},"maxItems":10,"minItems":2},"suggestions":{"type":"array","description":"Suggestion chips, up to 11, shown under the message. This is the message-level list, distinct from any suggestions carried inside the content itself.","items":{"$ref":"#/components/schemas/RCSSuggestion"},"maxItems":11,"minItems":0}},"required":["cards"],"title":"Carousel"},"RCSCarouselWidth":{"type":"string","description":"Card width applied to every card in an RCS carousel.","enum":["small","medium"],"example":"small"},"RCSCreateCalendar":{"type":"object","description":"Suggestion chip that offers a calendar event when tapped. Selected by `type: create_calendar`.","properties":{"type":{"type":"string","description":"Discriminator for this suggestion shape; always `create_calendar`.","enum":["create_calendar"],"example":"create_calendar"},"create_calendar":{"$ref":"#/components/schemas/RCSCreateCalendarAction","description":"The event this chip offers to add."}},"required":["create_calendar","type"],"title":"Create calendar"},"RCSCreateCalendarAction":{"type":"object","description":"Label, payload, and event details of a create-calendar chip. `end_time` must be after `start_time`.","properties":{"text":{"type":"string","description":"Label shown on the chip, up to 25 characters.","example":"Save the date","maxLength":25,"minLength":0},"postback_data":{"type":"string","description":"Opaque payload the handset returns when the chip is tapped, up to 2048 characters.","example":"create-calendar","maxLength":2048,"minLength":0},"title":{"type":"string","description":"Event title, up to 1024 characters.","example":"Promo launch","maxLength":1024,"minLength":0},"start_time":{"type":"string","format":"date-time","description":"Event start, RFC 3339 timestamp.","example":"2026-08-01T09:00:00Z"},"end_time":{"type":"string","format":"date-time","description":"Event end, RFC 3339 timestamp. Must be after start_time.","example":"2026-08-01T10:00:00Z"},"description":{"type":"string","description":"Event description, up to 1024 characters.","example":"Launch event","maxLength":1024,"minLength":0}},"required":["end_time","postback_data","start_time","text","title"]},"RCSDial":{"type":"object","description":"Suggestion chip that starts a call when tapped. Selected by `type: dial`.","properties":{"type":{"type":"string","description":"Discriminator for this suggestion shape; always `dial`.","enum":["dial"],"example":"dial"},"dial":{"$ref":"#/components/schemas/RCSDialAction","description":"The number this chip dials."}},"required":["dial","type"],"title":"Dial"},"RCSDialAction":{"type":"object","description":"Label, payload, and number of a dial chip.","properties":{"text":{"type":"string","description":"Label shown on the chip, up to 25 characters.","example":"Call us","maxLength":25,"minLength":0},"postback_data":{"type":"string","description":"Opaque payload the handset returns when the chip is tapped, up to 2048 characters.","example":"dial-us","maxLength":2048,"minLength":0},"phone":{"type":"string","description":"Brazilian mobile number. Common formats are accepted (parentheses, dashes, spaces, with or without +55); the value must resolve to a valid canonical mobile. It is delivered to the carrier in canonical E.164 and returned as submitted.","example":"+5511900000000"}},"required":["phone","postback_data","text"]},"RCSMediaHeight":{"type":"string","description":"Height the media renders at on an RCS card.","enum":["short","medium","tall"],"example":"medium"},"RCSOpenUrl":{"type":"object","description":"Suggestion chip that opens a web link when tapped. Selected by `type: open_url`.","properties":{"type":{"type":"string","description":"Discriminator for this suggestion shape; always `open_url`.","enum":["open_url"],"example":"open_url"},"open_url":{"$ref":"#/components/schemas/RCSOpenUrlAction","description":"The link this chip opens."}},"required":["open_url","type"],"title":"Open URL"},"RCSOpenUrlAction":{"type":"object","description":"Label, payload, and target of an open-url chip.","properties":{"text":{"type":"string","description":"Label shown on the chip, up to 25 characters.","example":"Open site","maxLength":25,"minLength":0},"postback_data":{"type":"string","description":"Opaque payload the handset returns when the chip is tapped, up to 2048 characters.","example":"open-site","maxLength":2048,"minLength":0},"url":{"type":"string","format":"uri","description":"Link to open. Must be a well-formed `http` or `https` URL; other schemes are rejected.","example":"https://shop.example.com/promo","minLength":1},"application":{"$ref":"#/components/schemas/RCSUrlApplication","description":"Where the link opens: the handset browser, or an in-app webview.","example":"webview"},"webview_view_mode":{"$ref":"#/components/schemas/RCSWebviewViewMode","description":"Height of the webview. Honoured only with application=webview.","example":"tall"}},"required":["postback_data","text","url"]},"RCSReply":{"type":"object","description":"Suggestion chip that sends a canned reply back when tapped. Selected by `type: reply`.","properties":{"type":{"type":"string","description":"Discriminator for this suggestion shape; always `reply`.","enum":["reply"],"example":"reply"},"reply":{"$ref":"#/components/schemas/RCSReplyAction","description":"The reply this chip sends."}},"required":["reply","type"],"title":"Reply"},"RCSReplyAction":{"type":"object","description":"Label and payload of a reply chip.","properties":{"text":{"type":"string","description":"Label shown on the chip and sent as the reply text, up to 25 characters.","example":"Yes","maxLength":25,"minLength":0},"postback_data":{"type":"string","description":"Opaque payload the handset returns when the chip is tapped, up to 2048 characters.","example":"reply-yes","maxLength":2048,"minLength":0}},"required":["postback_data","text"]},"RCSRequestLocation":{"type":"object","description":"Suggestion chip that asks the recipient to share their location. Selected by `type: request_location`.","properties":{"type":{"type":"string","description":"Discriminator for this suggestion shape; always `request_location`.","enum":["request_location"],"example":"request_location"},"request_location":{"$ref":"#/components/schemas/RCSRequestLocationAction","description":"The location request this chip raises."}},"required":["request_location","type"],"title":"Request location"},"RCSRequestLocationAction":{"type":"object","description":"Label and payload of a location-request chip.","properties":{"text":{"type":"string","description":"Label shown on the chip, up to 25 characters.","example":"Share location","maxLength":25,"minLength":0},"postback_data":{"type":"string","description":"Opaque payload the handset returns when the chip is tapped, up to 2048 characters.","example":"request-location","maxLength":2048,"minLength":0}},"required":["postback_data","text"]},"RCSShowLocation":{"type":"object","description":"Suggestion chip that opens a map pin when tapped. Selected by `type: show_location`.","properties":{"type":{"type":"string","description":"Discriminator for this suggestion shape; always `show_location`.","enum":["show_location"],"example":"show_location"},"show_location":{"$ref":"#/components/schemas/RCSShowLocationAction","description":"The place this chip pins on the map."}},"required":["show_location","type"],"title":"Show location"},"RCSShowLocationAction":{"type":"object","description":"Label, payload, and coordinates of a show-location chip.","properties":{"text":{"type":"string","description":"Label shown on the chip, up to 25 characters.","example":"Our store","maxLength":25,"minLength":0},"postback_data":{"type":"string","description":"Opaque payload the handset returns when the chip is tapped, up to 2048 characters.","example":"show-location","maxLength":2048,"minLength":0},"latitude":{"type":"number","format":"double","description":"Latitude of the pin, in decimal degrees.","example":-8.05,"maximum":90,"minimum":-90},"longitude":{"type":"number","format":"double","description":"Longitude of the pin, in decimal degrees.","example":-34.9,"maximum":180,"minimum":-180},"label":{"type":"string","description":"Name shown on the pin, up to 100 characters.","example":"Recife store","maxLength":100,"minLength":0}},"required":["latitude","longitude","postback_data","text"]},"RCSSuggestion":{"description":"A tappable chip offered with an RCS message, selected by the `type` discriminator. The action fields live in a nested object named after the type:\n\n- `reply` → `RCSReply` — sends a canned reply back.\n- `open_url` → `RCSOpenUrl` — opens a web link.\n- `dial` → `RCSDial` — starts a call to a number.\n- `request_location` → `RCSRequestLocation` — asks the recipient to share their location.\n- `show_location` → `RCSShowLocation` — opens a map pin.\n- `create_calendar` → `RCSCreateCalendar` — offers a calendar event.\n\nA message accepts up to 11 chips and each card up to 4.","discriminator":{"propertyName":"type","mapping":{"reply":"#/components/schemas/RCSReply","open_url":"#/components/schemas/RCSOpenUrl","dial":"#/components/schemas/RCSDial","request_location":"#/components/schemas/RCSRequestLocation","show_location":"#/components/schemas/RCSShowLocation","create_calendar":"#/components/schemas/RCSCreateCalendar"}},"oneOf":[{"$ref":"#/components/schemas/RCSReply"},{"$ref":"#/components/schemas/RCSOpenUrl"},{"$ref":"#/components/schemas/RCSDial"},{"$ref":"#/components/schemas/RCSRequestLocation"},{"$ref":"#/components/schemas/RCSShowLocation"},{"$ref":"#/components/schemas/RCSCreateCalendar"}]},"RCSText":{"type":"object","description":"RCS content carrying a text bubble. Identified by its `text` field.","properties":{"text":{"type":"string","description":"Bubble text, up to 2500 characters, including any `{{variable}}` placeholders.","example":"Your code is {{code}}","maxLength":2500,"minLength":0},"suggestions":{"type":"array","description":"Suggestion chips, up to 11, shown under the message. This is the message-level list, distinct from any suggestions carried inside the content itself.","items":{"$ref":"#/components/schemas/RCSSuggestion"},"maxItems":11,"minItems":0}},"required":["text"],"title":"Text"},"RCSUrlApplication":{"type":"string","description":"Where an open-url suggestion opens its link: the device browser or an in-app webview.","enum":["browser","webview"],"example":"browser"},"RCSWebviewViewMode":{"type":"string","description":"Height of the in-app webview an open-url suggestion opens.","enum":["tall","full"],"example":"tall"},"SMS":{"type":"object","description":"Message content for the `sms` channel: a single text field.","properties":{"text":{"type":"string","description":"Message text, up to 1600 characters, including any `{{variable}}` placeholders.","example":"Hello {{name}}, welcome aboard!","maxLength":1600,"minLength":0}},"required":["text"],"title":"SMS"},"TemplateCreateRequest":{"type":"object","description":"A message template to create. `content` is read against the schema `channel` selects, and every file the content references must already be uploaded and of a kind the slot accepts. The `{{variable}}` placeholders found in `content` are extracted into the template's `variables`. Only `name` can be changed afterwards.","properties":{"name":{"type":"string","description":"Template name, 3 to 120 characters. Unique per channel among live templates.","example":"Promo RCS card","maxLength":120,"minLength":3},"channel":{"$ref":"#/components/schemas/Channel","description":"Channel this template is sent over. Selects the accepted content shape.","example":"rcs"},"content":{"$ref":"#/components/schemas/Content","description":"Channel-specific content, read against the schema `channel` selects."},"follow_up_rules":{"type":"array","description":"Rules that send another template when a send of this one reaches a given outcome; at most one rule per outcome.","items":{"$ref":"#/components/schemas/FollowUpRuleCreate"}}},"required":["channel","content","name"]},"TriggerStatus":{"type":"string","description":"Outcome of a send that fires a follow-up rule.\n\n- `sent` — the carrier accepted the message.\n- `delivered` — the carrier confirmed delivery to the handset.\n- `not_delivered` — the carrier reported it could not be delivered (rejected, undeliverable, or expired).\n- `error` — the message never reached a carrier.\n\nA template carries at most one rule per outcome.","enum":["delivered","not_delivered","sent","error"],"example":"delivered"},"Voice":{"type":"object","description":"Message content for the `voice` channel: the audio played to the recipient.","properties":{"file_id":{"type":"string","format":"uuid","description":"Identifier of the audio to play. Must be an uploaded file of kind `audio`.","example":"01970001-0000-7000-8000-000000000001"},"audio_url":{"type":["string","null"],"description":"Playback URL for `file_id`. Server-populated and rejected on input; returned when `presign=true`, null otherwise.","example":"https://files.example.com/01970003-0000-7000-8000-000000000005.mp3","readOnly":true}},"required":["file_id"],"title":"Voice"},"FollowUpRule":{"type":"object","description":"Rule attached to this template: when a send of it reaches `trigger_status`, `target_template` is sent to the same recipient with the same variable values. The follow-up does not itself fire further rules.","properties":{"trigger_status":{"type":"string","description":"Send outcome that fires this rule.","enum":["delivered","not_delivered","sent","error"],"example":"delivered"},"target_template":{"$ref":"#/components/schemas/TargetTemplate","description":"The template sent when the outcome occurs."}},"required":["target_template","trigger_status"]},"TargetTemplate":{"type":"object","description":"Identity of the template a follow-up rule sends.","properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the target template.","example":"01970003-0000-7000-8000-000000000004"},"name":{"type":"string","description":"Name of the target template.","example":"Welcome SMS"},"channel":{"$ref":"#/components/schemas/Channel","description":"Channel the target template is sent over.","example":"sms"}},"required":["channel","id","name"]},"TemplateResponse":{"type":"object","description":"A stored message template: the channel it is sent over, the content rendered for each recipient, the variables the content references, and any follow-up rules attached to it.","properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the template.","example":"01970003-0000-7000-8000-000000000001"},"name":{"type":"string","description":"Template name, unique per channel among live templates.","example":"Promo RCS card"},"channel":{"$ref":"#/components/schemas/Channel","description":"Channel this template is sent over.","example":"rcs"},"content":{"$ref":"#/components/schemas/Content","description":"Channel-specific content, exactly as stored."},"variables":{"type":"array","description":"Sorted, de-duped names of every `{{variable}}` referenced in the content; supply these at send time.","items":{"type":"string","example":"discount"},"readOnly":true},"follow_up_rules":{"type":"array","description":"Rules that send another template when a send of this one reaches a given outcome. Absent when the template has none.","items":{"$ref":"#/components/schemas/FollowUpRule"}},"created_at":{"type":"string","format":"date-time","description":"When the template was created, RFC 3339 timestamp.","example":"2026-07-01T12:30:45Z"},"updated_at":{"type":"string","format":"date-time","description":"When the template was last renamed, RFC 3339 timestamp. Equals `created_at` until the first rename.","example":"2026-07-02T08:15:00Z"}},"required":["channel","content","created_at","id","name","updated_at"]},"InlineContent":{"type":"object","description":"Send content inline, without a saved template. `content` is read against the schema `channel` selects and rendered per recipient, exactly like a template.","properties":{"channel":{"$ref":"#/components/schemas/Channel","description":"Channel every message in this batch is sent over. Selects the accepted content shape.","example":"sms"},"content":{"$ref":"#/components/schemas/Content","description":"Channel-specific content, read against the schema `channel` selects."}},"required":["channel","content"]},"MessageBody":{"description":"What to send. Provide exactly one form: inline content carrying its own `channel` and `content`, or a saved template referenced by `template_id`. The two are told apart by their fields.","oneOf":[{"$ref":"#/components/schemas/InlineContent"},{"$ref":"#/components/schemas/TemplatedContent"}]},"MessageSendRequest":{"type":"object","description":"A batch send: one body — inline content or a saved template — rendered per recipient and delivered over its channel.","properties":{"body":{"$ref":"#/components/schemas/MessageBody","description":"What to send: inline content or a saved template reference."},"campaign_id":{"type":"string","format":"uuid","description":"Campaign to attribute every message this request creates to, so the send shows up under the `campaign_id` list filter. Must be a campaign of yours; an unknown id is rejected with `404`. Omit it for an ad-hoc send.","example":"01970005-0000-7000-8000-000000000001"},"recipients":{"type":"array","description":"Recipients of this batch, 1 to 50000. Each yields its own message, its own result entry, and its own charge.","items":{"$ref":"#/components/schemas/Recipient"},"maxItems":50000,"minItems":1}},"required":["body","recipients"]},"Recipient":{"type":"object","description":"One destination in a batch send, with the variables rendered for it.","properties":{"to":{"type":"string","description":"Brazilian mobile number. Common formats are accepted (parentheses, dashes, spaces, with or without +55) and normalized to canonical E.164 (+55 + DDD + 9-digit subscriber).","example":"+5511900000000"},"variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Values substituted into this recipient's `{{variable}}` tokens; names are case-insensitive. A token the template references but this map omits leaves the message terminal with status `error`.","example":{"name":"Ana","code":"4321"}}},"required":["to"]},"TemplatedContent":{"type":"object","description":"Send a saved template, rendered per recipient over the template's channel.","properties":{"template_id":{"type":"string","format":"uuid","description":"Template rendered for every recipient in this batch. Its channel is the channel every message in this batch is sent over.","example":"01970003-0000-7000-8000-000000000001"}},"required":["template_id"]},"MessageSendResponse":{"type":"object","description":"The per-recipient outcome of a batch send.","properties":{"data":{"type":"array","description":"One result per recipient, in the order the recipients were sent.","items":{"$ref":"#/components/schemas/SendResult"}}},"required":["data"]},"MessageStatus":{"type":"string","description":"Delivery state of a message.\n\n- `pending` — accepted and queued, not yet handed to a carrier.\n- `sending` — claimed for dispatch and being handed to a carrier.\n- `sent` — the carrier accepted it; awaiting a delivery receipt.\n- `delivered` — the carrier confirmed delivery to the handset.\n- `not_delivered` — the carrier reported it could not be delivered (rejected, undeliverable, or expired).\n- `error` — it never reached a carrier, because no sender was configured for the channel, media was unavailable, or the template could not be rendered.\n- `blocked` — the recipient is suppressed on this channel, so it was never dispatched and was not charged.\n\n`pending`, `sending` and `sent` are in flight; the rest are terminal and no further transition follows.","enum":["pending","sending","sent","delivered","not_delivered","error","blocked"],"example":"delivered"},"SendResult":{"type":"object","description":"The message created for one recipient and the status it was created with.","properties":{"id":{"type":"string","format":"uuid","description":"Id of the created message; use it to read the message back and to correlate delivery updates.","example":"01970002-0000-7000-8000-000000000001"},"to":{"type":"string","description":"The recipient this result belongs to, in canonical E.164 form. Without the `phones:view` scope, `+55`, the area code and the last four digits stay intact and every digit between them is replaced with `*`.","example":"+5511900000000","pattern":"^(?:\\+55\\d{2}(?:9\\d{8}|\\d{8})|\\+55\\d{2}(?:\\*{5}\\d{4}|\\*{4}\\d{4}))$"},"status":{"$ref":"#/components/schemas/MessageStatus","description":"Status the message was created with: `pending` when it was queued for dispatch, `blocked` when the recipient is suppressed on this channel, `error` when it could not be prepared. Only `pending` goes on to a carrier.","example":"pending"}},"required":["id","status","to"]},"ConfirmUploadResponse":{"type":"object","description":"Outcome of confirming a direct upload.","properties":{"status":{"$ref":"#/components/schemas/FileStatus","description":"Status of the file after confirmation.","example":"uploaded"}},"required":["status"]},"FileStatus":{"type":"string","description":"Upload state of a file.\n\n- `pending` — an upload URL was issued; the bytes are not confirmed yet, so the file has no download URL and cannot be used.\n- `uploaded` — the upload was confirmed; the file is stored and ready to use.\n\nA file starts `pending` and moves to `uploaded` on a successful confirm; it never moves back.","enum":["pending","uploaded"],"example":"uploaded"},"FileUsage":{"type":"string","description":"What an uploaded file is for. It decides which content types the upload accepts, so a `content_type` outside the declared usage is rejected with 415.\n\n- `voice` — audio played to the recipient of a voice message; accepts the `audio` kind.\n- `rcs_media` — media carried by an RCS message; accepts `image`, `video` and `document`.\n- `contact_import` — a CSV of contact rows to load into your contacts; accepts the `csv` kind.\n\n`GET /files/constraints` returns the current kinds per usage, and the content types of each kind.","enum":["voice","rcs_media","contact_import"],"example":"rcs_media"},"UploadUrlRequest":{"type":"object","description":"Declares the file about to be uploaded directly to storage.","properties":{"usage":{"$ref":"#/components/schemas/FileUsage","description":"What the file is for; it decides which content types are accepted.","example":"voice"},"content_type":{"type":"string","description":"MIME type of the bytes to upload. Must be one `GET /files/constraints` lists for a kind the `usage` allows, and must be sent as the `Content-Type` header on the upload `PUT`.","example":"audio/mpeg","pattern":"^[a-z]+/[a-z0-9.+-]+$"},"size_bytes":{"type":"integer","format":"int64","description":"Exact size of the bytes to upload. Signed into the upload URL, so the `PUT` must send the same `Content-Length`, and it must fit the kind's `max_size_bytes`.","example":1024,"minimum":1}},"required":["content_type","size_bytes","usage"]},"UploadUrlResponse":{"type":"object","description":"A presigned URL to upload the bytes to, and the id of the file that will hold them.","properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the created file; it stays `pending` until the upload is confirmed.","example":"01970001-0000-7000-8000-000000000001"},"url":{"type":"string","format":"uri","description":"URL to `PUT` the bytes to, with the declared `content_type` as `Content-Type` and `size_bytes` as `Content-Length`. It carries its own signature — send no API credentials.","example":"https://s3.test.invalid/bucket-public/upload?X-Amz-Signature=abc123"},"expires_at":{"type":"string","format":"date-time","description":"When the upload URL stops working. After it, request a new upload URL, which creates a new file.","example":"2026-07-01T13:30:45Z"}},"required":["expires_at","id","url"]},"ApiError":{"type":"object","description":"Standard error envelope for all `/v3` error responses.","properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"}},"required":["error"]},"ErrorCode":{"type":"string","description":"Stable, machine-branchable code returned in `error.code`. Branch on this rather than on the HTTP status or the human `message`.\n\n- `validation_failed` — a field failed validation; `error.fields` lists which.\n- `bad_request` — the request was malformed or a parameter was unusable.\n- `unauthorized` — no credential was presented, or it was rejected.\n- `forbidden` — the credential is valid but may not call this operation.\n- `not_found` — the addressed resource does not exist in your account.\n- `conflict` — the request collides with the resource's current state.\n- `duplicate_name` — a resource of this type already uses this name; choose another.\n- `idempotency_key_reuse` — this `Idempotency-Key` was already used with a different request body.\n- `unprocessable_entity` — well-formed, but it cannot be applied as asked.\n- `invalid_state` — the resource is not in a state that allows this transition.\n- `import_in_progress` — a bulk contact load is already running and blocks this change until it finishes.\n- `rate_limited` — your request allowance for the current window is exhausted; back off using `Retry-After`.\n- `body_too_large` — the request body itself exceeded the accepted size.\n- `file_too_large` — the declared file size exceeds the limit for its kind.\n- `unsupported_content_type` — the `Content-Type` is not accepted here.\n- `unsupported_file_type` — the content type is not permitted for the requested usage.\n- `not_implemented` — the operation is recognised but not available.\n- `service_unavailable` — a dependency is unavailable; retry with backoff.\n- `internal_error` — an unexpected failure; safe to retry idempotent calls.","enum":["validation_failed","bad_request","unauthorized","forbidden","not_found","conflict","duplicate_name","idempotency_key_reuse","unprocessable_entity","invalid_state","import_in_progress","rate_limited","body_too_large","file_too_large","unsupported_content_type","unsupported_file_type","not_implemented","service_unavailable","internal_error"],"example":"validation_failed"},"ErrorDetail":{"type":"object","description":"What went wrong: branch on `code`, show `message`.","properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable explanation, for logs and operators. Not for end users, and not stable enough to branch on — branch on `code`.","example":"phone must be a valid Brazilian mobile number"},"fields":{"type":"array","description":"Present when `code` is `validation_failed`: one entry per rejected input field.","items":{"$ref":"#/components/schemas/ErrorFieldDetail"}}},"required":["code","message"]},"ErrorFieldDetail":{"type":"object","description":"One rejected input field.","properties":{"field":{"type":"string","description":"Path of the rejected field, dotted for nested members.","example":"phone"},"message":{"type":"string","description":"Why this field was rejected.","example":"must be a valid Brazilian mobile number"}},"required":["field","message"]},"ContactUpsertRequest":{"type":"object","description":"Contact to create or update, matched on its normalized phone number.","properties":{"name":{"type":"string","description":"Display name; non-blank when present. Omit to keep the stored name.","example":"Ana Souza","maxLength":255,"minLength":1,"pattern":".*\\S.*"},"phone":{"type":"string","description":"Brazilian mobile number. Common formats are accepted (parentheses, dashes, spaces, with or without +55) and normalized to canonical E.164 (+55 + DDD + 9-digit subscriber).","example":"+5511900000000"},"attributes":{"type":"object","additionalProperties":{"type":"string"},"description":"Free-form string attributes; replaces the whole stored map when present, omit to keep it. Keys must start with a letter or underscore, continue with letters, digits or underscores, and may not be `name` or `phone`; keys and values are capped at 255 characters.","example":{"city":"Recife","plan":"gold"}}},"required":["phone"]},"ContactResponse":{"type":"object","description":"A contact in your account, identified by its normalized phone number.","properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the contact.","example":"01970000-0000-7000-8000-000000000001"},"name":{"type":["string","null"],"description":"Display name; null when the contact has none.","example":"Ana Souza"},"phone":{"type":"string","description":"Phone number in canonical E.164 form. Without the `phones:view` scope, `+55`, the area code and the last four digits stay intact and every digit between them is replaced with `*`.","example":"+5511900000000","pattern":"^(?:\\+55\\d{2}(?:9\\d{8}|\\d{8})|\\+55\\d{2}(?:\\*{5}\\d{4}|\\*{4}\\d{4}))$"},"attributes":{"type":"object","additionalProperties":{"type":"string"},"description":"Free-form string attributes.","example":{"city":"Recife","plan":"gold"}},"list_ids":{"type":"array","description":"Contact lists this contact currently belongs to.","example":["01970000-0000-7000-8000-000000000003"],"items":{"type":"string","format":"uuid"}},"created_at":{"type":"string","format":"date-time","description":"Instant the contact was created, UTC.","example":"2026-07-01T12:30:45Z"},"updated_at":{"type":"string","format":"date-time","description":"Instant the contact was last modified, UTC.","example":"2026-07-02T08:15:00Z"}},"required":["created_at","id","phone","updated_at"]},"ContactListCreateRequest":{"type":"object","description":"Contact list to create.","properties":{"name":{"type":"string","description":"Name for the list; must not already be taken by one of your lists.","example":"Blocked Numbers","maxLength":120,"minLength":3},"type":{"$ref":"#/components/schemas/ContactListType","description":"What the list is for. Defaults to `default` and cannot be changed afterwards.","example":"blacklist"},"channels":{"type":"array","description":"Channels a `blacklist` list suppresses on; duplicates are collapsed. Omit it or send `[]` on a `default` list — anything else is rejected with `422`.","example":["rcs","sms"],"items":{"$ref":"#/components/schemas/Channel"}}},"required":["name"]},"ContactListType":{"type":"string","description":"What a contact list is for. Fixed when the list is created and never changes.\n\n- `default` — a plain group of contacts, and the only kind a campaign can send to. Its `channels` is always empty.\n- `blacklist` — a suppression set covering the channels named in its `channels`. Every send is checked against it first: a recipient that belongs to a blacklist list covering the channel being sent on is created with status `blocked`, never handed to a carrier and never charged. A blacklist list is not an audience — pointing a campaign at one is rejected with `422`.\n\nSuppression is per channel, so a blacklist list whose `channels` is empty suppresses nothing. A contact may sit in any number of lists of either type; membership of one blacklist list covering the channel is enough to block it.","enum":["default","blacklist"],"example":"default"},"ContactListResponse":{"type":"object","description":"A named group of your contacts, used either as a send audience or as a suppression set.","properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the contact list.","example":"01970006-0000-7000-8000-000000000001"},"name":{"type":"string","description":"Name of the list, unique among your lists.","example":"Blocked Numbers"},"type":{"$ref":"#/components/schemas/ContactListType","description":"What the list is for; fixed when it was created.","example":"blacklist"},"channels":{"type":"array","description":"Channels a `blacklist` list suppresses on; always empty on a `default` list.","example":["rcs","sms"],"items":{"$ref":"#/components/schemas/Channel"}},"contact_count":{"type":"integer","format":"int32","description":"Number of contacts currently in the list.","example":42},"created_at":{"type":"string","format":"date-time","description":"Instant the list was created, UTC. Lists are returned newest first by this.","example":"2026-07-01T12:30:45Z"},"updated_at":{"type":"string","format":"date-time","description":"Instant the list's name or channels last changed, UTC. Adding or removing a contact does not move it.","example":"2026-07-02T08:15:00Z"}},"required":["channels","contact_count","created_at","id","name","type","updated_at"]},"CampaignCreateRequest":{"type":"object","description":"New campaign: the template to send and the contact list to send it to. The campaign's channel is the template's channel. Both are fixed at creation — only `name` can be changed afterwards — so a different template or list means a new campaign.","properties":{"name":{"type":"string","description":"Campaign name, 3 to 120 characters. The only field a later update can change.","example":"July Promo","maxLength":120,"minLength":3},"template_id":{"type":"string","format":"uuid","description":"Template rendered for every recipient. Immutable after creation.","example":"01970003-0000-7000-8000-000000000001"},"list_id":{"type":"string","format":"uuid","description":"Contact list whose contacts are the campaign's recipients. Immutable after creation; a blacklist-type list is a suppression set and is rejected here.","example":"01970006-0000-7000-8000-000000000001"}},"required":["list_id","name","template_id"]},"WebhookPatchRequest":{"type":"object","description":"Changes an endpoint's delivery settings; omitted fields keep their value.","properties":{"enabled":{"type":"boolean","description":"Turns deliveries on or off.","example":false},"events":{"type":"array","description":"Replaces the subscribed event types.","items":{"$ref":"#/components/schemas/WebhookEventType"},"uniqueItems":true}}},"WebhookEndpointResponse":{"type":"object","description":"A webhook endpoint receiving event deliveries.","properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the webhook endpoint.","example":"01970004-0000-7000-8000-000000000001"},"url":{"type":"string","format":"uri","description":"HTTPS URL deliveries are POSTed to.","example":"https://example.com/webhooks/sintalk"},"enabled":{"type":"boolean","description":"Whether deliveries are currently sent.","example":true},"events":{"type":"array","description":"Event types delivered to this endpoint.","items":{"$ref":"#/components/schemas/WebhookEventType"},"uniqueItems":true},"created_at":{"type":"string","format":"date-time","description":"Instant the endpoint was created, UTC.","example":"2026-07-01T12:30:45Z"},"updated_at":{"type":"string","format":"date-time","description":"Instant the endpoint's settings last changed, UTC.","example":"2026-07-02T08:15:00Z"}},"required":["created_at","enabled","events","id","updated_at","url"]},"TemplateRenameRequest":{"type":"object","description":"New name for a template.","properties":{"name":{"type":"string","description":"Template name, 3 to 120 characters. Unique per channel among live templates.","example":"Promo RCS card","maxLength":120,"minLength":3}},"required":["name"]},"ContactPatchRequest":{"type":"object","description":"Fields to change on an existing contact; every member is optional and omitted members are left unchanged.","properties":{"name":{"type":"string","description":"Display name; non-blank when present.","example":"Ana Souza","maxLength":255,"minLength":1,"pattern":".*\\S.*"},"phone":{"type":"string","description":"Brazilian mobile number. Common formats are accepted (parentheses, dashes, spaces, with or without +55) and normalized to canonical E.164 (+55 + DDD + 9-digit subscriber).","example":"+5511900000000"},"attributes":{"type":"object","additionalProperties":{"type":"string"},"description":"Replaces the entire attributes map when provided; omit to leave unchanged, send {} to clear. Keys must start with a letter or underscore, continue with letters, digits or underscores, and may not be `name` or `phone`; keys and values are capped at 255 characters.","example":{"city":"Recife","plan":"gold"}}}},"ContactListPatchRequest":{"type":"object","description":"Fields to change on a contact list; its type is not among them.","properties":{"name":{"type":"string","description":"New name for the list; must not already be taken by another of your lists.","example":"Blocked Numbers","maxLength":120,"minLength":3},"channels":{"type":"array","description":"Replaces the channels a `blacklist` list suppresses on; duplicates are collapsed. Omit it to leave them as they are, or send `[]` to stop the list suppressing anything. A non-empty value on a `default` list is rejected with `422`.","example":["rcs","sms"],"items":{"$ref":"#/components/schemas/Channel"}}},"required":["name"]},"CampaignUpdateRequest":{"type":"object","description":"New name for a campaign.","properties":{"name":{"type":"string","description":"Campaign name, 3 to 120 characters.","example":"July Promo","maxLength":120,"minLength":3}},"required":["name"]},"PageWebhookEndpointResponse":{"type":"object","description":"One page of results, with the offset pagination state that produced it.","properties":{"data":{"type":"array","description":"The rows on this page.","items":{"$ref":"#/components/schemas/WebhookEndpointResponse"}},"pagination":{"$ref":"#/components/schemas/Pagination","description":"Pagination state for this page; use it to fetch the next one."}},"required":["data","pagination"]},"Pagination":{"type":"object","description":"Offset pagination state for a list response.","properties":{"limit":{"type":"integer","format":"int32","description":"Effective page size applied to this request.","example":25},"offset":{"type":"integer","format":"int32","description":"Offset this page starts at.","example":0},"total":{"type":["integer","null"],"format":"int64","description":"Total rows matching the query. Omitted when counting would be too expensive; use `has_more` to decide whether to advance.","example":137},"has_more":{"type":"boolean","description":"True when more rows exist past this page. Advance by adding `limit` to `offset`.","example":true}},"required":["has_more","limit","offset"]},"PageTemplateResponse":{"type":"object","description":"One page of results, with the offset pagination state that produced it.","properties":{"data":{"type":"array","description":"The rows on this page.","items":{"$ref":"#/components/schemas/TemplateResponse"}},"pagination":{"$ref":"#/components/schemas/Pagination","description":"Pagination state for this page; use it to fetch the next one."}},"required":["data","pagination"]},"MessageResponse":{"type":"object","description":"One message addressed to a single recipient, with its delivery state.","properties":{"id":{"type":"string","format":"uuid","description":"Unique id of the message.","example":"01970002-0000-7000-8000-000000000001"},"channel":{"$ref":"#/components/schemas/Channel","description":"Channel the message was sent over.","example":"sms"},"to":{"type":"string","description":"Recipient of the message, in canonical E.164 form. Without the `phones:view` scope, `+55`, the area code and the last four digits stay intact and every digit between them is replaced with `*`.","example":"+5511900000000","pattern":"^(?:\\+55\\d{2}(?:9\\d{8}|\\d{8})|\\+55\\d{2}(?:\\*{5}\\d{4}|\\*{4}\\d{4}))$"},"campaign_id":{"type":["string","null"],"format":"uuid","description":"Campaign this message was sent as part of, and the value the `campaign_id` list filter matches; null for an ad-hoc send.","example":"01970005-0000-7000-8000-000000000001"},"template_id":{"type":["string","null"],"format":"uuid","description":"Template the message content was rendered from; null for an inline send.","example":"01970003-0000-7000-8000-000000000001"},"follow_up_id":{"type":["string","null"],"format":"uuid","description":"Id of the message sent as this one's follow-up, set when a follow-up rule on its template fires on the status this message reached; null otherwise.","example":"01970002-0000-7000-8000-000000000005"},"variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Variables substituted into the template or inline content for this recipient.","example":{"name":"Ana","code":"4321"}},"status":{"$ref":"#/components/schemas/MessageStatus","description":"Current delivery state of the message.","example":"delivered"},"status_history":{"type":"array","description":"Every status the message has entered, ordered by event time ascending. Populated when a single message is read; empty on list items.","items":{"$ref":"#/components/schemas/MessageStatusChange"}},"created_at":{"type":"string","format":"date-time","description":"When the message was created, which is what `from`/`to` bound and what the list is ordered by.","example":"2026-07-01T12:30:45Z"},"updated_at":{"type":"string","format":"date-time","description":"When the message last changed, including each delivery-state transition.","example":"2026-07-01T12:31:10Z"}},"required":["channel","created_at","id","status","to","updated_at"]},"MessageStatusChange":{"type":"object","description":"One entry in a message's status history.","properties":{"status":{"$ref":"#/components/schemas/MessageStatus","description":"Status the message entered.","example":"delivered"},"at":{"type":"string","format":"date-time","description":"When the message entered this status.","example":"2026-07-01T12:31:10Z"}},"required":["at","status"]},"PageMessageResponse":{"type":"object","description":"One page of results, with the offset pagination state that produced it.","properties":{"data":{"type":"array","description":"The rows on this page.","items":{"$ref":"#/components/schemas/MessageResponse"}},"pagination":{"$ref":"#/components/schemas/Pagination","description":"Pagination state for this page; use it to fetch the next one."}},"required":["data","pagination"]},"InboundRcsEvent":{"type":"object","description":"An action someone took on a message you sent to them over RCS, such as tapping a suggested action. No message is posted into the conversation.","properties":{"id":{"type":"string","format":"uuid","description":"Id of the inbound event.","example":"0197000c-0000-7000-8000-000000000001"},"sender":{"type":"string","description":"Phone number that raised the event, in canonical E.164 form. Without the `phones:view` scope, `+55`, the area code and the last four digits stay intact and every digit between them is replaced with `*`.","example":"+5511900000000","pattern":"^(?:\\+55\\d{2}(?:9\\d{8}|\\d{8})|\\+55\\d{2}(?:\\*{5}\\d{4}|\\*{4}\\d{4}))$"},"recipient":{"type":"string","description":"Registered sender the event was addressed to.","example":"acme-rcs"},"text":{"type":["string","null"],"description":"Label of the action that was tapped, when it carries one.","example":"Ligar para o suporte"},"postback_data":{"type":["string","null"],"description":"Payload you attached to the action that was tapped, when it carries one.","example":"call-support"},"paired_message_id":{"type":["string","null"],"format":"uuid","description":"Id of the message you sent that this event acts on, when it could be matched.","example":"01970002-0000-7000-8000-000000000001"},"campaign_id":{"type":["string","null"],"format":"uuid","description":"Id of the campaign the acted-on message belongs to.","example":"01970005-0000-7000-8000-000000000001"},"received_at":{"type":"string","format":"date-time","description":"When the event was received.","example":"2026-07-01T12:30:45Z"},"created_at":{"type":"string","format":"date-time","description":"When the event was stored.","example":"2026-07-02T08:15:00Z"}},"required":["created_at","id","received_at","recipient","sender"]},"PageInboundRcsEvent":{"type":"object","description":"One page of results, with the offset pagination state that produced it.","properties":{"data":{"type":"array","description":"The rows on this page.","items":{"$ref":"#/components/schemas/InboundRcsEvent"}},"pagination":{"$ref":"#/components/schemas/Pagination","description":"Pagination state for this page; use it to fetch the next one."}},"required":["data","pagination"]},"InboundContentType":{"type":"string","description":"The shape of an inbound message's content.\n\n- `text` — a text bubble the sender typed.\n- `file` — an image, video, document, or audio clip.\n- `location` — a map position the sender shared.\n- `suggestion` — a chip the sender tapped on a message you sent.","enum":["text","file","location","suggestion"],"example":"text"},"InboundContent":{"description":"What an inbound message carries, selected by the `type` discriminator:\n\n- `text` → `InboundText` — a text bubble the sender typed.\n- `file` → `InboundFile` — an image, video, document, or audio clip.\n- `location` → `InboundLocation` — a map position the sender shared.\n- `suggestion` → `InboundSuggestion` — a chip the sender tapped on a message you sent.","discriminator":{"propertyName":"type","mapping":{"text":"#/components/schemas/InboundText","file":"#/components/schemas/InboundFile","location":"#/components/schemas/InboundLocation","suggestion":"#/components/schemas/InboundSuggestion"}},"oneOf":[{"$ref":"#/components/schemas/InboundText"},{"$ref":"#/components/schemas/InboundFile"},{"$ref":"#/components/schemas/InboundLocation"},{"$ref":"#/components/schemas/InboundSuggestion"}]},"InboundFile":{"type":"object","description":"Inbound content carrying a file. Selected by `type: file`. The `url` is hosted upstream and may stop resolving, so download anything you need to keep.","properties":{"type":{"type":"string","description":"Discriminator for this content shape; always `file`.","enum":["file"],"example":"file"},"url":{"type":"string","description":"Where the file can be downloaded from.","example":"https://example.com/inbound/a1b2c3.png"},"name":{"type":["string","null"],"description":"File name as the sender's handset reported it.","example":"comprovante.png"},"content_type":{"type":["string","null"],"description":"MIME type of the file.","example":"image/png"},"size":{"type":["integer","null"],"format":"int64","description":"File size in bytes.","example":184320}},"required":["type","url"],"title":"File"},"InboundLocation":{"type":"object","description":"Inbound content carrying a map position. Selected by `type: location`.","properties":{"type":{"type":"string","description":"Discriminator for this content shape; always `location`.","enum":["location"],"example":"location"},"latitude":{"type":"number","format":"double","description":"Latitude in decimal degrees.","example":-23.5505},"longitude":{"type":"number","format":"double","description":"Longitude in decimal degrees.","example":-46.6333}},"required":["latitude","longitude","type"],"title":"Location"},"InboundMessage":{"type":"object","description":"A message someone sent to one of your registered senders.","properties":{"id":{"type":"string","format":"uuid","description":"Id of the inbound message.","example":"0197000a-0000-7000-8000-000000000001"},"channel":{"$ref":"#/components/schemas/Channel","description":"Channel the message arrived on."},"sender":{"type":"string","description":"Phone number that sent the message, in canonical E.164 form. Without the `phones:view` scope, `+55`, the area code and the last four digits stay intact and every digit between them is replaced with `*`.","example":"+5511900000000","pattern":"^(?:\\+55\\d{2}(?:9\\d{8}|\\d{8})|\\+55\\d{2}(?:\\*{5}\\d{4}|\\*{4}\\d{4}))$"},"recipient":{"type":"string","description":"Registered sender the message was addressed to.","example":"acme-rcs"},"content":{"$ref":"#/components/schemas/InboundContent","description":"What the message carries."},"keyword":{"type":["string","null"],"description":"Keyword extracted from the message, when there is one.","example":"SAIR"},"paired_message_id":{"type":["string","null"],"format":"uuid","description":"Id of the message you sent that this one replies to, when it could be matched.","example":"01970002-0000-7000-8000-000000000001"},"campaign_id":{"type":["string","null"],"format":"uuid","description":"Id of the campaign the replied-to message belongs to.","example":"01970005-0000-7000-8000-000000000001"},"conversation_id":{"type":["string","null"],"description":"Id of the conversation this message belongs to.","example":"conv-8f21c0"},"received_at":{"type":"string","format":"date-time","description":"When the message was received.","example":"2026-07-01T12:30:45Z"},"created_at":{"type":"string","format":"date-time","description":"When the message was stored.","example":"2026-07-02T08:15:00Z"}},"required":["channel","content","created_at","id","received_at","recipient","sender"]},"InboundSuggestion":{"type":"object","description":"Inbound content produced by the sender tapping a chip on a message you sent. Selected by `type: suggestion`.","properties":{"type":{"type":"string","description":"Discriminator for this content shape; always `suggestion`.","enum":["suggestion"],"example":"suggestion"},"text":{"type":"string","description":"Label of the chip that was tapped.","example":"Sim"},"postback_data":{"type":["string","null"],"description":"Opaque payload you set on the chip, returned when it is tapped.","example":"reply-yes"}},"required":["text","type"],"title":"Suggestion"},"InboundText":{"type":"object","description":"Inbound content carrying a text bubble. Selected by `type: text`.","properties":{"type":{"type":"string","description":"Discriminator for this content shape; always `text`.","enum":["text"],"example":"text"},"text":{"type":"string","description":"The text the sender typed.","example":"Sim, pode confirmar"}},"required":["text","type"],"title":"Text"},"PageInboundMessage":{"type":"object","description":"One page of results, with the offset pagination state that produced it.","properties":{"data":{"type":"array","description":"The rows on this page.","items":{"$ref":"#/components/schemas/InboundMessage"}},"pagination":{"$ref":"#/components/schemas/Pagination","description":"Pagination state for this page; use it to fetch the next one."}},"required":["data","pagination"]},"FileKind":{"type":"string","description":"Content class of a file, derived by the server from its `content_type`.\n\n- `csv` — comma-separated text (`text/csv`).\n- `audio` — an audio recording (`audio/mpeg`, `audio/wav`).\n- `image` — a still image (`image/jpeg`, `image/png`).\n- `video` — a video (`video/mp4`).\n- `document` — a PDF document (`application/pdf`).\n\nEach kind has its own accepted content types and maximum size; `GET /files/constraints` returns both. `csv` is the only private kind — it is downloaded through a short-lived presigned URL, while every other kind is served from a stable URL.","enum":["csv","audio","image","video","document"],"example":"audio"},"File":{"type":"object","description":"A file held in your storage.","properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the file.","example":"01970001-0000-7000-8000-000000000001"},"status":{"$ref":"#/components/schemas/FileStatus","description":"Whether the upload has been confirmed.","example":"uploaded"},"public":{"type":"boolean","description":"True when the file is served from a stable URL; false when each download needs a short-lived presigned URL.","example":true},"kind":{"$ref":"#/components/schemas/FileKind","description":"Content class, derived from `content_type` when the upload URL was requested.","example":"audio"},"content_type":{"type":"string","description":"MIME type declared for the upload; the bytes are served with it.","example":"audio/mpeg"},"size_bytes":{"type":"integer","format":"int64","description":"Size of the stored bytes, in bytes.","example":1024,"minimum":0},"uploaded_at":{"type":["string","null"],"format":"date-time","description":"When the upload was confirmed; null while `status` is `pending`.","example":"2026-07-02T08:15:00Z"},"created_at":{"type":"string","format":"date-time","description":"When the upload URL was issued.","example":"2026-07-01T12:30:45Z"},"updated_at":{"type":"string","format":"date-time","description":"When the file last changed.","example":"2026-07-02T08:15:00Z"}},"required":["content_type","created_at","id","kind","public","size_bytes","status","updated_at"]},"PageFile":{"type":"object","description":"One page of results, with the offset pagination state that produced it.","properties":{"data":{"type":"array","description":"The rows on this page.","items":{"$ref":"#/components/schemas/File"}},"pagination":{"$ref":"#/components/schemas/Pagination","description":"Pagination state for this page; use it to fetch the next one."}},"required":["data","pagination"]},"DownloadUrlResponse":{"type":"object","description":"A URL that serves a file's bytes.","properties":{"url":{"type":"string","format":"uri","description":"URL to `GET` the bytes from.","example":"https://s3.test.invalid/bucket-private/download?X-Amz-Signature=def456"},"expires_at":{"type":["string","null"],"format":"date-time","description":"When this URL stops working. Null for a file served from a stable URL; set for a presigned one, which has to be requested again after it.","example":"2026-07-01T12:45:45Z"}},"required":["url"]},"FileConstraintsResponse":{"type":"object","description":"The limits an upload is checked against, per file kind and per usage.","properties":{"max_size_bytes":{"type":"integer","format":"int64","description":"Largest size any kind accepts, in bytes. An upload is checked against its own kind's `max_size_bytes`, which may be lower.","example":1073741824,"minimum":1},"kinds":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/KindConstraint"},"description":"Per-kind upload constraints, keyed by file kind.","example":{"audio":{"mime_types":["audio/mpeg","audio/wav"],"max_size_bytes":10485760}}},"resource_usages":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}},"description":"File kinds permitted per resource usage, keyed by resource-usage identifier.","example":{"rcs_media":["document","image","video"],"voice":["audio"],"contact_import":["csv"]}}},"required":["kinds","max_size_bytes","resource_usages"]},"KindConstraint":{"type":"object","description":"Upload limits for one file kind.","properties":{"mime_types":{"type":"array","description":"Content types accepted for this kind.","example":["audio/mpeg","audio/wav"],"items":{"type":"string"}},"max_size_bytes":{"type":"integer","format":"int64","description":"Largest `size_bytes` accepted for this kind, in bytes.","example":10485760,"minimum":1}},"required":["max_size_bytes","mime_types"]},"PageContactResponse":{"type":"object","description":"One page of results, with the offset pagination state that produced it.","properties":{"data":{"type":"array","description":"The rows on this page.","items":{"$ref":"#/components/schemas/ContactResponse"}},"pagination":{"$ref":"#/components/schemas/Pagination","description":"Pagination state for this page; use it to fetch the next one."}},"required":["data","pagination"]},"PageContactListResponse":{"type":"object","description":"One page of results, with the offset pagination state that produced it.","properties":{"data":{"type":"array","description":"The rows on this page.","items":{"$ref":"#/components/schemas/ContactListResponse"}},"pagination":{"$ref":"#/components/schemas/Pagination","description":"Pagination state for this page; use it to fetch the next one."}},"required":["data","pagination"]},"PageCampaignResponse":{"type":"object","description":"One page of results, with the offset pagination state that produced it.","properties":{"data":{"type":"array","description":"The rows on this page.","items":{"$ref":"#/components/schemas/CampaignResponse"}},"pagination":{"$ref":"#/components/schemas/Pagination","description":"Pagination state for this page; use it to fetch the next one."}},"required":["data","pagination"]},"MessageUpdateEvent":{"type":"object","description":"The payload delivered to a webhook endpoint when the message's status changes (the interim `sent` transition or a terminal status).","properties":{"id":{"type":"string","format":"uuid","description":"Stable event id that repeats across retries of the same event, so it can be used to discard a duplicate delivery.","example":"01970004-0000-7000-8000-000000000001"},"event":{"type":"string","description":"The event type. Always `message_update`; branch on it so a future event type delivered to the same endpoint does not surprise your handler.","enum":["message_update"],"example":"message_update"},"occurred_at":{"type":"string","format":"date-time","description":"When the status change happened, as opposed to when this delivery was attempted. Order events by this: delivery is at-least-once and batched, so arrival order is not event order.","example":"2026-07-01T12:30:45Z"},"data":{"type":"object","description":"The message this event concerns, as of the delivered status.","properties":{"message_id":{"type":"string","format":"uuid","description":"The message whose status changed; read it back for the full history.","example":"01970002-0000-7000-8000-000000000001"},"channel":{"$ref":"#/components/schemas/Channel"},"status":{"$ref":"#/components/schemas/MessageStatus"},"recipient":{"type":"string","description":"The destination phone number, in E.164.","example":"+5511900000000"}},"required":["message_id","channel","status","recipient"]}},"required":["id","event","occurred_at","data"]}},"responses":{"BadRequest":{"description":"The request was rejected before it was applied — either a field failed validation (`validation_failed`, with `error.fields` naming each one), or the request could not be read at all (`bad_request`: malformed JSON, an unknown field, a missing parameter, or an unparseable path or query value).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"Unauthorized":{"description":"No credential was presented, or the one presented was rejected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"Forbidden":{"description":"The credential is valid but is not allowed to call this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"RateLimited":{"description":"Rate limit exceeded","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","format":"int32"}},"RateLimit-Limit":{"description":"Requests allowed in the current window.","schema":{"type":"integer","format":"int32"}},"RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer","format":"int32"}},"RateLimit-Reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer","format":"int32"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"UnexpectedError":{"description":"An unexpected error. Safe to retry with backoff; use an idempotency key where the operation accepts one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"NotFound":{"description":"No resource with that id exists in your account. An id belonging to another account is reported the same way as one that never existed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"Conflict":{"description":"The request collides with what already exists, or with the resource's current state. Re-read the resource before retrying.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"UnprocessableEntity":{"description":"The body parsed and every field is individually valid, but the request cannot be applied as asked. Retrying it unchanged will fail again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"securitySchemes":{"ApiKeyBearerAuth":{"type":"http","description":"The same API key, sent as a bearer credential: `Authorization: Bearer st_...`.","scheme":"bearer"},"ApiKeyAuth":{"type":"apiKey","description":"Your API key (`st_...`), sent as `X-API-Key: st_...` or `Authorization: Bearer st_...`.","name":"X-API-Key","in":"header"}}}}