{"openapi":"3.1.0","info":{"title":"WA Gateway API","version":"1.0.0","description":"Self-hosted WhatsApp gateway. Send WhatsApp messages from a linked number over HTTP and follow them to delivery. Authenticate with a device token in `Authorization`, bare or as `Bearer <token>`."},"tags":[{"name":"v1","description":"Public API — token-authenticated, rate-limited per key"},{"name":"public","description":"Published and supported. These operations appear in the reference site and may not change without notice; everything else is internal, however reachable it looks."}],"paths":{"/v1/messages/{id}":{"get":{"operationId":"getMessage","summary":"Get a message","tags":["v1","public"],"description":"The current status and timestamps of one message this device sent. The message must belong to the token's own device.","parameters":[{"schema":{"type":"string","minLength":1},"in":"path","name":"id","required":true,"description":"The message id, from the `id` array POST /v1/send returned."}],"security":[{"deviceToken":[]}],"responses":{"200":{"description":"The message, with its current status and timestamps.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always true on a successful request.","enum":[true]},"message":{"type":"object","properties":{"id":{"type":"string","description":"The message id, as returned in the `id` array of POST /v1/send."},"deviceId":{"type":"string","description":"The device that sent or received this message."},"requestId":{"description":"The `requestid` of the send that created it. Null on a received message.","type":["string","null"]},"direction":{"type":"string","enum":["out","in"],"description":"`out` for a message this device sent, `in` for one it received."},"targetInput":{"description":"The recipient exactly as you wrote it, before normalization.","type":["string","null"]},"jid":{"description":"The chat address in phone-number form, e.g. `628123456789@s.whatsapp.net`.","type":["string","null"]},"lid":{"description":"The chat's WhatsApp linked identity. This, not `jid`, is the stable identifier — treat both as opaque strings and never split them on `@`.","type":["string","null"]},"kind":{"type":"string","enum":["text","image","video","audio","document","location","poll"],"description":"What kind of content the message carries. Inferred from the fields you sent."},"content":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `text` for this variant.","enum":["text"]},"text":{"type":"string","maxLength":60000,"description":"The message body, after template substitution."}},"required":["kind","text"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `image` for this variant.","enum":["image"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `video` for this variant.","enum":["video"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `audio` for this variant.","enum":["audio"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `document` for this variant.","enum":["document"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `location` for this variant.","enum":["location"]},"latitude":{"type":"number","minimum":-90,"maximum":90,"description":"Decimal degrees, −90 to 90."},"longitude":{"type":"number","minimum":-180,"maximum":180,"description":"Decimal degrees, −180 to 180."}},"required":["kind","latitude","longitude"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `poll` for this variant.","enum":["poll"]},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The poll question."},"choices":{"minItems":2,"maxItems":12,"type":"array","items":{"type":"string","minLength":1,"description":"One poll option."},"description":"The options offered, 2 to 12 of them."},"selectableCount":{"type":"integer","minimum":1,"maximum":12,"description":"How many options a voter may pick. 1 for a single-answer poll."}},"required":["kind","name","choices","selectableCount"],"additionalProperties":false}],"description":"The message content, discriminated by `kind`."},"status":{"type":"string","enum":["waiting","queued","processing","sent","delivered","read","played","pending","invalid","failed","expired"],"description":"Where the message is in its lifecycle. It only ever moves forward: `waiting` (scheduled) → `queued` → `processing` → `sent` → `delivered` → `read` → `played`. `pending` is the accepted-but-not-yet-queued moment, and `invalid`, `failed` and `expired` are terminal."},"waMessageId":{"description":"WhatsApp's own id for the message. Null until it has been sent.","type":["string","null"]},"error":{"description":"Why a `failed` or `invalid` message did not go out. Null otherwise.","type":["string","null"]},"scheduledAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When a scheduled message is due to send. Null for an immediate one."},"queuedAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When the message was handed to the sender."},"sentAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When WhatsApp accepted it — one tick on the sender's screen."},"deliveredAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When it reached the recipient device — two ticks."},"readAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When the recipient opened it — blue ticks, if they share receipts."},"senderJid":{"description":"Who wrote a received message, in phone-number form. Null on a sent message.","type":["string","null"]},"senderLid":{"description":"The author's linked identity. Null on a sent message.","type":["string","null"]},"pushName":{"description":"The display name the author has set. Null on a sent message.","type":["string","null"]},"participant":{"description":"The individual sender inside a group chat. Null outside groups.","type":["string","null"]},"createdAt":{"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))$","description":"When the gateway first recorded the message, ISO 8601."}},"required":["id","deviceId","requestId","direction","targetInput","jid","lid","kind","content","status","waMessageId","error","scheduledAt","queuedAt","sentAt","deliveredAt","readAt","senderJid","senderLid","pushName","participant","createdAt"],"additionalProperties":false,"description":"The message, with its current status and timestamps."}},"required":["status","message"],"additionalProperties":false,"description":"The message, with its current status and timestamps."}}}},"401":{"description":"The token is missing, malformed or revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"The token is missing, malformed or revoked."}}}},"403":{"description":"An account token was used; these need the device token.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"An account token was used; these need the device token."}}}},"404":{"description":"No message with that id belongs to this device. A message that is somebody else's answers the same way — the id is never confirmed to exist.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"No message with that id belongs to this device. A message that is somebody else's answers the same way — the id is never confirmed to exist."}}}},"429":{"description":"Too many requests for this token. Wait the seconds in `retryAfter`.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"Too many requests for this token. Wait the seconds in `retryAfter`."}}}}}},"delete":{"operationId":"cancelMessage","summary":"Cancel a message","tags":["v1","public"],"description":"Cancels a message that has not been sent yet — one still `waiting`, `queued` or `pending`. A message already handed to WhatsApp cannot be recalled and answers `409`.","parameters":[{"schema":{"type":"string","minLength":1},"in":"path","name":"id","required":true,"description":"The message id, from the `id` array POST /v1/send returned."}],"security":[{"deviceToken":[]}],"responses":{"200":{"description":"The cancelled message, now in a terminal state.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always true on a successful request.","enum":[true]},"message":{"type":"object","properties":{"id":{"type":"string","description":"The message id, as returned in the `id` array of POST /v1/send."},"deviceId":{"type":"string","description":"The device that sent or received this message."},"requestId":{"description":"The `requestid` of the send that created it. Null on a received message.","type":["string","null"]},"direction":{"type":"string","enum":["out","in"],"description":"`out` for a message this device sent, `in` for one it received."},"targetInput":{"description":"The recipient exactly as you wrote it, before normalization.","type":["string","null"]},"jid":{"description":"The chat address in phone-number form, e.g. `628123456789@s.whatsapp.net`.","type":["string","null"]},"lid":{"description":"The chat's WhatsApp linked identity. This, not `jid`, is the stable identifier — treat both as opaque strings and never split them on `@`.","type":["string","null"]},"kind":{"type":"string","enum":["text","image","video","audio","document","location","poll"],"description":"What kind of content the message carries. Inferred from the fields you sent."},"content":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `text` for this variant.","enum":["text"]},"text":{"type":"string","maxLength":60000,"description":"The message body, after template substitution."}},"required":["kind","text"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `image` for this variant.","enum":["image"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `video` for this variant.","enum":["video"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `audio` for this variant.","enum":["audio"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `document` for this variant.","enum":["document"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `location` for this variant.","enum":["location"]},"latitude":{"type":"number","minimum":-90,"maximum":90,"description":"Decimal degrees, −90 to 90."},"longitude":{"type":"number","minimum":-180,"maximum":180,"description":"Decimal degrees, −180 to 180."}},"required":["kind","latitude","longitude"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `poll` for this variant.","enum":["poll"]},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The poll question."},"choices":{"minItems":2,"maxItems":12,"type":"array","items":{"type":"string","minLength":1,"description":"One poll option."},"description":"The options offered, 2 to 12 of them."},"selectableCount":{"type":"integer","minimum":1,"maximum":12,"description":"How many options a voter may pick. 1 for a single-answer poll."}},"required":["kind","name","choices","selectableCount"],"additionalProperties":false}],"description":"The message content, discriminated by `kind`."},"status":{"type":"string","enum":["waiting","queued","processing","sent","delivered","read","played","pending","invalid","failed","expired"],"description":"Where the message is in its lifecycle. It only ever moves forward: `waiting` (scheduled) → `queued` → `processing` → `sent` → `delivered` → `read` → `played`. `pending` is the accepted-but-not-yet-queued moment, and `invalid`, `failed` and `expired` are terminal."},"waMessageId":{"description":"WhatsApp's own id for the message. Null until it has been sent.","type":["string","null"]},"error":{"description":"Why a `failed` or `invalid` message did not go out. Null otherwise.","type":["string","null"]},"scheduledAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When a scheduled message is due to send. Null for an immediate one."},"queuedAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When the message was handed to the sender."},"sentAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When WhatsApp accepted it — one tick on the sender's screen."},"deliveredAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When it reached the recipient device — two ticks."},"readAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When the recipient opened it — blue ticks, if they share receipts."},"senderJid":{"description":"Who wrote a received message, in phone-number form. Null on a sent message.","type":["string","null"]},"senderLid":{"description":"The author's linked identity. Null on a sent message.","type":["string","null"]},"pushName":{"description":"The display name the author has set. Null on a sent message.","type":["string","null"]},"participant":{"description":"The individual sender inside a group chat. Null outside groups.","type":["string","null"]},"createdAt":{"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))$","description":"When the gateway first recorded the message, ISO 8601."}},"required":["id","deviceId","requestId","direction","targetInput","jid","lid","kind","content","status","waMessageId","error","scheduledAt","queuedAt","sentAt","deliveredAt","readAt","senderJid","senderLid","pushName","participant","createdAt"],"additionalProperties":false,"description":"The message, with its current status and timestamps."}},"required":["status","message"],"additionalProperties":false,"description":"The cancelled message, now in a terminal state."}}}},"401":{"description":"The token is missing, malformed or revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"The token is missing, malformed or revoked."}}}},"403":{"description":"An account token was used; these need the device token.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"An account token was used; these need the device token."}}}},"404":{"description":"No message with that id belongs to this device. A message that is somebody else's answers the same way — the id is never confirmed to exist.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"No message with that id belongs to this device. A message that is somebody else's answers the same way — the id is never confirmed to exist."}}}},"409":{"description":"The message has moved past the state this call needs — it has already been handed to WhatsApp.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"The message has moved past the state this call needs — it has already been handed to WhatsApp."}}}},"429":{"description":"Too many requests for this token. Wait the seconds in `retryAfter`.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"Too many requests for this token. Wait the seconds in `retryAfter`."}}}}}}},"/v1/messages/{id}/reschedule":{"post":{"operationId":"rescheduleMessage","summary":"Reschedule a message","tags":["v1","public"],"description":"Moves a scheduled message to a new send time. Only a message still `waiting` can be moved; a `schedule` in the past sends it immediately.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"schedule":{"description":"The new send time, as a unix timestamp in seconds. A time in the past sends the message immediately. Only a message still in `waiting` can be moved.","anyOf":[{"type":"number"},{"type":"string","pattern":"^-?\\d+$"}]}},"required":["schedule"]}}}},"parameters":[{"schema":{"type":"string","minLength":1},"in":"path","name":"id","required":true,"description":"The message id, from the `id` array POST /v1/send returned."}],"security":[{"deviceToken":[]}],"responses":{"200":{"description":"The message, with its new `scheduledAt`.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always true on a successful request.","enum":[true]},"message":{"type":"object","properties":{"id":{"type":"string","description":"The message id, as returned in the `id` array of POST /v1/send."},"deviceId":{"type":"string","description":"The device that sent or received this message."},"requestId":{"description":"The `requestid` of the send that created it. Null on a received message.","type":["string","null"]},"direction":{"type":"string","enum":["out","in"],"description":"`out` for a message this device sent, `in` for one it received."},"targetInput":{"description":"The recipient exactly as you wrote it, before normalization.","type":["string","null"]},"jid":{"description":"The chat address in phone-number form, e.g. `628123456789@s.whatsapp.net`.","type":["string","null"]},"lid":{"description":"The chat's WhatsApp linked identity. This, not `jid`, is the stable identifier — treat both as opaque strings and never split them on `@`.","type":["string","null"]},"kind":{"type":"string","enum":["text","image","video","audio","document","location","poll"],"description":"What kind of content the message carries. Inferred from the fields you sent."},"content":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `text` for this variant.","enum":["text"]},"text":{"type":"string","maxLength":60000,"description":"The message body, after template substitution."}},"required":["kind","text"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `image` for this variant.","enum":["image"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `video` for this variant.","enum":["video"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `audio` for this variant.","enum":["audio"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `document` for this variant.","enum":["document"]},"mediaId":{"description":"Opaque id of the stored object the gateway sent. Absent on a received message.","type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$"},"mimetype":{"type":"string","minLength":1,"maxLength":255,"description":"The media type, e.g. `image/jpeg`."},"url":{"description":"The `url` you asked the gateway to fetch. An echo — it is never fetched again.","type":"string","format":"uri"},"caption":{"description":"Text shown alongside the media.","type":"string","maxLength":60000},"filename":{"description":"The file name the recipient sees.","type":"string","maxLength":255}},"required":["kind","mimetype"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `location` for this variant.","enum":["location"]},"latitude":{"type":"number","minimum":-90,"maximum":90,"description":"Decimal degrees, −90 to 90."},"longitude":{"type":"number","minimum":-180,"maximum":180,"description":"Decimal degrees, −180 to 180."}},"required":["kind","latitude","longitude"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","description":"Discriminator — always `poll` for this variant.","enum":["poll"]},"name":{"type":"string","minLength":1,"maxLength":255,"description":"The poll question."},"choices":{"minItems":2,"maxItems":12,"type":"array","items":{"type":"string","minLength":1,"description":"One poll option."},"description":"The options offered, 2 to 12 of them."},"selectableCount":{"type":"integer","minimum":1,"maximum":12,"description":"How many options a voter may pick. 1 for a single-answer poll."}},"required":["kind","name","choices","selectableCount"],"additionalProperties":false}],"description":"The message content, discriminated by `kind`."},"status":{"type":"string","enum":["waiting","queued","processing","sent","delivered","read","played","pending","invalid","failed","expired"],"description":"Where the message is in its lifecycle. It only ever moves forward: `waiting` (scheduled) → `queued` → `processing` → `sent` → `delivered` → `read` → `played`. `pending` is the accepted-but-not-yet-queued moment, and `invalid`, `failed` and `expired` are terminal."},"waMessageId":{"description":"WhatsApp's own id for the message. Null until it has been sent.","type":["string","null"]},"error":{"description":"Why a `failed` or `invalid` message did not go out. Null otherwise.","type":["string","null"]},"scheduledAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When a scheduled message is due to send. Null for an immediate one."},"queuedAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When the message was handed to the sender."},"sentAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When WhatsApp accepted it — one tick on the sender's screen."},"deliveredAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When it reached the recipient device — two ticks."},"readAt":{"anyOf":[{"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))$"},{"type":"null"}],"description":"When the recipient opened it — blue ticks, if they share receipts."},"senderJid":{"description":"Who wrote a received message, in phone-number form. Null on a sent message.","type":["string","null"]},"senderLid":{"description":"The author's linked identity. Null on a sent message.","type":["string","null"]},"pushName":{"description":"The display name the author has set. Null on a sent message.","type":["string","null"]},"participant":{"description":"The individual sender inside a group chat. Null outside groups.","type":["string","null"]},"createdAt":{"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))$","description":"When the gateway first recorded the message, ISO 8601."}},"required":["id","deviceId","requestId","direction","targetInput","jid","lid","kind","content","status","waMessageId","error","scheduledAt","queuedAt","sentAt","deliveredAt","readAt","senderJid","senderLid","pushName","participant","createdAt"],"additionalProperties":false,"description":"The message, with its current status and timestamps."}},"required":["status","message"],"additionalProperties":false,"description":"The message, with its new `scheduledAt`."}}}},"400":{"description":"`schedule` is missing or is not a unix timestamp in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"`schedule` is missing or is not a unix timestamp in seconds."}}}},"401":{"description":"The token is missing, malformed or revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"The token is missing, malformed or revoked."}}}},"403":{"description":"An account token was used; these need the device token.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"An account token was used; these need the device token."}}}},"404":{"description":"No message with that id belongs to this device. A message that is somebody else's answers the same way — the id is never confirmed to exist.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"No message with that id belongs to this device. A message that is somebody else's answers the same way — the id is never confirmed to exist."}}}},"409":{"description":"The message has moved past the state this call needs — it has already been handed to WhatsApp.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"The message has moved past the state this call needs — it has already been handed to WhatsApp."}}}},"429":{"description":"Too many requests for this token. Wait the seconds in `retryAfter`.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"Too many requests for this token. Wait the seconds in `retryAfter`."}}}},"503":{"description":"The queue is unavailable, so the move could not be recorded.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"The queue is unavailable, so the move could not be recorded."}}}}}}},"/v1/send":{"post":{"operationId":"sendMessage","summary":"Send a message","tags":["v1","public"],"description":"Sends text, media, a location or a poll from this device to one or many recipients. Authenticate with the device token — an account token cannot send. The response arrives as soon as the messages are queued, so a `200` means accepted, not delivered: follow each id with GET /v1/messages/{id} or wait for the `message.status` webhook.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"target":{"description":"SEND-01/02 — one or more recipients, comma-separated. Each entry is a phone number, optionally followed by `|`-separated values for the message template: the first fills `{name}`, the rest fill `{var1}`, `{var2}`, … For example `628123456789|Budi|40192, 08129999888|Ani`. A number starting with `0` is expanded using `countryCode`. Required unless `data` is given.","type":"string","minLength":1},"message":{"description":"The text to send, or the caption when `url` or a file upload is present. Supports the `{name}` and `{var1}`…`{varN}` placeholders filled from `target`; an unmatched placeholder is left as written. Cannot be combined with `location` or `choices`.","type":"string","maxLength":60000},"url":{"description":"SEND-10 — a publicly reachable URL the gateway fetches once and sends as media. Max 4 MB. Private, loopback and link-local addresses are refused (`url invalid`). Mutually exclusive with a multipart file upload, `location` and `choices`.","type":"string","format":"uri"},"filename":{"description":"The name the recipient sees for a document. Ignored for image, video and audio.","type":"string","maxLength":255},"schedule":{"description":"SEND-05 — send at this unix timestamp (seconds) instead of now. The message is stored with status `waiting`; check, cancel or move it with the `/v1/messages/{id}` endpoints. A timestamp in the past sends immediately.","anyOf":[{"type":"number"},{"type":"string","pattern":"^-?\\d+$"}]},"delay":{"description":"SEND-04 — seconds to wait between consecutive targets, overriding the device pacing range. `\"5\"` is a fixed gap; `\"5-100\"` draws a random gap in that range for each target. Each bound is at most 600. The first target of a request never waits.","type":"string","pattern":"^\\d+(-\\d+)?$"},"countryCode":{"default":"62","description":"SEND-02 — the country code that replaces a leading `0` in a target, without the `+`. Defaults to `62` (Indonesia). Pass `\"0\"` to disable expansion, which requires every target to already be fully qualified.","type":"string","pattern":"^\\d{1,4}$"},"location":{"description":"SEND-11 — send a location pin instead of text, as `\"latitude,longitude\"` in decimal degrees, e.g. `\"-6.2088,106.8456\"`. Cannot be combined with `message`.","type":"string","pattern":"^-?\\d+(\\.\\d+)?,-?\\d+(\\.\\d+)?$"},"typing":{"description":"SEND-13 — show the composing indicator to the recipient for `duration` seconds before the message lands. Accepts a boolean, or `\"true\"` / `\"false\"` from a form-encoded caller.","type":"boolean"},"duration":{"description":"How long the typing indicator shows, in seconds (1–600). Only meaningful with `typing`. Accepts a number, or a digit string from a form-encoded caller.","anyOf":[{"type":"number"},{"type":"string","pattern":"^-?\\d+$"}]},"choices":{"description":"SEND-12 — send a poll instead of text. Comma-separated options, 2 to 12 of them, e.g. `\"yes, no, maybe\"`. Requires `pollname`. Cannot be combined with `message`.","type":"string"},"select":{"description":"Whether a poll accepts one answer or several. Defaults to `single`.","type":"string","enum":["single","multiple"]},"pollname":{"description":"The poll question. Required whenever `choices` is given.","type":"string","maxLength":255},"file":{"description":"Read-only echo of an uploaded file name. Set by the gateway when the request arrives as `multipart/form-data` with a `file` part; a JSON caller that sets it without uploading anything is refused. Send media as a file part or as `url`, never both.","type":"string","maxLength":255},"connectOnly":{"description":"Refuse the request with `device disconnected` when the device is not connected, instead of queueing the message until it reconnects. Defaults to true.","type":"boolean"},"sequence":{"description":"SEND-06 — send to the targets strictly in the order given, one after the other, rather than letting the pacing worker interleave them.","type":"boolean"},"preview":{"description":"Accepted for Fonnte compatibility and ignored. Link previews are not generated.","type":"boolean"},"inboxid":{"description":"MSG-08 — the id of a message this device received, to send this one as a reply quoting it. Must belong to the same device and the same chat.","type":"string"},"data":{"description":"SEND-09 — a JSON string holding an array of send objects, for addressing many recipients with different content in one request. Each element takes the same fields as this body except `data` and a file upload, and each must carry its own `target`. When `data` is given the top-level `target` is ignored.","type":"string"}}}}}},"security":[{"deviceToken":[]}],"responses":{"200":{"description":"Accepted. Every valid target has a queued message and an id; delivery has not happened yet.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always true on an accepted request.","enum":[true]},"id":{"type":"array","items":{"type":"string"},"description":"One message id per accepted target, in the same order as `target`. Poll each with GET /v1/messages/{id}, or wait for the `message.status` webhook."},"target":{"type":"array","items":{"type":"string"},"description":"The normalized recipients, digits only, one per accepted message."},"process":{"type":"string","description":"Always `pending`. The request was accepted and the messages are queued — delivery is asynchronous and this is not a delivery confirmation.","enum":["pending"]},"requestid":{"type":"string","description":"Correlation id for this request. It travels with every message it created, through the queue and into each webhook delivery — quote it when reporting a problem."},"detail":{"description":"Present when something about the request needs explaining — targets dropped as invalid, or messages held back by the device warm-up ceiling.","type":"string"}},"required":["status","id","target","process","requestid"],"additionalProperties":false,"description":"Accepted. Every valid target has a queued message and an id; delivery has not happened yet."}}}},"400":{"description":"The request could not be understood — a malformed body, a `data` batch that is not JSON, no recipient, no content, or more than one content kind.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"The request could not be understood — a malformed body, a `data` batch that is not JSON, no recipient, no content, or more than one content kind."}}}},"401":{"description":"The token is missing, malformed or revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"The token is missing, malformed or revoked."}}}},"403":{"description":"An account token was used; sending needs the device token.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"An account token was used; sending needs the device token."}}}},"404":{"description":"The token's device no longer exists.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"The token's device no longer exists."}}}},"429":{"description":"Too many requests for this token. Wait the seconds in `retryAfter`.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"boolean","description":"Always false on an error. The success envelope carries true.","enum":[false]},"reason":{"type":"string","enum":["token invalid","input invalid","target invalid","url invalid","url unreachable","file format not supported","file size must under 4MB","insufficient quota","device disconnected","JSON format invalid","rate limited","unauthenticated","forbidden","not found","internal error","device already connect","device already disconnected","device not found","engine unavailable","qr not ready","message not found","message not cancellable"],"description":"Why the request was refused. A closed vocabulary — match on it, never on the HTTP status alone, and never parse it for substrings. New reasons may be added; existing ones never change meaning."},"retryAfter":{"description":"Seconds to wait before retrying. Present only on `rate limited`, and mirrored in the `retry-after` response header.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","reason"],"additionalProperties":false,"description":"Too many requests for this token. Wait the seconds in `retryAfter`."}}}}}}}},"components":{"securitySchemes":{"deviceToken":{"type":"apiKey","in":"header","name":"Authorization","description":"Device token — scoped to exactly one device. `<token>` or `Bearer <token>`."}}},"servers":[{"url":"https://api.sapa.tokoaplikasi.id","description":"This instance"}]}