{"openapi":"3.1.0","info":{"title":"relay API","version":"1.0.0","description":"Send and receive WhatsApp messages on your organization's numbers from your own code: the same numbers, chats and\nmessages as the web inbox, plus outgoing webhooks for everything that happens on them.\n\n## Quickstart\n\n1. Create an API key in the dashboard (**Settings → Developers → API keys**) with the `send` scope. It is shown once.\n2. Send a message:\n\n```bash\ncurl https://api.wrelay.site/v1/messages \\\n  -H \"Authorization: Bearer rl_live_…\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"number_id\": \"num_…\", \"to\": \"+212612345678\", \"type\": \"text\", \"text\": \"Hello!\"}'\n```\n\n`202 { \"id\": \"msg_…\", \"status\": \"pending\", \"chat_id\": \"chat_…\" }` — the message is recorded and leaves WhatsApp within\nseconds; follow it with the `message.sent` and `message.status` webhooks or `GET /messages/{id}`.\n\n## Authentication and scopes\n\nSend the key as a bearer token: `Authorization: Bearer rl_live_…`. A key acts for the whole organization; we store only\nits hash. A missing, unknown, revoked or expired key answers `401 unauthorized`.\n\n| Scope | Grants |\n|---|---|\n| `read` | every `GET`: numbers, chats, messages, groups, contacts, media downloads |\n| `send` | sending, reactions, edits, revokes, chat state (archive, pin, mute, read), groups, uploads, number checks |\n| `admin` | connecting, pairing, logging out and deleting numbers; webhook endpoints and the delivery log |\n\nScopes are not cumulative: give a key every scope it needs. `GET /operations/{id}` accepts any key. A call outside the\nkey's scopes answers `403 insufficient_scope`.\n\n## Conventions\n\n- **Ids** are prefixed by resource: `num_` number, `chat_` chat (and group), `msg_` message, `ct_` contact,\n  `upl_` upload, `media_` attachment, `whk_` webhook endpoint, `dlv_` delivery, `evt_` event, `op_` operation.\n  An id of another organization, malformed, or with the wrong prefix answers `404 <resource>_not_found`.\n- **Phones** are E.164 (`+212612345678`). `jid` fields are WhatsApp's own ids, given for matching only.\n- **Timestamps** are ISO 8601 in UTC. Field names are `snake_case`. Bodies are JSON (1 MB max; files go through uploads).\n\n## Pagination\n\nLists are keyset-paged: `?limit=` and `?cursor=`. Each page answers `{ data, has_more, next_cursor }`; pass\n`next_cursor` as `cursor` for the next page until it is `null`. Pages stay stable while new rows arrive. A cursor this\nlist did not issue answers `400 invalid_cursor`.\n\n## Idempotency\n\nEvery write (`POST`, `PATCH`, `DELETE`) requires an `Idempotency-Key` header: a unique string per logical request\n(1–150 visible ASCII characters; a UUID is ideal). Retry a failed or timed-out call with the\n**same key and the same body**: within 24 h you get the original answer (header `Idempotent-Replayed: true`) and\nnothing happens twice. The same key with a different method, path or body answers `422 idempotency_key_reused`; a retry\nwhile the first call is still running answers `409 idempotency_key_in_use`. Only successful answers are kept: after an\nerror, retrying the same key runs the request again. For sends, the key is also the message's `client_key`: a retry\nof `POST /messages` (even past 24 h) answers the same message with its current status, and queues again a message that\nfailed before it left WhatsApp. One exception: `POST /numbers/{id}/pairing-code` takes no key (its code is\nnever stored).\n\n## Long operations\n\nRequests that wait on WhatsApp (connecting or deleting a number, group commands, number checks) answer `202` with\n`{ operation_id, operation }` and a `Location` header. Poll `GET /operations/{id}` (every 1–2 s) until `status` is\n`succeeded` (read `result`) or `failed` (read `error.code`), then re-read the resource. An operation that does not\nsettle within an hour reads `failed` with `expired`.\n\n## Sending files\n\n1. `POST /media/uploads` with `mime_type` and `size_bytes` → an `upl_` id and a presigned `upload.url`.\n2. `PUT` the raw bytes to `upload.url` with exactly `upload.headers` (no Authorization header) within 15 minutes.\n3. `POST /media/uploads/{id}/finalize` → `state: ready`.\n4. `POST /messages` with `type` (`image`, `video`, `audio`, `voice` or `document`), `upload_id` and an optional\n   `text` caption, within 24 h.\n\nReceived attachments are downloaded with `GET /media/{media.id}`, which answers a signed URL valid 5 minutes.\n\n## Rate limits and quotas\n\n| Limit | Value | Answer |\n|---|---|---|\n| Burst, per key | 20 requests / second | `429 rate_limited` |\n| Sustained, per key | the plan's per-minute limit below (or the key's own) | `429 rate_limited` |\n| Per number | 30 send requests / minute (sends, reactions, edits, revokes, group commands) | `429 rate_limited` |\n| Monthly API requests, per organization | the plan's quota below; resets on the 1st (UTC) | `429 quota_exceeded` |\n| Monthly messages sent, per organization | the plan's send quota below | `429 quota_exceeded` |\n| Daily sends, per number | ramps up over a new number's first two weeks (`daily_send_cap` on the number) | `429 send_cap_reached` |\n| New conversations, per number per day | capped (people who never wrote to the number first) | `429 recipient_never_wrote` |\n\n| Plan | Requests / minute / key | API requests / month | Messages sent / month |\n|---|---|---|---|\n| Starter | 60 | 10,000 | 10,000 |\n| Growth | 300 | 100,000 | 60,000 |\n| Business | 1200 | 1,000,000 | 300,000 |\n\nEvery authenticated answer carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` (seconds) for the tightest window\nand `RateLimit-Policy`; a `429` adds `Retry-After` (seconds). An organization without an active plan answers\n`429 quota_exceeded` until it subscribes.\n\n## Responsible sending\n\nNumbers are linked as unofficial WhatsApp devices, which breaks WhatsApp's terms: WhatsApp may restrict or ban a\nnumber without notice, and automated or bulk sending is the most common trigger. There is **no bulk or broadcast\nendpoint** — one request sends one message to one conversation — and sends leave each number paced a few seconds\napart. Message people who expect it, keep volumes human, and watch `number.status` for `failed`. Connecting a number\nrequires `tos_acknowledged: true`.\n\n## Errors\n\nEvery non-2xx answer is an RFC 9457 problem (`application/problem+json`):\n\n```json\n{ \"type\": \"urn:relay:problem:number_not_working\", \"title\": \"Conflict\", \"status\": 409, \"code\": \"number_not_working\",\n  \"detail\": \"This number is not connected: sending is paused\", \"errors\": { \"field\": \"message\" } }\n```\n\nBranch on `code` (codes are never renamed or removed; new ones may appear). `errors` lists per-field messages.\n\n| Status | Codes |\n|---|---|\n| 400 | `invalid_json` · `invalid_cursor` · `idempotency_key_required` · `idempotency_key_invalid` |\n| 401 | `unauthorized` |\n| 403 | `insufficient_scope` · `sends_blocked` (sending paused for the organization) |\n| 404 | `not_found` · `number_not_found` · `chat_not_found` · `message_not_found` · `contact_not_found` · `media_not_found` · `upload_not_found` · `operation_not_found` · `webhook_endpoint_not_found` · `webhook_delivery_not_found` |\n| 409 | `number_not_working` · `number_already_linked` · `number_already_logged_out` · `number_not_pairing` · `number_not_awaiting_passkey` · `session_not_ready` · `number_allowance_reached` · `message_not_sent` · `message_revoked` · `not_own_message` · `edit_window_passed` · `revoke_window_passed` · `group_left` · `webhook_endpoint_limit_reached` · `webhook_delivery_pending` · `idempotency_key_in_use` |\n| 413 | `payload_too_large` |\n| 422 | `validation_failed` · `invalid_phone` · `invalid_recipient` · `tos_not_acknowledged` · `unsupported_type` · `too_large` · `upload_failed` · `invalid_webhook_url` · `idempotency_key_reused` |\n| 429 | `rate_limited` · `quota_exceeded` · `send_cap_reached` · `recipient_never_wrote` |\n| 500 | `internal_error` |\n| 502 | `gateway_<reason>` (WhatsApp's side did not answer an in-request call: retry) |\n| 503 | `enqueue_failed` (saved but not queued: retry with the same Idempotency-Key) |\n\n## Webhooks\n\nAdd an endpoint (`POST /webhooks/endpoints`, or in the dashboard) and we `POST` events to it: `number.status` · `message.received` · `message.sent` · `message.status` · `message.reaction` · `message.edited` · `message.revoked` · `chat.updated` · `group.updated` · `group.participants`.\nFilter per endpoint by event and by number; empty filters receive everything. Each event's payload is documented under\n**Webhooks** in this reference. Events describe live activity: messages imported from a number's history when it is\nlinked, or recovered after an outage, are readable through this API but are not sent as events.\n\n```json\n{ \"id\": \"evt_…\", \"event\": \"message.received\", \"created_at\": \"2026-11-11T09:12:44.120Z\", \"data\": { \"id\": \"msg_…\" } }\n```\n\n**Headers:** `X-Relay-Event` (the event), `X-Relay-Delivery` (`dlv_…`), `X-Relay-Timestamp` (unix seconds) and\n`X-Relay-Signature: v1=<hex>`. Deduplicate on the body's `id`: it stays the same across retries and redeliveries.\n\n**Verify the signature** before parsing: HMAC-SHA256 of `<X-Relay-Timestamp>.<raw body>` with the endpoint's secret\n(`whsec_…`, shown on create and rotate), compared in constant time; reject a timestamp more than\n5 minutes old.\n\n```js\nconst expected = \"v1=\" + crypto.createHmac(\"sha256\", secret).update(`${req.headers[\"x-relay-timestamp\"]}.${rawBody}`).digest(\"hex\");\nconst given = req.headers[\"x-relay-signature\"] ?? \"\";\nconst ok = given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));\n```\n\n**Retries:** answer any 2xx within 10 s. Timeouts, network errors, 408, 429 and 5xx are\nretried with backoff (30 s, 2 min, 10 min, 30 min, 1 h, 2 h, 4 h, 6 h, 8 h, …) for 24 h; any other status is final and\nredirects are not followed. After 10 deliveries in a row end failed, the endpoint is\ndisabled: fix it, then `PATCH` it with `enabled: true`. Deliveries are at least once and may arrive out of order:\ncompare timestamps or re-read the resource before overwriting. `POST /webhooks/endpoints/{id}/test` sends a\n`webhook.test` event; the delivery log (`GET /webhooks/deliveries`) shows each attempt and redelivers any finished one."},"servers":[{"url":"https://api.wrelay.site/v1"}],"tags":[{"name":"numbers","description":"Your WhatsApp numbers: connect (QR or pairing code), status, logout, delete."},{"name":"chats","description":"Conversations of a number: list, read state, archive, pin, mute."},{"name":"messages","description":"Send, read, react to, edit and revoke messages."},{"name":"groups","description":"Create and administer WhatsApp groups."},{"name":"contacts","description":"The people your numbers talk to, and « is this phone on WhatsApp »."},{"name":"media","description":"Upload files to send, and download attachments."},{"name":"webhooks","description":"Endpoints we POST events to, and their delivery log."},{"name":"operations","description":"The handle of every long request (202 answers)."}],"security":[{"ApiKey":[]}],"paths":{"/numbers":{"get":{"operationId":"listNumbers","summary":"List numbers","description":"The organization's numbers, oldest first.\n\n**Scope:** `read`","tags":["numbers"],"x-relay-scope":"read","parameters":[{"in":"query","name":"limit","schema":{"default":50,"description":"Page size, 1–200 (default 50).","type":"integer","minimum":1,"maximum":200},"description":"Page size, 1–200 (default 50)."},{"in":"query","name":"cursor","schema":{"description":"The `next_cursor` of the previous page; omit for the first page.","type":"string","maxLength":512},"description":"The `next_cursor` of the previous page; omit for the first page."},{"in":"query","name":"status","schema":{"description":"Comma-separated statuses to keep (pairing, passkey, working, disconnected, failed, logged_out).","example":"working,disconnected","type":"string"},"description":"Comma-separated statuses to keep (pairing, passkey, working, disconnected, failed, logged_out)."},{"in":"query","name":"search","schema":{"description":"Matches the label or the phone digits.","type":"string","maxLength":100},"description":"Matches the label or the phone digits."}],"responses":{"200":{"description":"A page of numbers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"operationId":"connectNumber","summary":"Connect a new number","description":"Adds a number in `pairing` and starts its WhatsApp session in the background. Poll the operation; once it succeeds, show `GET /numbers/{id}/pairing`'s QR code to the phone's owner (WhatsApp → Linked devices → Link a device), or request a pairing code. **Terms of Service:** linking a number this way is an unofficial WhatsApp device and breaks WhatsApp's terms; WhatsApp may restrict or ban the number without notice — `tos_acknowledged: true` records that you were told. Refused with `number_allowance_reached` when every number of the plan is in use.\n\n**Scope:** `admin` · **Idempotency-Key:** required","tags":["numbers"],"x-relay-scope":"admin","parameters":[{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tos_acknowledged":{"type":"boolean","description":"Must be `true`: you acknowledge that this links the number as an unofficial WhatsApp device, which breaks WhatsApp's terms — WhatsApp may restrict or ban the number without notice, and automated sending is the most common trigger."},"label":{"description":"Your name for the number (max 60 characters).","type":"string","maxLength":60}},"required":["tos_acknowledged"],"additionalProperties":false}}}},"responses":{"202":{"description":"The number is recorded and its session is starting","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberConnectAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/numbers/{id}":{"get":{"operationId":"getNumber","summary":"Get a number","description":"One number with its status, send capacity and history import progress.\n\n**Scope:** `read`","tags":["numbers"],"x-relay-scope":"read","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The number id (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The number id (`num_…`)."}],"responses":{"200":{"description":"The number","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Number"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"operationId":"deleteNumber","summary":"Delete a number","description":"The number reads as gone at once; in the background its WhatsApp session is removed and its chats, messages, contacts and media are deleted. This cannot be undone.\n\n**Scope:** `admin` · **Idempotency-Key:** required","tags":["numbers"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The number to delete (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The number to delete (`num_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"202":{"description":"The deletion is running","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/numbers/{id}/connect":{"post":{"operationId":"relinkNumber","summary":"Link a number again","description":"Starts a new pairing for a number that is `failed`, `logged_out` or still pairing. We never re-pair a number on our own: a ban is not lifted by linking again. Refused with `number_already_linked` for a working or reconnecting number. Same Terms-of-Service acknowledgement as a new connect.\n\n**Scope:** `admin` · **Idempotency-Key:** required","tags":["numbers"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The number to link again (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The number to link again (`num_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tos_acknowledged":{"type":"boolean","description":"Must be `true`: you acknowledge that this links the number as an unofficial WhatsApp device, which breaks WhatsApp's terms — WhatsApp may restrict or ban the number without notice, and automated sending is the most common trigger."},"label":{"description":"Your name for the number (max 60 characters).","type":"string","maxLength":60}},"required":["tos_acknowledged"],"additionalProperties":false}}}},"responses":{"202":{"description":"The number is back in pairing and its session is starting","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberConnectAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/numbers/{id}/pairing":{"get":{"operationId":"getNumberPairing","summary":"Get the pairing QR code","description":"The live QR code (status `pairing`) or passkey code (status `passkey`) of a number being linked. Poll every 2–5 s until `status` is `working`. Any other status answers with `qr` and `passkey_code` null.\n\n**Scope:** `admin`","tags":["numbers"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The number being linked (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The number being linked (`num_…`)."}],"responses":{"200":{"description":"The pairing state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberPairing"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/numbers/{id}/pairing-code":{"post":{"operationId":"requestNumberPairingCode","summary":"Request a pairing code","description":"Links with a phone number instead of a QR scan: answers an 8-character code to type on the phone. Only while the number is `pairing` and its session is placed (`session_not_ready` otherwise: retry in a moment). This endpoint takes no `Idempotency-Key`: its code is never stored, a retry asks for a new one.\n\n**Scope:** `admin`","tags":["numbers"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The number being linked (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The number being linked (`num_…`)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","minLength":3,"maxLength":32,"description":"The phone being linked, in international format.","example":"+212612345678"}},"required":["phone"],"additionalProperties":false}}}},"responses":{"200":{"description":"The pairing code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberPairingCode"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/numbers/{id}/passkey/confirm":{"post":{"operationId":"confirmNumberPasskey","summary":"Confirm the passkey","description":"Confirms that the code on the phone matches `passkey_code` (status `passkey`); the link then completes by itself — poll `GET /numbers/{id}` until `working`.\n\n**Scope:** `admin` · **Idempotency-Key:** required","tags":["numbers"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The number waiting for a passkey confirmation (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The number waiting for a passkey confirmation (`num_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"200":{"description":"Confirmed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberPasskeyConfirmed"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/numbers/{id}/logout":{"post":{"operationId":"logoutNumber","summary":"Log a number out","description":"Unlinks the device from the phone in the background; the number becomes `logged_out` and its chats and messages stay until the plan's retention purges them. Link it again with `POST /numbers/{id}/connect`.\n\n**Scope:** `admin` · **Idempotency-Key:** required","tags":["numbers"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The number to unlink (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The number to unlink (`num_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"202":{"description":"The logout is running","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/chats":{"get":{"operationId":"listChats","summary":"List chats","description":"Newest activity first (pinned: most recently pinned first), keyset-paged. A chat appears once it holds a message.\n\n**Scope:** `read`","tags":["chats"],"x-relay-scope":"read","parameters":[{"in":"query","name":"limit","schema":{"default":30,"description":"Page size, 1–100 (default 30).","type":"integer","minimum":1,"maximum":100},"description":"Page size, 1–100 (default 30)."},{"in":"query","name":"cursor","schema":{"description":"The `next_cursor` of the previous page; omit for the first page.","type":"string","maxLength":512},"description":"The `next_cursor` of the previous page; omit for the first page."},{"in":"query","name":"number_id","schema":{"type":"string","description":"One number's chats; omitted = every number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"}},{"in":"query","name":"view","schema":{"default":"all","description":"all (not archived) · unread · groups · archived · pinned (most recently pinned first).","type":"string","enum":["all","unread","groups","archived","pinned"]},"description":"all (not archived) · unread · groups · archived · pinned (most recently pinned first)."},{"in":"query","name":"search","schema":{"description":"Matches the chat's name, its contact's names or its phone digits.","type":"string","maxLength":100},"description":"Matches the chat's name, its contact's names or its phone digits."}],"responses":{"200":{"description":"A page of chats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/chats/{id}":{"get":{"operationId":"getChat","summary":"Get a chat","description":"One conversation with its unread count and inbox state. Its messages: `GET /chats/{id}/messages`.\n\n**Scope:** `read`","tags":["chats"],"x-relay-scope":"read","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The chat id (`chat_…`)."}],"responses":{"200":{"description":"The chat","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chat"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/chats/{id}/archive":{"post":{"operationId":"archiveChat","summary":"Archive a chat","description":"Moves the chat out of the main list (view `archived`). Inbox state only: nothing is sent to WhatsApp. Archiving an archived chat changes nothing.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["chats"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"200":{"description":"The chat after the change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chat"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/chats/{id}/unarchive":{"post":{"operationId":"unarchiveChat","summary":"Unarchive a chat","description":"Brings the chat back to the main list. Inbox state only.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["chats"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"200":{"description":"The chat after the change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chat"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/chats/{id}/pin":{"post":{"operationId":"pinChat","summary":"Pin a chat","description":"Pins the chat (view `pinned`). Inbox state only.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["chats"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"200":{"description":"The chat after the change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chat"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/chats/{id}/unpin":{"post":{"operationId":"unpinChat","summary":"Unpin a chat","description":"Unpins the chat. Inbox state only.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["chats"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"200":{"description":"The chat after the change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chat"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/chats/{id}/mute":{"post":{"operationId":"muteChat","summary":"Mute a chat","description":"Silences the chat's notifications until `until`, or always. Inbox state only.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["chats"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"until":{"description":"Mute until this instant (ISO 8601); omitted = always.","example":"2026-12-01T08:00:00Z","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"additionalProperties":false}}}},"responses":{"200":{"description":"The chat after the change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chat"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/chats/{id}/unmute":{"post":{"operationId":"unmuteChat","summary":"Unmute a chat","description":"Turns the chat's notifications back on. Inbox state only.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["chats"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"200":{"description":"The chat after the change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chat"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/chats/{id}/read":{"post":{"operationId":"markChatRead","summary":"Mark a chat read","description":"Clears the unread count and sends WhatsApp read receipts for the chat's unread messages (in the background, coalesced). A chat with nothing unread changes nothing.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["chats"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"200":{"description":"The chat after the change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chat"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/chats/{id}/messages":{"get":{"operationId":"listChatMessages","summary":"List a chat's messages","description":"Newest first, keyset-paged: pass `next_cursor` back as `cursor` to read older messages. Pages stay stable while new messages arrive.\n\n**Scope:** `read`","tags":["messages"],"x-relay-scope":"read","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The conversation (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The conversation (`chat_…`)."},{"in":"query","name":"limit","schema":{"default":50,"description":"Page size, 1–100 (default 50).","type":"integer","minimum":1,"maximum":100},"description":"Page size, 1–100 (default 50)."},{"in":"query","name":"cursor","schema":{"description":"The `next_cursor` of the previous page; omit for the first page.","type":"string","maxLength":512},"description":"The `next_cursor` of the previous page; omit for the first page."}],"responses":{"200":{"description":"A page of messages","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/messages/{id}":{"get":{"operationId":"getMessage","summary":"Get a message","description":"One message with its current status, sender and attachment. Download the attachment with `GET /media/{media.id}`.\n\n**Scope:** `read`","tags":["messages"],"x-relay-scope":"read","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The message id (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The message id (`msg_…`)."}],"responses":{"200":{"description":"The message","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageOutput"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/messages":{"post":{"operationId":"sendMessage","summary":"Send a message","description":"Records the message `pending` under your `Idempotency-Key` and queues it; it leaves WhatsApp paced per number (a few seconds apart). Address an existing conversation with `chat_id`, or a phone with `number_id` + `to`. Media: upload the file first (`POST /media/uploads`), then pass its `upload_id`. A retry with the same key and body answers the same message with its current status and never sends twice (a message that failed before leaving WhatsApp is queued again). There is no bulk or broadcast endpoint: one request sends one message to one conversation.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["messages"],"x-relay-scope":"send","parameters":[{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"number_id":{"type":"string","description":"With `to`: the number that sends (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"to":{"description":"With `number_id`: the recipient's phone in international format. Opens the conversation if needed.","example":"+212612345678","type":"string","minLength":3,"maxLength":32},"chat_id":{"type":"string","description":"Instead of `number_id` + `to`: an existing conversation (direct or group) (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"type":{"type":"string","enum":["text","image","video","audio","voice","document"],"description":"`text`, or the kind of the uploaded file: `image` / `video` / `audio` are read from its type, any other file is a `document`; `voice` sends an audio file as a voice note."},"text":{"description":"The text (required for `text`), or the caption of a media message.","type":"string","maxLength":65536},"upload_id":{"type":"string","description":"A finalized upload (`POST /media/uploads` → PUT → finalize); required for media types (`upl_…`).","example":"upl_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"reply_to":{"type":"string","description":"A sent message of the same chat to quote (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"mentions":{"description":"Phones (international format) to mention in a group; put `@<digits>` in `text` where each should appear.","maxItems":50,"type":"array","items":{"type":"string","maxLength":32}}},"required":["type"],"additionalProperties":false}}}},"responses":{"202":{"description":"The message is recorded and queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/messages/{id}/react":{"post":{"operationId":"reactToMessage","summary":"React to a message","description":"Sets our number's reaction on a sent or received message (one per message: a new emoji replaces it, an empty one removes it). Queued, then confirmed by the `message.reaction` webhook.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["messages"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The message id (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The message id (`msg_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"emoji":{"type":"string","maxLength":16,"description":"One emoji; an empty string removes our reaction.","example":"👍"}},"required":["emoji"],"additionalProperties":false}}}},"responses":{"202":{"description":"The action is queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageActionAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/messages/{id}/edit":{"post":{"operationId":"editMessage","summary":"Edit a message","description":"Replaces the text (or caption) of one of our own sent messages, within 15 minutes of sending (`edit_window_passed` after). Queued; the message changes once WhatsApp confirms (`message.edited` webhook).\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["messages"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The message id (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The message id (`msg_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":65536,"description":"The new text."}},"required":["text"],"additionalProperties":false}}}},"responses":{"202":{"description":"The action is queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageActionAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/messages/{id}/revoke":{"post":{"operationId":"revokeMessage","summary":"Delete a message for everyone","description":"Deletes one of our own sent messages for everyone, within about 60 hours of sending (`revoke_window_passed` after). Queued; the message changes once WhatsApp confirms (`message.revoked` webhook).\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["messages"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The message id (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The message id (`msg_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"202":{"description":"The action is queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageActionAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/groups":{"post":{"operationId":"createGroup","summary":"Create a group","description":"Creates a WhatsApp group from one of your numbers with the given participants, in the background. On success the operation's `result.chat_id` is the new group's chat id.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["groups"],"x-relay-scope":"send","parameters":[{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"participants":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","maxLength":32},"description":"1 to 50 phones in international format.","example":["+212612345678"]},"number_id":{"type":"string","description":"The number that creates the group (it becomes its admin) (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"subject":{"type":"string","minLength":1,"maxLength":100,"description":"The group name (1–100 characters)."}},"required":["participants","number_id","subject"],"additionalProperties":false}}}},"responses":{"202":{"description":"The group is being created","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/groups/{id}":{"get":{"operationId":"getGroup","summary":"Get a group","description":"The group's subject, settings and participants as last synced from WhatsApp. A direct chat's id answers 404 `chat_not_found`.\n\n**Scope:** `read`","tags":["groups"],"x-relay-scope":"read","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The group's chat id (`chat_…`)."}],"responses":{"200":{"description":"The group","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Group"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"patch":{"operationId":"updateGroup","summary":"Update a group","description":"Changes the subject, description, picture and / or settings. Send only what changes: each field is its own WhatsApp command and answers its own operation. Our number must be an admin.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["groups"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The group's chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"description":"The new group name.","type":"string","minLength":1,"maxLength":100},"description":{"description":"The new description (empty clears it).","type":"string","maxLength":2048},"upload_id":{"type":"string","description":"A finalized image upload for the group picture (`upl_…`).","example":"upl_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"only_admins_send":{"description":"Only admins can send messages.","type":"boolean"},"only_admins_edit":{"description":"Only admins can edit the group's info.","type":"boolean"}},"additionalProperties":false}}}},"responses":{"202":{"description":"The changes are running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupUpdateAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/groups/{id}/participants/add":{"post":{"operationId":"addGroupParticipants","summary":"Add participants","description":"Adds people to the group by phone. Our number must be an admin. The operation's `result.results` lists each participant's outcome (`ok`, `error_code`).\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["groups"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The group's chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"participants":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","maxLength":32},"description":"1 to 50 phones in international format.","example":["+212612345678"]}},"required":["participants"],"additionalProperties":false}}}},"responses":{"202":{"description":"The change is running","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/groups/{id}/participants/remove":{"post":{"operationId":"removeGroupParticipants","summary":"Remove participants","description":"Removes people from the group. Our number must be an admin. The operation's `result.results` lists each participant's outcome (`ok`, `error_code`).\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["groups"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The group's chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"participants":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","maxLength":128},"description":"1 to 50 current participants: a phone in international format, or the participant's `jid` from `GET /groups/{id}` (the only way to name one WhatsApp shows without a phone).","example":["+212612345678"]}},"required":["participants"],"additionalProperties":false}}}},"responses":{"202":{"description":"The change is running","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/groups/{id}/participants/promote":{"post":{"operationId":"promoteGroupParticipants","summary":"Promote participants","description":"Makes participants group admins. Our number must be an admin. The operation's `result.results` lists each participant's outcome (`ok`, `error_code`).\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["groups"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The group's chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"participants":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","maxLength":128},"description":"1 to 50 current participants: a phone in international format, or the participant's `jid` from `GET /groups/{id}` (the only way to name one WhatsApp shows without a phone).","example":["+212612345678"]}},"required":["participants"],"additionalProperties":false}}}},"responses":{"202":{"description":"The change is running","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/groups/{id}/participants/demote":{"post":{"operationId":"demoteGroupParticipants","summary":"Demote participants","description":"Removes participants' admin rights. Our number must be an admin. The operation's `result.results` lists each participant's outcome (`ok`, `error_code`).\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["groups"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The group's chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"participants":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","maxLength":128},"description":"1 to 50 current participants: a phone in international format, or the participant's `jid` from `GET /groups/{id}` (the only way to name one WhatsApp shows without a phone).","example":["+212612345678"]}},"required":["participants"],"additionalProperties":false}}}},"responses":{"202":{"description":"The change is running","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/groups/{id}/invite-link":{"post":{"operationId":"getGroupInviteLink","summary":"Get the invite link","description":"Asks WhatsApp for the group's invite link (our number must be an admin); the operation's `result.invite_link` carries it.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["groups"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The group's chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"202":{"description":"The command is running","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/groups/{id}/invite-link/revoke":{"post":{"operationId":"revokeGroupInviteLink","summary":"Revoke the invite link","description":"Invalidates the current invite link; the operation's `result.invite_link` carries the new one.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["groups"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The group's chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"202":{"description":"The command is running","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/groups/{id}/leave":{"post":{"operationId":"leaveGroup","summary":"Leave a group","description":"Our number leaves the group; its history stays readable (the group's `left_at` is set).\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["groups"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The group's chat id (`chat_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"202":{"description":"The command is running","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/contacts":{"get":{"operationId":"listContacts","summary":"List contacts","description":"One number's contacts, newest first, keyset-paged.\n\n**Scope:** `read`","tags":["contacts"],"x-relay-scope":"read","parameters":[{"in":"query","name":"limit","schema":{"default":50,"description":"Page size, 1–200 (default 50).","type":"integer","minimum":1,"maximum":200},"description":"Page size, 1–200 (default 50)."},{"in":"query","name":"cursor","schema":{"description":"The `next_cursor` of the previous page; omit for the first page.","type":"string","maxLength":512},"description":"The `next_cursor` of the previous page; omit for the first page."},{"in":"query","name":"number_id","schema":{"type":"string","description":"The number whose contacts to list (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The number whose contacts to list (`num_…`)."},{"in":"query","name":"search","schema":{"description":"Matches a name or the phone digits.","type":"string","maxLength":100},"description":"Matches a name or the phone digits."}],"responses":{"200":{"description":"A page of contacts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/contacts/check":{"post":{"operationId":"checkWhatsAppNumber","summary":"Check a phone is on WhatsApp","description":"Asks WhatsApp, through one of your working numbers, whether a phone has an account. The operation's `result` is `{ phone, on_whatsapp }`. One phone per request: checking lists of numbers is not supported.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["contacts"],"x-relay-scope":"send","parameters":[{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","minLength":3,"maxLength":32,"description":"The phone to check, in international format.","example":"+212612345678"},"number_id":{"type":"string","description":"The working number that asks WhatsApp; omitted = the organization's oldest working number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"}},"required":["phone"],"additionalProperties":false}}}},"responses":{"202":{"description":"The check is running","headers":{"Location":{"required":true,"description":"The operation to poll: `/v1/operations/{id}`.","schema":{"type":"string","description":"The operation to poll: `/v1/operations/{id}`."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/contacts/{id}":{"get":{"operationId":"getContact","summary":"Get a contact","description":"One contact with its names and phone.\n\n**Scope:** `read`","tags":["contacts"],"x-relay-scope":"read","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The contact id (`ct_…`).","example":"ct_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The contact id (`ct_…`)."}],"responses":{"200":{"description":"The contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/media/uploads":{"post":{"operationId":"createMediaUpload","summary":"Create an upload slot","description":"Step 1 of sending a file: answers a presigned URL. Step 2: `PUT` the bytes to `upload.url` with `upload.headers`. Step 3: `POST /media/uploads/{id}/finalize`. Then send it: `POST /messages` with `upload_id`. Files never travel through this API. Refused with `unsupported_type` or `too_large` per WhatsApp's limits.\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["media"],"x-relay-scope":"send","parameters":[{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mime_type":{"type":"string","minLength":1,"maxLength":255,"description":"The file's type, e.g. image/jpeg, video/mp4, audio/ogg, application/pdf.","example":"image/jpeg"},"size_bytes":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"The exact size in bytes (images ≤ 16 MB, video ≤ 64 MB, audio ≤ 16 MB, documents ≤ 100 MB)."},"file_name":{"description":"Shown to the recipient for documents.","type":"string","maxLength":255},"kind":{"description":"How the file will be sent, when its type alone doesn't say (voice, sticker): the size limit follows the kind.","type":"string","enum":["image","video","audio","voice","document","sticker"]}},"required":["mime_type","size_bytes"],"additionalProperties":false}}}},"responses":{"201":{"description":"The upload slot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaUploadSlot"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/media/uploads/{id}/finalize":{"post":{"operationId":"finalizeMediaUpload","summary":"Finalize an upload","description":"Step 3: checks the stored file against the declared type and size and marks the upload `ready`; pass its `id` as `upload_id` to `POST /messages` within 24 h. A finalized upload answers as it is. `upload_failed` = the PUT did not reach storage (upload again).\n\n**Scope:** `send` · **Idempotency-Key:** required","tags":["media"],"x-relay-scope":"send","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The upload id (`upl_…`).","example":"upl_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The upload id (`upl_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"200":{"description":"The upload, ready to send","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaUpload"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/media/{id}":{"get":{"operationId":"getMediaDownload","summary":"Get a download URL","description":"Answers a short-lived signed URL for a message's attachment, issued per request. 404 `media_not_found` while it is not stored yet (`media.state` pending), once expired by retention, or when its message was deleted for everyone.\n\n**Scope:** `read`","tags":["media"],"x-relay-scope":"read","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"A message attachment id (`media.id` on a message) (`media_…`).","example":"media_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"A message attachment id (`media.id` on a message) (`media_…`)."},{"in":"query","name":"download","schema":{"description":"`true` makes the URL download the file under its name (Content-Disposition: attachment) instead of displaying it.","type":"string","enum":["true","false"]},"description":"`true` makes the URL download the file under its name (Content-Disposition: attachment) instead of displaying it."}],"responses":{"200":{"description":"The signed download","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaDownload"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/webhooks/endpoints":{"get":{"operationId":"listWebhookEndpoints","summary":"List webhook endpoints","description":"The organization's endpoints, oldest first (at most 20 exist). Secrets are never listed.\n\n**Scope:** `admin`","tags":["webhooks"],"x-relay-scope":"admin","parameters":[{"in":"query","name":"limit","schema":{"default":20,"description":"Page size, 1–100 (default 20).","type":"integer","minimum":1,"maximum":100},"description":"Page size, 1–100 (default 20)."},{"in":"query","name":"cursor","schema":{"description":"The `next_cursor` of the previous page; omit for the first page.","type":"string","maxLength":512},"description":"The `next_cursor` of the previous page; omit for the first page."}],"responses":{"200":{"description":"A page of endpoints","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"operationId":"createWebhookEndpoint","summary":"Add a webhook endpoint","description":"Registers a URL to receive events. The answer carries the signing `secret` — store it now, it is not shown again (a retry with the same Idempotency-Key within 24 h answers it again). At most 20 endpoints (`webhook_endpoint_limit_reached`).\n\n**Scope:** `admin` · **Idempotency-Key:** required","tags":["webhooks"],"x-relay-scope":"admin","parameters":[{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048,"description":"An https:// URL on the public internet (private, loopback and cluster-internal hosts are refused with `invalid_webhook_url`).","example":"https://example.com/hooks/relay"},"description":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"events":{"description":"The events to receive; empty or omitted = every event.","maxItems":10,"type":"array","items":{"type":"string","enum":["number.status","message.received","message.sent","message.status","message.reaction","message.edited","message.revoked","chat.updated","group.updated","group.participants"]}},"number_ids":{"description":"Only these numbers' events; empty or omitted = every number.","maxItems":200,"type":"array","items":{"type":"string","description":"A number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"}}},"required":["url"],"additionalProperties":false}}}},"responses":{"201":{"description":"The endpoint and its signing secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecret"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/webhooks/endpoints/{id}":{"get":{"operationId":"getWebhookEndpoint","summary":"Get a webhook endpoint","description":"One endpoint and its health. The secret is never answered here: rotate it to get a new one.\n\n**Scope:** `admin`","tags":["webhooks"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The endpoint id (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The endpoint id (`whk_…`)."}],"responses":{"200":{"description":"The endpoint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"patch":{"operationId":"updateWebhookEndpoint","summary":"Update a webhook endpoint","description":"Changes only the fields sent. Re-enable an auto-disabled endpoint with `enabled: true` once it answers again.\n\n**Scope:** `admin` · **Idempotency-Key:** required","tags":["webhooks"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The endpoint id (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The endpoint id (`whk_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048,"description":"An https:// URL on the public internet (private, loopback and cluster-internal hosts are refused with `invalid_webhook_url`).","example":"https://example.com/hooks/relay"},"description":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"events":{"description":"The events to receive; empty or omitted = every event.","maxItems":10,"type":"array","items":{"type":"string","enum":["number.status","message.received","message.sent","message.status","message.reaction","message.edited","message.revoked","chat.updated","group.updated","group.participants"]}},"number_ids":{"description":"Only these numbers' events; empty or omitted = every number.","maxItems":200,"type":"array","items":{"type":"string","description":"A number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"}},"enabled":{"description":"true re-enables an endpoint disabled by hand or by failures (its failure count resets); false disables it.","type":"boolean"}},"additionalProperties":false}}}},"responses":{"200":{"description":"The endpoint after the change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}},"delete":{"operationId":"deleteWebhookEndpoint","summary":"Delete a webhook endpoint","description":"Removes the endpoint and its delivery log; pending retries stop.\n\n**Scope:** `admin` · **Idempotency-Key:** required","tags":["webhooks"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The endpoint id (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The endpoint id (`whk_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointDeleted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/webhooks/endpoints/{id}/rotate-secret":{"post":{"operationId":"rotateWebhookSecret","summary":"Rotate the signing secret","description":"Replaces the endpoint's signing secret at once and answers the new one (shown this once). Deliveries signed from now on use it, retries of older events included.\n\n**Scope:** `admin` · **Idempotency-Key:** required","tags":["webhooks"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The endpoint id (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The endpoint id (`whk_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"200":{"description":"The endpoint and its new secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecret"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/webhooks/endpoints/{id}/test":{"post":{"operationId":"testWebhookEndpoint","summary":"Send a test event","description":"Queues a signed `webhook.test` event to this endpoint, whatever events and numbers it listens to (even disabled). Follow it in the delivery log.\n\n**Scope:** `admin` · **Idempotency-Key:** required","tags":["webhooks"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The endpoint id (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The endpoint id (`whk_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"202":{"description":"The test delivery is queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/webhooks/deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"List deliveries","description":"The delivery log, newest first, keyset-paged. Read one delivery for the exact body sent.\n\n**Scope:** `admin`","tags":["webhooks"],"x-relay-scope":"admin","parameters":[{"in":"query","name":"limit","schema":{"default":50,"description":"Page size, 1–100 (default 50).","type":"integer","minimum":1,"maximum":100},"description":"Page size, 1–100 (default 50)."},{"in":"query","name":"cursor","schema":{"description":"The `next_cursor` of the previous page; omit for the first page.","type":"string","maxLength":512},"description":"The `next_cursor` of the previous page; omit for the first page."},{"in":"query","name":"endpoint_id","schema":{"type":"string","description":"One endpoint's deliveries (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"}},{"in":"query","name":"status","schema":{"type":"string","enum":["pending","succeeded","failed"]}},{"in":"query","name":"event","schema":{"description":"One event name, e.g. `message.received`.","type":"string","maxLength":64},"description":"One event name, e.g. `message.received`."}],"responses":{"200":{"description":"A page of deliveries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/webhooks/deliveries/{id}":{"get":{"operationId":"getWebhookDelivery","summary":"Get a delivery","description":"One delivery with the exact body POSTed and your endpoint's last answer.\n\n**Scope:** `admin`","tags":["webhooks"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The delivery id (`dlv_…`).","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`)."}],"responses":{"200":{"description":"The delivery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryDetail"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/webhooks/deliveries/{id}/redeliver":{"post":{"operationId":"redeliverWebhook","summary":"Redeliver an event","description":"Sends a finished delivery again with the same body and the same `id` (your dedup key), opening a fresh 24 h retry window. Refused with `webhook_delivery_pending` while it is still being attempted.\n\n**Scope:** `admin` · **Idempotency-Key:** required","tags":["webhooks"],"x-relay-scope":"admin","parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The delivery id (`dlv_…`).","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`)."},{"in":"header","name":"Idempotency-Key","schema":{"type":"string","minLength":1,"maxLength":150,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`.","example":"3f1c9a52-7d43-4bb8-9a3e-1e2f5c6d7a80"},"required":true,"description":"A unique key per logical request (a UUID is ideal), 1–150 visible ASCII characters. A retry with the same key and the same request answers the stored response for 24 h and changes nothing; the same key with a different request answers 422 `idempotency_key_reused`."}],"responses":{"202":{"description":"The delivery is queued again","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/GatewayError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/operations/{id}":{"get":{"operationId":"getOperation","summary":"Get an operation","description":"Poll a long request's progress: every endpoint answering 202 returns `operation_id` and a `Location` header here. Poll every 1–2 s until `status` is `succeeded` or `failed`, then re-read the resource it changed.\n\n**Scope:** any key","tags":["operations"],"parameters":[{"in":"path","name":"id","schema":{"type":"string","description":"The operation id from a 202 answer (`op_…`).","example":"op_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The operation id from a 202 answer (`op_…`)."}],"responses":{"200":{"description":"The operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Operation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}}},"webhooks":{"number.status":{"post":{"operationId":"webhook_number_status","summary":"number.status","description":"A number's status changed: linked (`working`), reconnecting, `failed` (often a ban) or `logged_out` from the phone.","tags":["webhooks"],"parameters":[{"in":"header","name":"X-Relay-Event","schema":{"type":"string","description":"The event name (same as `event` in the body).","example":"message.received"},"required":true,"description":"The event name (same as `event` in the body)."},{"in":"header","name":"X-Relay-Timestamp","schema":{"type":"string","description":"Unix seconds when this attempt was signed.","example":"1794440000"},"required":true,"description":"Unix seconds when this attempt was signed."},{"in":"header","name":"X-Relay-Signature","schema":{"type":"string","description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`.","example":"v1=5f2b…"},"required":true,"description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`."},{"in":"header","name":"X-Relay-Delivery","schema":{"type":"string","description":"The delivery id (`dlv_…`), as in the delivery log.","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`), as in the delivery log."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The event id: stable across retries and redeliveries, deduplicate on it (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","const":"number.status"},"created_at":{"type":"string","description":"When the event was emitted. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/NumberStatusEvent"}},"required":["id","event","created_at","data"]}}}},"responses":{"200":{"description":"Any 2xx within 10 s acknowledges the delivery. 408, 429, 5xx and network errors are retried; any other status is final."}}}},"message.received":{"post":{"operationId":"webhook_message_received","summary":"message.received","description":"A message arrived on one of your numbers (1:1 or group).","tags":["webhooks"],"parameters":[{"in":"header","name":"X-Relay-Event","schema":{"type":"string","description":"The event name (same as `event` in the body).","example":"message.received"},"required":true,"description":"The event name (same as `event` in the body)."},{"in":"header","name":"X-Relay-Timestamp","schema":{"type":"string","description":"Unix seconds when this attempt was signed.","example":"1794440000"},"required":true,"description":"Unix seconds when this attempt was signed."},{"in":"header","name":"X-Relay-Signature","schema":{"type":"string","description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`.","example":"v1=5f2b…"},"required":true,"description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`."},{"in":"header","name":"X-Relay-Delivery","schema":{"type":"string","description":"The delivery id (`dlv_…`), as in the delivery log.","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`), as in the delivery log."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The event id: stable across retries and redeliveries, deduplicate on it (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","const":"message.received"},"created_at":{"type":"string","description":"When the event was emitted. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/Message"}},"required":["id","event","created_at","data"]}}}},"responses":{"200":{"description":"Any 2xx within 10 s acknowledges the delivery. 408, 429, 5xx and network errors are retried; any other status is final."}}}},"message.sent":{"post":{"operationId":"webhook_message_sent","summary":"message.sent","description":"A message left one of your numbers: one you sent through this API or the inbox once WhatsApp accepted it, or one typed on the phone itself.","tags":["webhooks"],"parameters":[{"in":"header","name":"X-Relay-Event","schema":{"type":"string","description":"The event name (same as `event` in the body).","example":"message.received"},"required":true,"description":"The event name (same as `event` in the body)."},{"in":"header","name":"X-Relay-Timestamp","schema":{"type":"string","description":"Unix seconds when this attempt was signed.","example":"1794440000"},"required":true,"description":"Unix seconds when this attempt was signed."},{"in":"header","name":"X-Relay-Signature","schema":{"type":"string","description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`.","example":"v1=5f2b…"},"required":true,"description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`."},{"in":"header","name":"X-Relay-Delivery","schema":{"type":"string","description":"The delivery id (`dlv_…`), as in the delivery log.","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`), as in the delivery log."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The event id: stable across retries and redeliveries, deduplicate on it (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","const":"message.sent"},"created_at":{"type":"string","description":"When the event was emitted. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/Message"}},"required":["id","event","created_at","data"]}}}},"responses":{"200":{"description":"Any 2xx within 10 s acknowledges the delivery. 408, 429, 5xx and network errors are retried; any other status is final."}}}},"message.status":{"post":{"operationId":"webhook_message_status","summary":"message.status","description":"An outbound message was delivered, read, or failed.","tags":["webhooks"],"parameters":[{"in":"header","name":"X-Relay-Event","schema":{"type":"string","description":"The event name (same as `event` in the body).","example":"message.received"},"required":true,"description":"The event name (same as `event` in the body)."},{"in":"header","name":"X-Relay-Timestamp","schema":{"type":"string","description":"Unix seconds when this attempt was signed.","example":"1794440000"},"required":true,"description":"Unix seconds when this attempt was signed."},{"in":"header","name":"X-Relay-Signature","schema":{"type":"string","description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`.","example":"v1=5f2b…"},"required":true,"description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`."},{"in":"header","name":"X-Relay-Delivery","schema":{"type":"string","description":"The delivery id (`dlv_…`), as in the delivery log.","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`), as in the delivery log."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The event id: stable across retries and redeliveries, deduplicate on it (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","const":"message.status"},"created_at":{"type":"string","description":"When the event was emitted. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/MessageStatusEvent"}},"required":["id","event","created_at","data"]}}}},"responses":{"200":{"description":"Any 2xx within 10 s acknowledges the delivery. 408, 429, 5xx and network errors are retried; any other status is final."}}}},"message.reaction":{"post":{"operationId":"webhook_message_reaction","summary":"message.reaction","description":"A reaction on a message was added, changed or removed (by anyone, us included).","tags":["webhooks"],"parameters":[{"in":"header","name":"X-Relay-Event","schema":{"type":"string","description":"The event name (same as `event` in the body).","example":"message.received"},"required":true,"description":"The event name (same as `event` in the body)."},{"in":"header","name":"X-Relay-Timestamp","schema":{"type":"string","description":"Unix seconds when this attempt was signed.","example":"1794440000"},"required":true,"description":"Unix seconds when this attempt was signed."},{"in":"header","name":"X-Relay-Signature","schema":{"type":"string","description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`.","example":"v1=5f2b…"},"required":true,"description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`."},{"in":"header","name":"X-Relay-Delivery","schema":{"type":"string","description":"The delivery id (`dlv_…`), as in the delivery log.","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`), as in the delivery log."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The event id: stable across retries and redeliveries, deduplicate on it (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","const":"message.reaction"},"created_at":{"type":"string","description":"When the event was emitted. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/MessageReactionEvent"}},"required":["id","event","created_at","data"]}}}},"responses":{"200":{"description":"Any 2xx within 10 s acknowledges the delivery. 408, 429, 5xx and network errors are retried; any other status is final."}}}},"message.edited":{"post":{"operationId":"webhook_message_edited","summary":"message.edited","description":"A message's text or caption was edited.","tags":["webhooks"],"parameters":[{"in":"header","name":"X-Relay-Event","schema":{"type":"string","description":"The event name (same as `event` in the body).","example":"message.received"},"required":true,"description":"The event name (same as `event` in the body)."},{"in":"header","name":"X-Relay-Timestamp","schema":{"type":"string","description":"Unix seconds when this attempt was signed.","example":"1794440000"},"required":true,"description":"Unix seconds when this attempt was signed."},{"in":"header","name":"X-Relay-Signature","schema":{"type":"string","description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`.","example":"v1=5f2b…"},"required":true,"description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`."},{"in":"header","name":"X-Relay-Delivery","schema":{"type":"string","description":"The delivery id (`dlv_…`), as in the delivery log.","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`), as in the delivery log."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The event id: stable across retries and redeliveries, deduplicate on it (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","const":"message.edited"},"created_at":{"type":"string","description":"When the event was emitted. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/Message"}},"required":["id","event","created_at","data"]}}}},"responses":{"200":{"description":"Any 2xx within 10 s acknowledges the delivery. 408, 429, 5xx and network errors are retried; any other status is final."}}}},"message.revoked":{"post":{"operationId":"webhook_message_revoked","summary":"message.revoked","description":"A message was deleted for everyone.","tags":["webhooks"],"parameters":[{"in":"header","name":"X-Relay-Event","schema":{"type":"string","description":"The event name (same as `event` in the body).","example":"message.received"},"required":true,"description":"The event name (same as `event` in the body)."},{"in":"header","name":"X-Relay-Timestamp","schema":{"type":"string","description":"Unix seconds when this attempt was signed.","example":"1794440000"},"required":true,"description":"Unix seconds when this attempt was signed."},{"in":"header","name":"X-Relay-Signature","schema":{"type":"string","description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`.","example":"v1=5f2b…"},"required":true,"description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`."},{"in":"header","name":"X-Relay-Delivery","schema":{"type":"string","description":"The delivery id (`dlv_…`), as in the delivery log.","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`), as in the delivery log."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The event id: stable across retries and redeliveries, deduplicate on it (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","const":"message.revoked"},"created_at":{"type":"string","description":"When the event was emitted. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/MessageRevokedEvent"}},"required":["id","event","created_at","data"]}}}},"responses":{"200":{"description":"Any 2xx within 10 s acknowledges the delivery. 408, 429, 5xx and network errors are retried; any other status is final."}}}},"chat.updated":{"post":{"operationId":"webhook_chat_updated","summary":"chat.updated","description":"A chat's name, unread count or inbox state (archived, pinned, muted) changed.","tags":["webhooks"],"parameters":[{"in":"header","name":"X-Relay-Event","schema":{"type":"string","description":"The event name (same as `event` in the body).","example":"message.received"},"required":true,"description":"The event name (same as `event` in the body)."},{"in":"header","name":"X-Relay-Timestamp","schema":{"type":"string","description":"Unix seconds when this attempt was signed.","example":"1794440000"},"required":true,"description":"Unix seconds when this attempt was signed."},{"in":"header","name":"X-Relay-Signature","schema":{"type":"string","description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`.","example":"v1=5f2b…"},"required":true,"description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`."},{"in":"header","name":"X-Relay-Delivery","schema":{"type":"string","description":"The delivery id (`dlv_…`), as in the delivery log.","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`), as in the delivery log."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The event id: stable across retries and redeliveries, deduplicate on it (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","const":"chat.updated"},"created_at":{"type":"string","description":"When the event was emitted. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/ChatUpdatedEvent"}},"required":["id","event","created_at","data"]}}}},"responses":{"200":{"description":"Any 2xx within 10 s acknowledges the delivery. 408, 429, 5xx and network errors are retried; any other status is final."}}}},"group.updated":{"post":{"operationId":"webhook_group_updated","summary":"group.updated","description":"A group's subject, description or settings changed, or our number left it.","tags":["webhooks"],"parameters":[{"in":"header","name":"X-Relay-Event","schema":{"type":"string","description":"The event name (same as `event` in the body).","example":"message.received"},"required":true,"description":"The event name (same as `event` in the body)."},{"in":"header","name":"X-Relay-Timestamp","schema":{"type":"string","description":"Unix seconds when this attempt was signed.","example":"1794440000"},"required":true,"description":"Unix seconds when this attempt was signed."},{"in":"header","name":"X-Relay-Signature","schema":{"type":"string","description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`.","example":"v1=5f2b…"},"required":true,"description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`."},{"in":"header","name":"X-Relay-Delivery","schema":{"type":"string","description":"The delivery id (`dlv_…`), as in the delivery log.","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`), as in the delivery log."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The event id: stable across retries and redeliveries, deduplicate on it (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","const":"group.updated"},"created_at":{"type":"string","description":"When the event was emitted. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/GroupUpdatedEvent"}},"required":["id","event","created_at","data"]}}}},"responses":{"200":{"description":"Any 2xx within 10 s acknowledges the delivery. 408, 429, 5xx and network errors are retried; any other status is final."}}}},"group.participants":{"post":{"operationId":"webhook_group_participants","summary":"group.participants","description":"A group's participants or their roles changed.","tags":["webhooks"],"parameters":[{"in":"header","name":"X-Relay-Event","schema":{"type":"string","description":"The event name (same as `event` in the body).","example":"message.received"},"required":true,"description":"The event name (same as `event` in the body)."},{"in":"header","name":"X-Relay-Timestamp","schema":{"type":"string","description":"Unix seconds when this attempt was signed.","example":"1794440000"},"required":true,"description":"Unix seconds when this attempt was signed."},{"in":"header","name":"X-Relay-Signature","schema":{"type":"string","description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`.","example":"v1=5f2b…"},"required":true,"description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`."},{"in":"header","name":"X-Relay-Delivery","schema":{"type":"string","description":"The delivery id (`dlv_…`), as in the delivery log.","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`), as in the delivery log."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The event id: stable across retries and redeliveries, deduplicate on it (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","const":"group.participants"},"created_at":{"type":"string","description":"When the event was emitted. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/GroupParticipantsEvent"}},"required":["id","event","created_at","data"]}}}},"responses":{"200":{"description":"Any 2xx within 10 s acknowledges the delivery. 408, 429, 5xx and network errors are retried; any other status is final."}}}},"webhook.test":{"post":{"operationId":"webhook_webhook_test","summary":"webhook.test","description":"Sent by `POST /webhooks/endpoints/{id}/test`; never by a real change.","tags":["webhooks"],"parameters":[{"in":"header","name":"X-Relay-Event","schema":{"type":"string","description":"The event name (same as `event` in the body).","example":"message.received"},"required":true,"description":"The event name (same as `event` in the body)."},{"in":"header","name":"X-Relay-Timestamp","schema":{"type":"string","description":"Unix seconds when this attempt was signed.","example":"1794440000"},"required":true,"description":"Unix seconds when this attempt was signed."},{"in":"header","name":"X-Relay-Signature","schema":{"type":"string","description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`.","example":"v1=5f2b…"},"required":true,"description":"`v1=<hex HMAC-SHA256(secret, \"<X-Relay-Timestamp>.<raw body>\")>`."},{"in":"header","name":"X-Relay-Delivery","schema":{"type":"string","description":"The delivery id (`dlv_…`), as in the delivery log.","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"required":true,"description":"The delivery id (`dlv_…`), as in the delivery log."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The event id: stable across retries and redeliveries, deduplicate on it (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","const":"webhook.test"},"created_at":{"type":"string","description":"When the event was emitted. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/WebhookTestEvent"}},"required":["id","event","created_at","data"]}}}},"responses":{"200":{"description":"Any 2xx within 10 s acknowledges the delivery. 408, 429, 5xx and network errors are retried; any other status is final."}}}}},"components":{"schemas":{"NumberStatusEvent":{"type":"object","properties":{"id":{"type":"string","description":"The number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"label":{"type":["string","null"]},"phone":{"type":["string","null"]},"status":{"type":"string","enum":["pairing","passkey","working","disconnected","failed","logged_out"]},"status_reason":{"type":["string","null"]},"status_changed_at":{"description":"When the status changed. ISO 8601, UTC.","type":["string","null"]},"connected_at":{"description":"When the current link started. ISO 8601, UTC.","type":["string","null"]}},"required":["id","label","phone","status","status_reason","status_changed_at","connected_at"],"description":"The number's status after the change. Full detail: `GET /numbers/{id}`."},"Message":{"type":"object","properties":{"id":{"type":"string","description":"The message id (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"number_id":{"type":"string","description":"The number that sent or received it (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"chat_id":{"type":"string","description":"The conversation (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"chat_type":{"type":"string","enum":["dm","group"]},"chat_jid":{"type":"string","description":"WhatsApp's id of the conversation."},"wa_message_id":{"description":"WhatsApp's own message id (unique within its chat), set once the message left; null while `pending`.","type":["string","null"]},"from_me":{"type":"boolean"},"sender":{"anyOf":[{"$ref":"#/components/schemas/MessageSender"},{"type":"null"}]},"type":{"type":"string","enum":["text","image","video","audio","voice","document","sticker","location","contact","poll","system","unsupported"]},"body":{"description":"The text, or a media caption; null once revoked.","type":["string","null"]},"content":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"Type-specific fields (location, contact card, poll options); null once revoked."},"media":{"anyOf":[{"$ref":"#/components/schemas/MessageMedia"},{"type":"null"}]},"quoted_message_id":{"anyOf":[{"type":"string","description":"The message replied to (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},{"type":"null"}]},"quoted_wa_message_id":{"type":["string","null"]},"mentioned_jids":{"type":"array","items":{"type":"string"}},"is_forwarded":{"type":"boolean"},"is_view_once":{"type":"boolean"},"status":{"type":"string","enum":["pending","sent","delivered","read","failed"],"description":"pending → sent → delivered → read, or failed. Only moves forward."},"error_code":{"description":"Why a send failed (status `failed`).","type":["string","null"]},"source":{"type":"string","enum":["inbox","api","live","catch_up","history"],"description":"api (sent with this API) · inbox (sent from the web inbox) · live / catch_up / history (received or synced from the phone)."},"client_key":{"description":"The `Idempotency-Key` the message was sent with.","type":["string","null"]},"sent_at":{"description":"When it was sent or received. ISO 8601, UTC.","type":["string","null"]},"edited_at":{"description":"When it was last edited. ISO 8601, UTC.","type":["string","null"]},"revoked_at":{"description":"When it was deleted for everyone. ISO 8601, UTC.","type":["string","null"]}},"required":["id","number_id","chat_id","chat_type","chat_jid","wa_message_id","from_me","sender","type","body","content","media","quoted_message_id","quoted_wa_message_id","mentioned_jids","is_forwarded","is_view_once","status","error_code","source","client_key","sent_at","edited_at","revoked_at"],"description":"A message, sent or received. The same object is the `data` of the `message.received`, `message.sent` and `message.edited` webhooks."},"MessageSender":{"type":"object","properties":{"jid":{"type":"string","description":"WhatsApp's id of the author (canonical form)."},"phone":{"description":"E.164, when WhatsApp exposes it (null for a privacy id).","type":["string","null"]},"name":{"description":"The saved name, else the author's own profile name.","type":["string","null"]},"contact_id":{"anyOf":[{"type":"string","description":"The author's contact (`ct_…`).","example":"ct_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},{"type":"null"}]}},"required":["jid","phone","name","contact_id"],"description":"Who wrote the message; null for a system message."},"MessageMedia":{"type":"object","properties":{"id":{"type":"string","description":"The attachment id: download it with `GET /media/{id}` (`media_…`).","example":"media_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"state":{"type":"string","enum":["pending","stored","expired","failed"],"description":"pending (still downloading from WhatsApp) · stored (downloadable) · expired (past the plan's retention) · failed (WhatsApp no longer serves it)."},"mime":{"type":["string","null"]},"size_bytes":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"file_name":{"type":["string","null"]},"duration_seconds":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"width":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"height":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"sha256":{"type":["string","null"]}},"required":["id","state","mime","size_bytes","file_name","duration_seconds","width","height","sha256"],"description":"A message's attachment, stored in our private bucket."},"MessageStatusEvent":{"type":"object","properties":{"id":{"type":"string","description":"The message (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"number_id":{"type":"string","description":"The number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"chat_id":{"type":"string","description":"The conversation (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"wa_message_id":{"type":["string","null"]},"from_me":{"type":"boolean"},"client_key":{"description":"The `Idempotency-Key` the message was sent with.","type":["string","null"]},"status":{"type":"string","enum":["pending","sent","delivered","read","failed"]},"error_code":{"type":["string","null"]},"sent_at":{"description":"When it was sent. ISO 8601, UTC.","type":["string","null"]}},"required":["id","number_id","chat_id","wa_message_id","from_me","client_key","status","error_code","sent_at"],"description":"An outbound message's new status; carries no content."},"MessageReactionEvent":{"type":"object","properties":{"message":{"type":"object","properties":{"id":{"type":"string","description":"The message (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"number_id":{"type":"string","description":"The number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"chat_id":{"type":"string","description":"The conversation (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"wa_message_id":{"type":["string","null"]},"from_me":{"type":"boolean"}},"required":["id","number_id","chat_id","wa_message_id","from_me"]},"reactions":{"type":"array","items":{"type":"object","properties":{"sender_jid":{"type":"string"},"sender_phone":{"type":["string","null"]},"from_me":{"type":"boolean"},"emoji":{"type":"string"},"reacted_at":{"description":"When the reaction was set. ISO 8601, UTC.","type":["string","null"]}},"required":["sender_jid","sender_phone","from_me","emoji","reacted_at"]}}},"required":["message","reactions"],"description":"The message's whole current reaction list (state, not a delta: a removed reaction is simply absent)."},"MessageRevokedEvent":{"type":"object","properties":{"id":{"type":"string","description":"The message (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"number_id":{"type":"string","description":"The number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"chat_id":{"type":"string","description":"The conversation (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"wa_message_id":{"type":["string","null"]},"from_me":{"type":"boolean"},"revoked_at":{"description":"When it was deleted for everyone. ISO 8601, UTC.","type":["string","null"]}},"required":["id","number_id","chat_id","wa_message_id","from_me","revoked_at"],"description":"A message was deleted for everyone; its content is gone."},"ChatUpdatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"The conversation (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"number_id":{"type":"string","description":"The number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"type":{"type":"string","enum":["dm","group"]},"jid":{"type":"string"},"phone":{"type":["string","null"]},"name":{"type":["string","null"]},"unread_count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"last_message_at":{"description":"The newest message. ISO 8601, UTC.","type":["string","null"]},"archived":{"type":"boolean"},"pinned":{"type":"boolean"},"muted_until":{"description":"Muted until. ISO 8601, UTC.","type":["string","null"]}},"required":["id","number_id","type","jid","phone","name","unread_count","last_message_at","archived","pinned","muted_until"],"description":"The chat after the change. Full detail: `GET /chats/{id}`."},"GroupUpdatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"number_id":{"type":"string","description":"The number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"jid":{"type":"string"},"subject":{"type":["string","null"]},"description":{"type":["string","null"]},"only_admins_send":{"type":"boolean"},"only_admins_edit":{"type":"boolean"},"created_at":{"description":"When WhatsApp created the group. ISO 8601, UTC.","type":["string","null"]},"left_at":{"description":"When our number left it. ISO 8601, UTC.","type":["string","null"]}},"required":["id","number_id","jid","subject","description","only_admins_send","only_admins_edit","created_at","left_at"],"description":"The group after the change. Full detail: `GET /groups/{id}`."},"GroupParticipantsEvent":{"type":"object","properties":{"id":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"number_id":{"type":"string","description":"The number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"jid":{"type":"string"},"participants":{"type":"array","items":{"type":"object","properties":{"jid":{"type":"string"},"phone":{"type":["string","null"]},"role":{"type":"string","enum":["member","admin","superadmin"]}},"required":["jid","phone","role"]}}},"required":["id","number_id","jid","participants"],"description":"The group's whole current participant list (state, not a delta)."},"WebhookTestEvent":{"type":"object","properties":{"endpoint_id":{"type":"string","description":"The endpoint tested (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"message":{"type":"string"}},"required":["endpoint_id","message"]},"NumberList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Number"}},"has_more":{"type":"boolean"},"next_cursor":{"description":"Pass as `cursor` to read the next page; null on the last page.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of numbers, oldest first."},"Number":{"type":"object","properties":{"id":{"type":"string","description":"The number id (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"label":{"description":"Your name for the number.","type":["string","null"]},"phone":{"description":"E.164, known once paired.","type":["string","null"]},"display_name":{"description":"The WhatsApp profile name.","type":["string","null"]},"avatar_url":{"description":"The profile picture: a signed URL valid 5 minutes, issued per request.","type":["string","null"]},"status":{"type":"string","enum":["pairing","passkey","working","disconnected","failed","logged_out"],"description":"pairing (waiting for a QR scan or pairing code) · passkey (confirm on the phone) · working · disconnected (reconnecting by itself) · failed (banned or refused: link again) · logged_out (unlinked from the phone: link again)."},"status_reason":{"description":"A failure code (never free text) for failed / disconnected / logged_out.","type":["string","null"]},"status_changed_at":{"description":"When the status last changed. ISO 8601, UTC.","type":["string","null"]},"first_connected_at":{"description":"When the number was first linked. ISO 8601, UTC.","type":["string","null"]},"connected_at":{"description":"When the current link started. ISO 8601, UTC.","type":["string","null"]},"last_event_at":{"description":"The last event WhatsApp sent for this number. ISO 8601, UTC.","type":["string","null"]},"can_send":{"type":"boolean","description":"false for every status but `working`."},"daily_send_cap":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Today's send cap: it ramps up over a new number's first two weeks."},"history_import":{"type":"object","properties":{"status":{"type":"string","enum":["pending","running","completed","failed","skipped"]},"started_at":{"description":"Import start. ISO 8601, UTC.","type":["string","null"]},"finished_at":{"description":"Import end. ISO 8601, UTC.","type":["string","null"]},"chats_total":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"chats_done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"messages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"error_code":{"type":["string","null"]}},"required":["status","started_at","finished_at","chats_total","chats_done","messages","error_code"],"additionalProperties":false,"description":"The import of past chats after the first link, within the plan's retention window."},"created_at":{"description":"When the number was added. ISO 8601, UTC.","type":["string","null"]}},"required":["id","label","phone","display_name","avatar_url","status","status_reason","status_changed_at","first_connected_at","connected_at","last_event_at","can_send","daily_send_cap","history_import","created_at"],"additionalProperties":false,"description":"A WhatsApp number linked to your organization as a device. Its `number.status` webhook carries a subset of these fields."},"Problem":{"type":"object","properties":{"type":{"type":"string","description":"`urn:relay:problem:<code>`: a stable identifier of the problem type."},"title":{"type":"string","description":"A short summary of the HTTP status."},"status":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"code":{"type":"string","description":"The machine-readable reason: branch on this (see « Errors »). Codes are never renamed or removed."},"detail":{"description":"A human explanation of this occurrence.","type":"string"},"errors":{"description":"Per-field messages, keyed by the request field (dotted for nested fields).","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"required":["type","title","status","code"],"additionalProperties":false,"description":"Every non-2xx answer: an RFC 9457 problem document (`application/problem+json`)."},"NumberConnectAccepted":{"type":"object","properties":{"operation_id":{"type":"string","description":"The operation to poll (`op_…`).","example":"op_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"operation":{"$ref":"#/components/schemas/Operation"},"number":{"$ref":"#/components/schemas/Number"}},"required":["operation_id","operation","number"],"additionalProperties":false,"description":"The number is recorded `pairing` and its session is being placed on a gateway: poll the operation, then read `GET /numbers/{id}/pairing` for the QR code."},"Operation":{"type":"object","properties":{"id":{"type":"string","description":"The operation id (`op_…`).","example":"op_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"kind":{"type":"string","enum":["number_connect","number_logout","number_delete","organization_delete","group_command","number_check","number_restart","data_export"]},"status":{"type":"string","enum":["queued","running","succeeded","failed"],"description":"queued → running → succeeded | failed. A live operation past its expiry (1 h) reads `failed` with `error.code` `expired`."},"progress":{"anyOf":[{"type":"object","properties":{"done":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"total":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]}},"required":["done","total"],"additionalProperties":false},{"type":"null"}]},"result":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"Set on success, by kind — `number_connect`: `{ number_id }` (then read `GET /numbers/{id}/pairing`); `number_logout` / `number_delete`: `{ number_id }`; `number_check`: `{ phone, on_whatsapp }`; `group_command`: `{ chat_id }` (create), `{ invite_link }` (invite link), `{ results: [{ phone, jid, ok, error_code }] }` (participants), `{}` otherwise."},"error":{"anyOf":[{"$ref":"#/components/schemas/OperationError"},{"type":"null"}]},"created_at":{"description":"When the request was accepted. ISO 8601, UTC.","type":["string","null"]},"started_at":{"description":"When a worker picked it up. ISO 8601, UTC.","type":["string","null"]},"finished_at":{"description":"When it settled. ISO 8601, UTC.","type":["string","null"]}},"required":["id","kind","status","progress","result","error","created_at","started_at","finished_at"],"additionalProperties":false,"description":"A long request's handle: every 202 answer points here. Poll `GET /operations/{id}` (1 s, then 2 s) until it settles, then re-read what it changed."},"OperationError":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":["string","null"]}},"required":["code","message"],"additionalProperties":false,"description":"Why the operation failed; `code` is machine-readable (e.g. `number_not_working`, `expired`)."},"OperationAccepted":{"type":"object","properties":{"operation_id":{"type":"string","description":"The operation to poll (`op_…`).","example":"op_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"operation":{"$ref":"#/components/schemas/Operation"}},"required":["operation_id","operation"],"additionalProperties":false,"description":"The request was accepted and runs in the background."},"NumberPairing":{"type":"object","properties":{"number_id":{"type":"string","description":"The number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"status":{"type":"string","enum":["pairing","passkey","working","disconnected","failed","logged_out"]},"qr":{"description":"While `pairing`: the QR payload to render as a QR code (it rotates: read it again every few seconds). Never store or log it.","type":["string","null"]},"passkey_code":{"description":"While `passkey`: the code the phone shows; confirm with `POST /numbers/{id}/passkey/confirm` once they match.","type":["string","null"]},"waiting_for_session":{"type":"boolean","description":"true = the session is not placed yet: ask again in a moment."},"error_code":{"description":"`gateway_<reason>` when WhatsApp's side did not answer; try again.","type":["string","null"]}},"required":["number_id","status","qr","passkey_code","waiting_for_session","error_code"],"additionalProperties":false,"description":"What to show the phone's owner to link the number. Fetched live; nothing is stored."},"NumberPairingCode":{"type":"object","properties":{"number_id":{"type":"string","description":"The number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"code":{"type":"string","description":"Type it on the phone: WhatsApp → Linked devices → Link with phone number. Answered once, never stored."}},"required":["number_id","code"],"additionalProperties":false},"NumberPasskeyConfirmed":{"type":"object","properties":{"number_id":{"type":"string","description":"The number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"confirmed":{"type":"boolean","const":true}},"required":["number_id","confirmed"],"additionalProperties":false},"ChatList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Chat"}},"has_more":{"type":"boolean"},"next_cursor":{"description":"Pass as `cursor` to read the next page; null on the last page.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of chats."},"Chat":{"type":"object","properties":{"id":{"type":"string","description":"The chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"number_id":{"type":"string","description":"The number the conversation belongs to (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"type":{"type":"string","enum":["dm","group"],"description":"dm (one person) or group."},"jid":{"type":"string","description":"WhatsApp's id of the conversation (canonical form)."},"phone":{"description":"E.164 of the other side of a direct chat, when WhatsApp exposes it.","type":["string","null"]},"name":{"description":"The group subject, or a direct chat's stored name (prefer the contact's name for a direct chat).","type":["string","null"]},"description":{"description":"The group description.","type":["string","null"]},"avatar_url":{"description":"The picture: a signed URL valid 5 minutes, issued per request.","type":["string","null"]},"unread_count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"last_message_at":{"description":"The newest message. ISO 8601, UTC.","type":["string","null"]},"archived":{"type":"boolean"},"pinned":{"type":"boolean"},"pinned_at":{"description":"When it was pinned. ISO 8601, UTC.","type":["string","null"]},"muted":{"type":"boolean"},"muted_until":{"description":"Muted until this instant (year 9999 = always). ISO 8601, UTC.","type":["string","null"]},"left_at":{"description":"Group: when our number stopped being a participant. ISO 8601, UTC.","type":["string","null"]},"only_admins_send":{"type":"boolean"},"only_admins_edit":{"type":"boolean"},"group_created_at":{"description":"Group: when WhatsApp created it. ISO 8601, UTC.","type":["string","null"]},"created_at":{"description":"When we first saw the conversation. ISO 8601, UTC.","type":["string","null"]}},"required":["id","number_id","type","jid","phone","name","description","avatar_url","unread_count","last_message_at","archived","pinned","pinned_at","muted","muted_until","left_at","only_admins_send","only_admins_edit","group_created_at","created_at"],"additionalProperties":false,"description":"A conversation of one number. Its `chat.updated` webhook carries a subset of these fields."},"MessageList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MessageOutput"}},"has_more":{"type":"boolean"},"next_cursor":{"description":"Pass as `cursor` to read the next page; null on the last page.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of messages, newest first."},"MessageAccepted":{"type":"object","properties":{"id":{"type":"string","description":"The message id (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"status":{"type":"string","enum":["pending","sent","delivered","read","failed"],"description":"`pending` on a new send; a retried request answers the message's current status."},"chat_id":{"type":"string","description":"The conversation it was recorded in (created on a first message to `to`) (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"}},"required":["id","status","chat_id"],"additionalProperties":false,"description":"The message is recorded and queued; follow it with the `message.sent` / `message.status` webhooks or `GET /messages/{id}`."},"MessageActionAccepted":{"type":"object","properties":{"message_id":{"type":"string","description":"The message acted on (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"kind":{"type":"string","enum":["reaction","edit","revoke"]},"status":{"type":"string","enum":["pending","sent","failed"],"description":"`pending` when accepted; a retried request answers the action's current status."}},"required":["message_id","kind","status"],"additionalProperties":false,"description":"The reaction, edit or revoke is queued; the message changes once WhatsApp confirms (`message.reaction`, `message.edited`, `message.revoked` webhooks)."},"Group":{"type":"object","properties":{"id":{"type":"string","description":"The group's chat id (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"number_id":{"type":"string","description":"The number in the group (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"jid":{"type":"string","description":"WhatsApp's id of the group."},"subject":{"type":["string","null"]},"description":{"type":["string","null"]},"avatar_url":{"description":"The group picture: a signed URL valid 5 minutes, issued per request.","type":["string","null"]},"only_admins_send":{"type":"boolean"},"only_admins_edit":{"type":"boolean"},"created_at":{"description":"When WhatsApp created the group. ISO 8601, UTC.","type":["string","null"]},"left_at":{"description":"When our number stopped being a participant (read-only from then on). ISO 8601, UTC.","type":["string","null"]},"participants":{"type":"array","items":{"$ref":"#/components/schemas/GroupParticipant"},"description":"Admins first."}},"required":["id","number_id","jid","subject","description","avatar_url","only_admins_send","only_admins_edit","created_at","left_at","participants"],"additionalProperties":false,"description":"A WhatsApp group one of your numbers is in. Its id is the group's chat id (`chat_…`): read its messages with `GET /chats/{id}/messages`."},"GroupParticipant":{"type":"object","properties":{"jid":{"type":"string","description":"WhatsApp's id of the participant (canonical form)."},"phone":{"description":"E.164, when WhatsApp exposes it (null for a privacy id).","type":["string","null"]},"role":{"type":"string","enum":["member","admin","superadmin"]},"is_self":{"type":"boolean","description":"true for our own number."}},"required":["jid","phone","role","is_self"],"additionalProperties":false},"GroupUpdateAccepted":{"type":"object","properties":{"operations":{"type":"array","items":{"$ref":"#/components/schemas/OperationAccepted"},"description":"One operation per changed field, in the order subject, description, picture, settings."}},"required":["operations"],"additionalProperties":false},"ContactList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"has_more":{"type":"boolean"},"next_cursor":{"description":"Pass as `cursor` to read the next page; null on the last page.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of contacts, newest first."},"Contact":{"type":"object","properties":{"id":{"type":"string","description":"The contact id (`ct_…`).","example":"ct_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"number_id":{"type":"string","description":"The number that knows this contact (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"jid":{"type":"string","description":"WhatsApp's id of the person (canonical form)."},"phone":{"description":"E.164, when WhatsApp exposes it.","type":["string","null"]},"push_name":{"description":"The person's own profile name.","type":["string","null"]},"saved_name":{"description":"The name saved in the phone's address book.","type":["string","null"]},"business_name":{"type":["string","null"]},"display_name":{"description":"The name to show: saved, then business, then the profile's own name.","type":["string","null"]},"avatar_url":{"description":"The profile picture: a signed URL valid 5 minutes, issued per request.","type":["string","null"]},"created_at":{"description":"When we first saw the contact. ISO 8601, UTC.","type":["string","null"]}},"required":["id","number_id","jid","phone","push_name","saved_name","business_name","display_name","avatar_url","created_at"],"additionalProperties":false,"description":"A person one of your numbers talks to."},"MediaUploadSlot":{"type":"object","properties":{"id":{"type":"string","description":"The upload id: pass it as `upload_id` when sending (`upl_…`).","example":"upl_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"state":{"type":"string","enum":["pending","ready","consumed","expired"],"description":"pending (waiting for the PUT) · ready (finalized, sendable) · consumed (sent) · expired."},"file_name":{"type":["string","null"]},"mime_type":{"type":"string"},"declared_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"size_bytes":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"The stored size, set at finalize."},"expires_at":{"description":"The upload must be sent before this instant (24 h). ISO 8601, UTC.","type":["string","null"]},"created_at":{"description":"When the slot was created. ISO 8601, UTC.","type":["string","null"]},"upload":{"type":"object","properties":{"method":{"type":"string","const":"PUT"},"url":{"type":"string","description":"A presigned storage URL: PUT the raw bytes to it (no Authorization header). Never log it."},"headers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Send exactly these headers with the PUT (the declared type and size are enforced)."},"max_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"expires_at":{"description":"The URL stops accepting the PUT at this instant (15 minutes). ISO 8601, UTC.","type":["string","null"]}},"required":["method","url","headers","max_bytes","expires_at"],"additionalProperties":false,"description":"How to upload the bytes."}},"required":["id","state","file_name","mime_type","declared_bytes","size_bytes","expires_at","created_at","upload"],"additionalProperties":false},"MediaUpload":{"type":"object","properties":{"id":{"type":"string","description":"The upload id: pass it as `upload_id` when sending (`upl_…`).","example":"upl_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"state":{"type":"string","enum":["pending","ready","consumed","expired"],"description":"pending (waiting for the PUT) · ready (finalized, sendable) · consumed (sent) · expired."},"file_name":{"type":["string","null"]},"mime_type":{"type":"string"},"declared_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"size_bytes":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"The stored size, set at finalize."},"expires_at":{"description":"The upload must be sent before this instant (24 h). ISO 8601, UTC.","type":["string","null"]},"created_at":{"description":"When the slot was created. ISO 8601, UTC.","type":["string","null"]}},"required":["id","state","file_name","mime_type","declared_bytes","size_bytes","expires_at","created_at"],"additionalProperties":false,"description":"A file uploaded to send. It is consumed by the message (or group picture) that uses it."},"MediaDownload":{"type":"object","properties":{"id":{"type":"string","description":"The attachment id (`media_…`).","example":"media_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"url":{"type":"string","description":"A signed URL to GET the file (no Authorization header), valid 5 minutes. Never store or log it."},"expires_at":{"description":"The URL stops working at this instant. ISO 8601, UTC.","type":["string","null"]},"mime_type":{"type":["string","null"]},"size_bytes":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"file_name":{"type":["string","null"]}},"required":["id","url","expires_at","mime_type","size_bytes","file_name"],"additionalProperties":false},"WebhookEndpointList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}},"has_more":{"type":"boolean"},"next_cursor":{"description":"Pass as `cursor` to read the next page; null on the last page.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of webhook endpoints."},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string","description":"The endpoint id (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"url":{"type":"string"},"description":{"type":["string","null"]},"events":{"type":"array","items":{"type":"string","enum":["number.status","message.received","message.sent","message.status","message.reaction","message.edited","message.revoked","chat.updated","group.updated","group.participants"]},"description":"The events it receives; empty = every event."},"number_ids":{"type":"array","items":{"type":"string","description":"A number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"description":"The numbers whose events it receives; empty = every number."},"enabled":{"type":"boolean","description":"false once disabled by hand or after repeated failed deliveries."},"disabled_at":{"description":"When it was disabled. ISO 8601, UTC.","type":["string","null"]},"disabled_reason":{"anyOf":[{"type":"string","enum":["manual","failures"]},{"type":"null"}]},"consecutive_failures":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Deliveries in a row that ended failed (after their 24 h of retries)."},"last_delivery_at":{"description":"The last delivery attempt. ISO 8601, UTC.","type":["string","null"]},"last_success_at":{"description":"The last 2xx answer. ISO 8601, UTC.","type":["string","null"]},"created_at":{"description":"When it was added. ISO 8601, UTC.","type":["string","null"]}},"required":["id","url","description","events","number_ids","enabled","disabled_at","disabled_reason","consecutive_failures","last_delivery_at","last_success_at","created_at"],"additionalProperties":false,"description":"A URL we POST signed events to."},"WebhookEndpointWithSecret":{"type":"object","properties":{"id":{"type":"string","description":"The endpoint id (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"url":{"type":"string"},"description":{"type":["string","null"]},"events":{"type":"array","items":{"type":"string","enum":["number.status","message.received","message.sent","message.status","message.reaction","message.edited","message.revoked","chat.updated","group.updated","group.participants"]},"description":"The events it receives; empty = every event."},"number_ids":{"type":"array","items":{"type":"string","description":"A number (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"description":"The numbers whose events it receives; empty = every number."},"enabled":{"type":"boolean","description":"false once disabled by hand or after repeated failed deliveries."},"disabled_at":{"description":"When it was disabled. ISO 8601, UTC.","type":["string","null"]},"disabled_reason":{"anyOf":[{"type":"string","enum":["manual","failures"]},{"type":"null"}]},"consecutive_failures":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Deliveries in a row that ended failed (after their 24 h of retries)."},"last_delivery_at":{"description":"The last delivery attempt. ISO 8601, UTC.","type":["string","null"]},"last_success_at":{"description":"The last 2xx answer. ISO 8601, UTC.","type":["string","null"]},"created_at":{"description":"When it was added. ISO 8601, UTC.","type":["string","null"]},"secret":{"type":"string","description":"The signing secret (`whsec_…`): shown on create and rotate only. Verify `X-Relay-Signature` with it."}},"required":["id","url","description","events","number_ids","enabled","disabled_at","disabled_reason","consecutive_failures","last_delivery_at","last_success_at","created_at","secret"],"additionalProperties":false},"WebhookEndpointDeleted":{"type":"object","properties":{"id":{"type":"string","description":"The deleted endpoint (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"deleted":{"type":"boolean","const":true}},"required":["id","deleted"],"additionalProperties":false},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","description":"The delivery id, also sent as `X-Relay-Delivery` (`dlv_…`).","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event_id":{"type":"string","description":"The body's `id`: your dedup key, kept by a redelivery (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"endpoint_id":{"type":"string","description":"The endpoint (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","description":"The event name (`message.received`, …, or `webhook.test`)."},"status":{"type":"string","enum":["pending","succeeded","failed"],"description":"pending (being attempted) · succeeded · failed (24 h of retries ran out, a final 4xx, or a refused destination)."},"attempts":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"next_attempt_at":{"description":"The next retry. ISO 8601, UTC.","type":["string","null"]},"last_attempt_at":{"description":"The last attempt. ISO 8601, UTC.","type":["string","null"]},"give_up_at":{"description":"Retries stop at this instant. ISO 8601, UTC.","type":["string","null"]},"response_code":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Your endpoint's last HTTP status."},"error_code":{"description":"Why the last attempt failed: `http_<status>`, `timeout`, `network_error`, `destination_not_public` (the host resolved to a private, loopback or link-local address; not retried), `invalid_url` (not retried), …","type":["string","null"]},"delivered_at":{"description":"When your endpoint answered 2xx. ISO 8601, UTC.","type":["string","null"]},"created_at":{"description":"When the event was emitted. ISO 8601, UTC.","type":["string","null"]}},"required":["id","event_id","endpoint_id","event","status","attempts","next_attempt_at","last_attempt_at","give_up_at","response_code","error_code","delivered_at","created_at"],"additionalProperties":false,"description":"One event sent to one endpoint, with its attempts."},"WebhookDeliveryList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}},"has_more":{"type":"boolean"},"next_cursor":{"description":"Pass as `cursor` to read the next page; null on the last page.","type":["string","null"]}},"required":["data","has_more","next_cursor"],"additionalProperties":false,"description":"A page of deliveries, newest first."},"WebhookDeliveryDetail":{"type":"object","properties":{"id":{"type":"string","description":"The delivery id, also sent as `X-Relay-Delivery` (`dlv_…`).","example":"dlv_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event_id":{"type":"string","description":"The body's `id`: your dedup key, kept by a redelivery (`evt_…`).","example":"evt_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"endpoint_id":{"type":"string","description":"The endpoint (`whk_…`).","example":"whk_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"event":{"type":"string","description":"The event name (`message.received`, …, or `webhook.test`)."},"status":{"type":"string","enum":["pending","succeeded","failed"],"description":"pending (being attempted) · succeeded · failed (24 h of retries ran out, a final 4xx, or a refused destination)."},"attempts":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"next_attempt_at":{"description":"The next retry. ISO 8601, UTC.","type":["string","null"]},"last_attempt_at":{"description":"The last attempt. ISO 8601, UTC.","type":["string","null"]},"give_up_at":{"description":"Retries stop at this instant. ISO 8601, UTC.","type":["string","null"]},"response_code":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Your endpoint's last HTTP status."},"error_code":{"description":"Why the last attempt failed: `http_<status>`, `timeout`, `network_error`, `destination_not_public` (the host resolved to a private, loopback or link-local address; not retried), `invalid_url` (not retried), …","type":["string","null"]},"delivered_at":{"description":"When your endpoint answered 2xx. ISO 8601, UTC.","type":["string","null"]},"created_at":{"description":"When the event was emitted. ISO 8601, UTC.","type":["string","null"]},"response_body":{"description":"Your endpoint's last answer, truncated.","type":["string","null"]},"request_body":{"type":"string","description":"The exact JSON body POSTed on every attempt."}},"required":["id","event_id","endpoint_id","event","status","attempts","next_attempt_at","last_attempt_at","give_up_at","response_code","error_code","delivered_at","created_at","response_body","request_body"],"additionalProperties":false},"MessageOutput":{"type":"object","properties":{"id":{"type":"string","description":"The message id (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"number_id":{"type":"string","description":"The number that sent or received it (`num_…`).","example":"num_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"chat_id":{"type":"string","description":"The conversation (`chat_…`).","example":"chat_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"chat_type":{"type":"string","enum":["dm","group"]},"chat_jid":{"type":"string","description":"WhatsApp's id of the conversation."},"wa_message_id":{"description":"WhatsApp's own message id (unique within its chat), set once the message left; null while `pending`.","type":["string","null"]},"from_me":{"type":"boolean"},"sender":{"anyOf":[{"$ref":"#/components/schemas/MessageSenderOutput"},{"type":"null"}]},"type":{"type":"string","enum":["text","image","video","audio","voice","document","sticker","location","contact","poll","system","unsupported"]},"body":{"description":"The text, or a media caption; null once revoked.","type":["string","null"]},"content":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"Type-specific fields (location, contact card, poll options); null once revoked."},"media":{"anyOf":[{"$ref":"#/components/schemas/MessageMediaOutput"},{"type":"null"}]},"quoted_message_id":{"anyOf":[{"type":"string","description":"The message replied to (`msg_…`).","example":"msg_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},{"type":"null"}]},"quoted_wa_message_id":{"type":["string","null"]},"mentioned_jids":{"type":"array","items":{"type":"string"}},"is_forwarded":{"type":"boolean"},"is_view_once":{"type":"boolean"},"status":{"type":"string","enum":["pending","sent","delivered","read","failed"],"description":"pending → sent → delivered → read, or failed. Only moves forward."},"error_code":{"description":"Why a send failed (status `failed`).","type":["string","null"]},"source":{"type":"string","enum":["inbox","api","live","catch_up","history"],"description":"api (sent with this API) · inbox (sent from the web inbox) · live / catch_up / history (received or synced from the phone)."},"client_key":{"description":"The `Idempotency-Key` the message was sent with.","type":["string","null"]},"sent_at":{"description":"When it was sent or received. ISO 8601, UTC.","type":["string","null"]},"edited_at":{"description":"When it was last edited. ISO 8601, UTC.","type":["string","null"]},"revoked_at":{"description":"When it was deleted for everyone. ISO 8601, UTC.","type":["string","null"]}},"required":["id","number_id","chat_id","chat_type","chat_jid","wa_message_id","from_me","sender","type","body","content","media","quoted_message_id","quoted_wa_message_id","mentioned_jids","is_forwarded","is_view_once","status","error_code","source","client_key","sent_at","edited_at","revoked_at"],"additionalProperties":false,"description":"A message, sent or received. The same object is the `data` of the `message.received`, `message.sent` and `message.edited` webhooks."},"MessageSenderOutput":{"type":"object","properties":{"jid":{"type":"string","description":"WhatsApp's id of the author (canonical form)."},"phone":{"description":"E.164, when WhatsApp exposes it (null for a privacy id).","type":["string","null"]},"name":{"description":"The saved name, else the author's own profile name.","type":["string","null"]},"contact_id":{"anyOf":[{"type":"string","description":"The author's contact (`ct_…`).","example":"ct_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},{"type":"null"}]}},"required":["jid","phone","name","contact_id"],"additionalProperties":false,"description":"Who wrote the message; null for a system message."},"MessageMediaOutput":{"type":"object","properties":{"id":{"type":"string","description":"The attachment id: download it with `GET /media/{id}` (`media_…`).","example":"media_0193a4f2c1d87b3e9f4a2b6c8d0e1f23"},"state":{"type":"string","enum":["pending","stored","expired","failed"],"description":"pending (still downloading from WhatsApp) · stored (downloadable) · expired (past the plan's retention) · failed (WhatsApp no longer serves it)."},"mime":{"type":["string","null"]},"size_bytes":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"file_name":{"type":["string","null"]},"duration_seconds":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"width":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"height":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"sha256":{"type":["string","null"]}},"required":["id","state","mime","size_bytes","file_name","duration_seconds","width","height","sha256"],"additionalProperties":false,"description":"A message's attachment, stored in our private bucket."}},"responses":{"BadRequest":{"description":"Malformed request: invalid JSON, a bad cursor, or a missing / invalid `Idempotency-Key`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Unauthorized":{"description":"Missing, unknown, revoked or expired API key (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Forbidden":{"description":"The key lacks the endpoint's scope (`insufficient_scope`), or sending is paused for the organization (`sends_blocked`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotFound":{"description":"No such resource in this organization (`<resource>_not_found`). An id of another organization or with the wrong prefix reads the same.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Unprocessable":{"description":"Invalid fields (`validation_failed` with `errors`), a domain refusal (e.g. `invalid_phone`), or an `Idempotency-Key` reused for another request (`idempotency_key_reused`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"TooManyRequests":{"description":"Rate limit (`rate_limited`), monthly API or send quota (`quota_exceeded`), or a per-number daily cap (`send_cap_reached`, `recipient_never_wrote`). Honor `Retry-After` when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Conflict":{"description":"The resource's state refuses the request (e.g. `number_not_working`), or the same `Idempotency-Key` is still in flight (`idempotency_key_in_use`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"GatewayError":{"description":"WhatsApp did not answer an in-request call (`gateway_<reason>`): retry shortly.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Unavailable":{"description":"The request was saved but could not be queued (`enqueue_failed`): retry with the same `Idempotency-Key`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","description":"An organization API key: `Authorization: Bearer rl_live_…`."}}}}