{"openapi":"3.1.0","info":{"title":"whatsapi.sh — the friendly WhatsApp API","version":"1.0.0","description":"Flat REST endpoints over your own paired WhatsApp numbers. Every operation here is also an MCP tool (see x-mcp-tools), authenticated by the same `wa_` key. Writing TypeScript or JavaScript? `npm i @whatsapi.sh/sdk` is one typed method per endpoint here."},"servers":[{"url":"http://127.0.0.1:3334"}],"tags":[{"name":"Numbers","description":"Add a number to your account, pair it by QR or pairing code, and follow its connection state."},{"name":"Messages","description":"Text, media, location pins, contact cards — to one chat or a batch, with typing indicator and read receipts."},{"name":"Queue","description":"Messages held back so a number's first approaches leave minutes apart instead of all at once — see what is waiting, or change your mind about one."},{"name":"OTP","description":"Deliver a one-time passcode and verify what the user typed — the code comes back in the response."},{"name":"Webhooks","description":"One signed-event endpoint per account: set it, test-fire it, rotate its secret, read its delivery log."},{"name":"Usage","description":"Metered sends for the billing month, split by API key."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A `wa_` API key from the dashboard."}}},"paths":{"/messages":{"post":{"operationId":"send_message_post","summary":"Send a WhatsApp message","tags":["Messages"],"description":"Send a text message to a phone number over WhatsApp. Counts as one message against the account's monthly quota.\n\n**A message to someone this number has never written to is not sent immediately.** It is accepted, charged and QUEUED: the answer carries `status: \"queued\"` and `scheduledAt`. It leaves 15–25 minutes later, one at a time. That is deliberate. A burst of first approaches is the fastest way to get a WhatsApp number banned, and spacing them out is what separates a number that lasts from one that does not. `message.sent` fires on the account's webhook when it goes out, carrying WhatsApp's id. Use `skipQueue: true` for a message the recipient is waiting for right now, or turn the queue off for the number with update_number.\n\nReplies to people who wrote first are never queued and never spend budget. A first approach also spends one of the number's NEW CONVERSATIONS for the day — the anti-ban ceiling reported as `newConversationsRemaining`. A `sent` answer carries WhatsApp's message id; delivery and read receipts arrive later on the account's webhook, not here.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"text":{"type":"string","minLength":1,"maxLength":4096,"description":"The message body."},"linkPreview":{"description":"Render a preview card for the first link in the text. Default: on.","type":"boolean"},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to","text"],"additionalProperties":false}}}},"responses":{"201":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]},"example":{"data":{"id":"9c1f0b7e-8b1a-4a2e-9b0a-1f2e3d4c5b6a","to":"+5511988887777","from":"a1b2c3d4-0000-0000-0000-000000000000","status":"queued","scheduledAt":"2026-08-24T19:18:00.000Z","timestamp":"2026-08-24T19:00:00.000Z","quotaRemaining":4999,"newConversationsRemaining":19,"creditRemaining":0}}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/messages/media":{"post":{"operationId":"send_media_post","summary":"Send a photo, video, voice note or file","tags":["Messages"],"description":"Send media over WhatsApp from a public URL or base64 data. `type` picks the bubble the recipient sees: `image` and `video` support a caption, `document` is a file attachment named by `fileName`, and `audio` becomes a voice note when the file is ogg/opus — any other audio format is sent as a playable attachment instead, because WhatsApp will not render a voice note that is not opus. Files are capped at 16MB, and `url` beats `base64` for anything sizeable. Counts as one message.\n\nLike every send: media to someone this number has never written to is QUEUED rather than sent, and comes back with `status: \"queued\"` and `scheduledAt`. The file is fetched when the slot comes up, so a `url` has to still be reachable then.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"type":{"type":"string","enum":["image","video","audio","document"],"description":"Which kind of media bubble to send."},"url":{"description":"Public https URL of the file — the better option. We stream the download and forward the bytes, so the size of the file never rides in your request.","type":"string","format":"uri"},"base64":{"description":"The file as base64, bare or as a `data:<mime>;base64,…` URI. Base64 inflates a request by a third, so prefer `url` for anything but small files.","type":"string","minLength":1},"mimeType":{"description":"Overrides the detected content type, e.g. `image/webp`.","type":"string","minLength":3},"caption":{"description":"Text shown under the media. Images and videos only — WhatsApp has no caption field for documents or audio.","type":"string","maxLength":1024},"fileName":{"description":"Documents: the filename WhatsApp shows on the bubble.","type":"string","minLength":1,"maxLength":255},"seconds":{"description":"Voice notes: duration, so the bubble shows the right length.","type":"integer","exclusiveMinimum":0,"maximum":600},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to","type"],"additionalProperties":false}}}},"responses":{"201":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/messages/image":{"post":{"operationId":"send_media_post","summary":"Send a photo, video, voice note or file","tags":["Messages"],"description":"Send media over WhatsApp from a public URL or base64 data. `type` picks the bubble the recipient sees: `image` and `video` support a caption, `document` is a file attachment named by `fileName`, and `audio` becomes a voice note when the file is ogg/opus — any other audio format is sent as a playable attachment instead, because WhatsApp will not render a voice note that is not opus. Files are capped at 16MB, and `url` beats `base64` for anything sizeable. Counts as one message.\n\nLike every send: media to someone this number has never written to is QUEUED rather than sent, and comes back with `status: \"queued\"` and `scheduledAt`. The file is fetched when the slot comes up, so a `url` has to still be reachable then.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"url":{"description":"Public https URL of the file — the better option. We stream the download and forward the bytes, so the size of the file never rides in your request.","type":"string","format":"uri"},"base64":{"description":"The file as base64, bare or as a `data:<mime>;base64,…` URI. Base64 inflates a request by a third, so prefer `url` for anything but small files.","type":"string","minLength":1},"mimeType":{"description":"Overrides the detected content type, e.g. `image/webp`.","type":"string","minLength":3},"caption":{"description":"Text shown under the media. Images and videos only — WhatsApp has no caption field for documents or audio.","type":"string","maxLength":1024},"fileName":{"description":"Documents: the filename WhatsApp shows on the bubble.","type":"string","minLength":1,"maxLength":255},"seconds":{"description":"Voice notes: duration, so the bubble shows the right length.","type":"integer","exclusiveMinimum":0,"maximum":600},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to"],"additionalProperties":false}}}},"responses":{"201":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/messages/video":{"post":{"operationId":"send_media_post","summary":"Send a photo, video, voice note or file","tags":["Messages"],"description":"Send media over WhatsApp from a public URL or base64 data. `type` picks the bubble the recipient sees: `image` and `video` support a caption, `document` is a file attachment named by `fileName`, and `audio` becomes a voice note when the file is ogg/opus — any other audio format is sent as a playable attachment instead, because WhatsApp will not render a voice note that is not opus. Files are capped at 16MB, and `url` beats `base64` for anything sizeable. Counts as one message.\n\nLike every send: media to someone this number has never written to is QUEUED rather than sent, and comes back with `status: \"queued\"` and `scheduledAt`. The file is fetched when the slot comes up, so a `url` has to still be reachable then.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"url":{"description":"Public https URL of the file — the better option. We stream the download and forward the bytes, so the size of the file never rides in your request.","type":"string","format":"uri"},"base64":{"description":"The file as base64, bare or as a `data:<mime>;base64,…` URI. Base64 inflates a request by a third, so prefer `url` for anything but small files.","type":"string","minLength":1},"mimeType":{"description":"Overrides the detected content type, e.g. `image/webp`.","type":"string","minLength":3},"caption":{"description":"Text shown under the media. Images and videos only — WhatsApp has no caption field for documents or audio.","type":"string","maxLength":1024},"fileName":{"description":"Documents: the filename WhatsApp shows on the bubble.","type":"string","minLength":1,"maxLength":255},"seconds":{"description":"Voice notes: duration, so the bubble shows the right length.","type":"integer","exclusiveMinimum":0,"maximum":600},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to"],"additionalProperties":false}}}},"responses":{"201":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/messages/audio":{"post":{"operationId":"send_media_post","summary":"Send a photo, video, voice note or file","tags":["Messages"],"description":"Send media over WhatsApp from a public URL or base64 data. `type` picks the bubble the recipient sees: `image` and `video` support a caption, `document` is a file attachment named by `fileName`, and `audio` becomes a voice note when the file is ogg/opus — any other audio format is sent as a playable attachment instead, because WhatsApp will not render a voice note that is not opus. Files are capped at 16MB, and `url` beats `base64` for anything sizeable. Counts as one message.\n\nLike every send: media to someone this number has never written to is QUEUED rather than sent, and comes back with `status: \"queued\"` and `scheduledAt`. The file is fetched when the slot comes up, so a `url` has to still be reachable then.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"url":{"description":"Public https URL of the file — the better option. We stream the download and forward the bytes, so the size of the file never rides in your request.","type":"string","format":"uri"},"base64":{"description":"The file as base64, bare or as a `data:<mime>;base64,…` URI. Base64 inflates a request by a third, so prefer `url` for anything but small files.","type":"string","minLength":1},"mimeType":{"description":"Overrides the detected content type, e.g. `image/webp`.","type":"string","minLength":3},"caption":{"description":"Text shown under the media. Images and videos only — WhatsApp has no caption field for documents or audio.","type":"string","maxLength":1024},"fileName":{"description":"Documents: the filename WhatsApp shows on the bubble.","type":"string","minLength":1,"maxLength":255},"seconds":{"description":"Voice notes: duration, so the bubble shows the right length.","type":"integer","exclusiveMinimum":0,"maximum":600},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to"],"additionalProperties":false}}}},"responses":{"201":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/messages/document":{"post":{"operationId":"send_media_post","summary":"Send a photo, video, voice note or file","tags":["Messages"],"description":"Send media over WhatsApp from a public URL or base64 data. `type` picks the bubble the recipient sees: `image` and `video` support a caption, `document` is a file attachment named by `fileName`, and `audio` becomes a voice note when the file is ogg/opus — any other audio format is sent as a playable attachment instead, because WhatsApp will not render a voice note that is not opus. Files are capped at 16MB, and `url` beats `base64` for anything sizeable. Counts as one message.\n\nLike every send: media to someone this number has never written to is QUEUED rather than sent, and comes back with `status: \"queued\"` and `scheduledAt`. The file is fetched when the slot comes up, so a `url` has to still be reachable then.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"url":{"description":"Public https URL of the file — the better option. We stream the download and forward the bytes, so the size of the file never rides in your request.","type":"string","format":"uri"},"base64":{"description":"The file as base64, bare or as a `data:<mime>;base64,…` URI. Base64 inflates a request by a third, so prefer `url` for anything but small files.","type":"string","minLength":1},"mimeType":{"description":"Overrides the detected content type, e.g. `image/webp`.","type":"string","minLength":3},"caption":{"description":"Text shown under the media. Images and videos only — WhatsApp has no caption field for documents or audio.","type":"string","maxLength":1024},"fileName":{"description":"Documents: the filename WhatsApp shows on the bubble.","type":"string","minLength":1,"maxLength":255},"seconds":{"description":"Voice notes: duration, so the bubble shows the right length.","type":"integer","exclusiveMinimum":0,"maximum":600},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to"],"additionalProperties":false}}}},"responses":{"201":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/messages/batch":{"post":{"operationId":"send_batch_post","summary":"Send one text to many recipients","tags":["Messages"],"description":"Send the same text to up to 25 recipients in one call — one message, and one message's worth of quota, per recipient. The answer reports each recipient separately: a batch where some land and some do not is the normal case, not an error. If something systemic stops the run (the number drops its session, the quota or rate limit refuses) the remaining recipients come back as `skipped` instead of being attempted. Send them again once that is cleared.\n\n**While the number paces its sends, which is the default, a batch does not send: it queues.** Every recipient comes back as `queued` with its own `scheduledAt`. The messages leave one at a time: minutes apart for anyone this number has never written to, seconds apart for people it already talks to. Twenty-five messages arriving in one second is precisely the pattern WhatsApp restricts numbers for, so the endpoint no longer does that. Watch `message.sent` on the account's webhook for the ids, `GET /v1/queue` for what is still waiting, and turn pacing off for the number if you truly want them at once. Paid plans only.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"minItems":1,"maxItems":25,"type":"array","items":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"description":"Recipients, up to 25. Each gets its own message; no duplicates."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"text":{"type":"string","minLength":1,"maxLength":4096,"description":"The message body — the same for everyone."},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to","text"],"additionalProperties":false}}}},"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/messages/location":{"post":{"operationId":"send_location_post","summary":"Send a location pin","tags":["Messages"],"description":"Send a map pin over WhatsApp. Counts as one message, and — like every send — is queued rather than sent when it opens a conversation this number has never had.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"latitude":{"type":"number","minimum":-90,"maximum":90},"longitude":{"type":"number","minimum":-180,"maximum":180},"name":{"description":"Label shown on the pin.","type":"string","maxLength":200},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to","latitude","longitude"],"additionalProperties":false}}}},"responses":{"201":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/messages/contact":{"post":{"operationId":"send_contact_post","summary":"Send a contact card","tags":["Messages"],"description":"Send a contact card (vCard) over WhatsApp, so the recipient can save the number with one tap. Counts as one message, and — like every send — is queued rather than sent when it opens a conversation this number has never had.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"name":{"type":"string","minLength":1,"maxLength":200,"description":"Display name on the card."},"vcard":{"type":"string","minLength":1,"description":"The vCard payload, e.g. `BEGIN:VCARD\\nVERSION:3.0\\n…\\nEND:VCARD`."},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to","name","vcard"],"additionalProperties":false}}}},"responses":{"201":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/otp":{"post":{"operationId":"send_otp_post","summary":"Send a verification code","tags":["OTP"],"description":"Generate a 6-digit code, send it over WhatsApp and return it. Returning the code is deliberate: verify it yourself, or hand it back to check_otp and keep nothing. Codes expire in 5 minutes by default and one number can only be sent a new code once a minute. Counts as one message.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"template":{"description":"Message body. Must contain `{code}`; `{minutes}` expands to the expiry. Default: “{code} is your verification code. It expires in {minutes} minutes.”","type":"string","minLength":1,"maxLength":1000},"expiresInSeconds":{"description":"How long the code stays valid. Default 300 (5 minutes).","type":"integer","minimum":60,"maximum":3600}},"required":["to"],"additionalProperties":false}}}},"responses":{"201":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/otp/check":{"post":{"operationId":"check_otp_post","summary":"Check a verification code","tags":["OTP"],"description":"Verify a code sent with send_otp and consume it, so it can never be used twice. A wrong or expired code answers 400 OTP_INVALID — deliberately an error, not a `verified: false`, so a missing check at the caller can never read as success. Five wrong guesses kill the code. Free.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"code":{"type":"string","minLength":4,"maxLength":10,"description":"The code the user typed."}},"required":["to","code"],"additionalProperties":false}}}},"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/numbers":{"get":{"operationId":"list_numbers_get","summary":"List your WhatsApp numbers","tags":["Numbers"],"description":"Every WhatsApp number on the account with its connection status. Start here when you do not know which number to send from, or to check whether one needs re-pairing. Free.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}},"post":{"operationId":"create_number_post","summary":"Add a WhatsApp number","tags":["Numbers"],"description":"Create a new WhatsApp number slot on the account and return it. This is step one of two: the slot starts `disconnected` and cannot send until pair_number links a real WhatsApp account to it. How many can exist at once is what the plan sells. Free.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"A label you choose, e.g. “Support” — only you ever see it."},"historySync":{"description":"Pull the phone's existing chat history on pairing. Off by default: it copies conversations we otherwise never see.","type":"boolean"},"age":{"description":"How long this WhatsApp line has been in real use — we cannot see it, so we ask. It sets how many NEW conversations the number may start per day: `new` (registered days ago) starts at 5/day and climbs, `established` (in use over a month) starts at the 20/day ceiling. Defaults to `new`, the safe answer. Overstating it only costs you the number.","type":"string","enum":["new","weeks","established"]}},"required":["name"],"additionalProperties":false}}}},"responses":{"201":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/numbers/check/{phone}":{"get":{"operationId":"check_number_get","summary":"Check whether a phone is on WhatsApp","tags":["Numbers"],"description":"Ask WhatsApp whether a phone number has an account, and get its profile picture when it does. Brazilian mobiles are checked with and without the extra 9 and the answer reports the form that actually matched. If WhatsApp cannot be reached the answer assumes yes and sets `verified: false` — an outage must not block sends to real people. Free.","security":[{"bearerAuth":[]}],"parameters":[{"name":"phone","in":"path","required":true,"description":"The phone number to look up, e.g. +5511988887777.","schema":{"type":"string","minLength":6,"description":"The phone number to look up, e.g. +5511988887777."}},{"name":"from","in":"query","required":false,"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","schema":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1}}],"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/numbers/{id}":{"get":{"operationId":"number_status_get","summary":"Get one number's status","tags":["Numbers"],"description":"One number's current state: `connected` (ready to send), `pairing` (waiting for a scan), `disconnected` (session dropped — pair it again) or `banned` (WhatsApp restricted it; wait out the 24h window before reconnecting). Free.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Id of the number, from list_numbers.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Id of the number, from list_numbers."}}],"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}},"patch":{"operationId":"update_number_patch","summary":"Change a number's settings","tags":["Numbers"],"description":"Rename a number, correct the age its owner declared, or change how it sends. The two sending policies are the interesting part:\n\n`pacing` is the queue. While it is on — the default — a message to someone this number has never written to is accepted and held, then sent 15–25 minutes later, one at a time. It is the single most effective thing keeping an unofficial number alive, so turn it off only if you are pacing the sends yourself.\n\n`humanize` is what one send looks like: online, reads what they wrote, types for as long as the reply is long. It costs a few seconds per call and buys a number that does not read as a script.\n\nEverything is optional and merges into what is already there. Free.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Id of the number, from list_numbers.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Id of the number, from list_numbers."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"The label you chose.","type":"string","minLength":1,"maxLength":80},"age":{"description":"Correct how long this WhatsApp line has been in real use — it sets how many NEW conversations the number may start per day. Overstating it only costs you the number.","type":"string","enum":["new","weeks","established"]},"pacing":{"description":"How this number spaces the conversations it STARTS. Replies to people who wrote first are never held.","type":"object","properties":{"enabled":{"description":"Queue first approaches instead of sending them straight away. On by default. Turning it off makes every send immediate — and makes the pacing your problem, which is a real thing to take on: bursts of first approaches are what WhatsApp bans numbers for.","type":"boolean"},"minMinutes":{"description":"Shortest gap between two conversations this number starts, in minutes. Default 15.","type":"integer","minimum":1,"maximum":720},"maxMinutes":{"description":"Longest gap, in minutes. Default 25. The real gap is picked at random between the two, because a queue that fires exactly every twenty minutes is a metronome, and a metronome is a signature.","type":"integer","minimum":1,"maximum":720}},"additionalProperties":false},"humanize":{"description":"Which parts of the human act this number performs on every send. Each one is a pattern WhatsApp can read: a session that is never online, never reads and answers a long message instantly is describing itself as software.","type":"object","properties":{"enabled":{"description":"Send like a person at all. On by default.","type":"boolean"},"typing":{"description":"Show “typing…” for as long as the message would take to type, before sending it.","type":"boolean"},"presence":{"description":"Come online before sending, go offline after.","type":"boolean"},"markRead":{"description":"Put blue ticks on what the recipient last wrote before answering it. Turn this off if read receipts on your own chats are not wanted.","type":"boolean"}},"additionalProperties":false}},"additionalProperties":false}}}},"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}},"delete":{"operationId":"delete_number_delete","summary":"Delete a number","tags":["Numbers"],"description":"Remove a number from the account for good, logging its WhatsApp session out on the way. This frees a slot against the plan's limit. Messages already sent are unaffected, and the usage they cost stays on the bill. Free, and not reversible.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Id of the number to delete, from list_numbers.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Id of the number to delete, from list_numbers."}}],"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/numbers/{id}/pair":{"post":{"operationId":"pair_number_post","summary":"Pair a number with WhatsApp","tags":["Numbers"],"description":"Start (or resume) linking a WhatsApp account to one of your numbers. `qr` returns a QR code to scan from WhatsApp → Linked devices; `code` returns an 8-character linking code to type there instead, and needs the phone number being linked. A QR lives ~90 seconds and a new one can only be minted every few minutes, so call again to poll: `state: \"pairing\"` with no code yet simply means it is still being minted. Free.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Id of the number to pair, from list_numbers.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Id of the number to pair, from list_numbers."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"description":"`qr` (default) or `code` for the type-in linking code.","type":"string","enum":["qr","code"]},"phone":{"description":"Required with `mode: \"code\"` — the phone number being linked.","type":"string","minLength":8}},"additionalProperties":false}}}},"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/numbers/{id}/unpair":{"post":{"operationId":"unpair_number_post","summary":"Disconnect a number from WhatsApp","tags":["Numbers"],"description":"Log the linked WhatsApp account out of this number, keeping the number itself. Use it to link a different phone: a connected number cannot be re-paired until it is unpaired. Free.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Id of the number, from list_numbers.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Id of the number, from list_numbers."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/chats/{phone}/typing":{"post":{"operationId":"typing_post","summary":"Show the typing indicator","tags":["Messages"],"description":"Show (or clear) the “typing…” indicator in a chat. Free — it is not a message. WhatsApp clears it on its own after a few seconds, so call it again for a long reply.","security":[{"bearerAuth":[]}],"parameters":[{"name":"phone","in":"path","required":true,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group).","schema":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"state":{"description":"`composing` shows the indicator, `paused` clears it. Default: composing.","type":"string","enum":["composing","paused"]},"recording":{"description":"Show “recording audio…” instead of “typing…”.","type":"boolean"}},"additionalProperties":false}}}},"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/chats/{phone}/read":{"post":{"operationId":"mark_read_post","summary":"Mark messages as read","tags":["Messages"],"description":"Put blue ticks on messages your number received in a chat. Free. Message ids come from the inbound webhook.","security":[{"bearerAuth":[]}],"parameters":[{"name":"phone","in":"path","required":true,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group).","schema":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"messageIds":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1},"description":"Ids of the received messages to acknowledge."}},"required":["messageIds"],"additionalProperties":false}}}},"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/webhooks":{"get":{"operationId":"get_webhook_get","summary":"Get your webhook endpoint","tags":["Webhooks"],"description":"The endpoint events are being POSTed to, which ones it is subscribed to, and whether it is still healthy. `null` when none is registered. `disabledReason: \"failures\"` means we stopped delivering after a long run of dead attempts — fix the endpoint and set it again (or fire a test) to switch it back on. Free.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}},"put":{"operationId":"set_webhook_put","summary":"Register your webhook endpoint","tags":["Webhooks"],"description":"Point whatsapi.sh at an https URL and start receiving events there. The answer carries the signing secret ONCE, on first registration: keep it — every delivery is signed with it in the `x-whatsapi-signature` header (`t=<unix>,v1=<hmac-sha256 of \"<t>.<body>\">`), and that signature is how you know a request is really from us. Calling this again updates the URL or the subscription and keeps the same secret unless you ask to rotate it. Saving also re-enables an endpoint we had disabled. Free.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Where to POST events, e.g. https://api.yourapp.com/webhooks/whatsapp."},"events":{"minItems":1,"type":"array","items":{"type":"string","enum":["message.created","message.sent","message.failed","message.status","number.connected","number.disconnected","number.logged_out"]},"description":"Which events to receive: message.created, message.sent, message.failed, message.status, number.connected, number.disconnected, number.logged_out. Defaults to all of them."},"rotateSecret":{"description":"Mint a new signing secret and return it — the old one stops working.","type":"boolean"}},"required":["url"],"additionalProperties":false}}}},"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}},"post":{"operationId":"set_webhook_post","summary":"Register your webhook endpoint","tags":["Webhooks"],"description":"Point whatsapi.sh at an https URL and start receiving events there. The answer carries the signing secret ONCE, on first registration: keep it — every delivery is signed with it in the `x-whatsapi-signature` header (`t=<unix>,v1=<hmac-sha256 of \"<t>.<body>\">`), and that signature is how you know a request is really from us. Calling this again updates the URL or the subscription and keeps the same secret unless you ask to rotate it. Saving also re-enables an endpoint we had disabled. Free.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Where to POST events, e.g. https://api.yourapp.com/webhooks/whatsapp."},"events":{"minItems":1,"type":"array","items":{"type":"string","enum":["message.created","message.sent","message.failed","message.status","number.connected","number.disconnected","number.logged_out"]},"description":"Which events to receive: message.created, message.sent, message.failed, message.status, number.connected, number.disconnected, number.logged_out. Defaults to all of them."},"rotateSecret":{"description":"Mint a new signing secret and return it — the old one stops working.","type":"boolean"}},"required":["url"],"additionalProperties":false}}}},"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}},"delete":{"operationId":"delete_webhook_delete","summary":"Remove your webhook endpoint","tags":["Webhooks"],"description":"Stop sending events and forget the endpoint, its secret and its delivery history. Registering again mints a new secret. Free.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/webhooks/test":{"post":{"operationId":"test_webhook_post","summary":"Fire a test event at your endpoint","tags":["Webhooks"],"description":"Send a `ping` event to your registered endpoint right now and wait for the answer, so you can verify the URL and your signature check without waiting for a real message. The result is the actual outcome: `ok: false` carries the HTTP status your server answered, or why we could not reach it. A successful ping also revives an endpoint we had auto-disabled. Free.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/webhooks/deliveries":{"get":{"operationId":"list_webhook_deliveries_get","summary":"Recent webhook deliveries","tags":["Webhooks"],"description":"The last deliveries we attempted, newest first — what we sent, what your endpoint answered, and how many attempts it took. This is the answer to “the event never arrived”. Deliveries are kept for a week. Free.","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"How many to return, newest first. Up to 50 (the default).","schema":{"description":"How many to return, newest first. Up to 50 (the default).","type":"integer","minimum":1,"maximum":50}}],"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/queue":{"get":{"operationId":"list_queued_messages_get","summary":"See what is waiting to be sent","tags":["Queue"],"description":"Every message currently holding a slot in a number's send queue, soonest first. Messages land here when they open a conversation the number has never had. They are accepted and charged straight away, then released 15–25 minutes apart, so a run of first approaches does not read as a broadcast. `scheduledAt` says when each one leaves, and `message.sent` fires on the account's webhook when it does. Free.","security":[{"bearerAuth":[]}],"parameters":[{"name":"numberId","in":"query","required":false,"description":"Only this number's queue. Omit for every number on the account.","schema":{"description":"Only this number's queue. Omit for every number on the account.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"limit","in":"query","required":false,"description":"How many to return, soonest first. Default 200.","schema":{"description":"How many to return, soonest first. Default 200.","type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]},"example":{"data":[{"id":"9c1f0b7e-8b1a-4a2e-9b0a-1f2e3d4c5b6a","numberId":"a1b2c3d4-0000-0000-0000-000000000000","to":"+5511988887777","kind":"text","preview":"Hi Marina — following up on the quote…","scheduledAt":"2026-08-24T19:18:00.000Z","queuedAt":"2026-08-24T19:00:00.000Z"}]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/queue/{id}":{"delete":{"operationId":"cancel_queued_message_delete","summary":"Cancel a message that hasn't been sent yet","tags":["Queue"],"description":"Take a waiting message back out of the queue. Its message quota and the day's new-conversation budget are both handed back, so it is as if it had never been asked for. A message that has already left cannot be cancelled — WhatsApp has it — and the answer says so rather than pretending. The id is the one the send call returned. Free.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Id of the queued message — what the send call answered with.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Id of the queued message — what the send call answered with."}}],"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}},"/usage":{"get":{"operationId":"get_usage_get","summary":"Check quota and usage","tags":["Usage"],"description":"Messages sent this month against the plan quota, today against the daily fair-use cap, the leftover credit balance, and the per-key breakdown. Read this before a bulk send. Free.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The request succeeded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object"}},"required":["data"]}}}},"400":{"description":"VALIDATION_ERROR — the issues, never the values."},"401":{"description":"UNAUTHORIZED / INVALID_TOKEN — the key is missing or wrong."},"429":{"description":"RATE_LIMIT_EXCEEDED / QUOTA_EXCEEDED — Retry-After says when it clears."}}}}},"x-mcp-tools":[{"name":"send_message","title":"Send a WhatsApp message","description":"Send a text message to a phone number over WhatsApp. Counts as one message against the account's monthly quota.\n\n**A message to someone this number has never written to is not sent immediately.** It is accepted, charged and QUEUED: the answer carries `status: \"queued\"` and `scheduledAt`. It leaves 15–25 minutes later, one at a time. That is deliberate. A burst of first approaches is the fastest way to get a WhatsApp number banned, and spacing them out is what separates a number that lasts from one that does not. `message.sent` fires on the account's webhook when it goes out, carrying WhatsApp's id. Use `skipQueue: true` for a message the recipient is waiting for right now, or turn the queue off for the number with update_number.\n\nReplies to people who wrote first are never queued and never spend budget. A first approach also spends one of the number's NEW CONVERSATIONS for the day — the anti-ban ceiling reported as `newConversationsRemaining`. A `sent` answer carries WhatsApp's message id; delivery and read receipts arrive later on the account's webhook, not here.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"text":{"type":"string","minLength":1,"maxLength":4096,"description":"The message body."},"linkPreview":{"description":"Render a preview card for the first link in the text. Default: on.","type":"boolean"},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to","text"],"additionalProperties":false}},{"name":"send_media","title":"Send a photo, video, voice note or file","description":"Send media over WhatsApp from a public URL or base64 data. `type` picks the bubble the recipient sees: `image` and `video` support a caption, `document` is a file attachment named by `fileName`, and `audio` becomes a voice note when the file is ogg/opus — any other audio format is sent as a playable attachment instead, because WhatsApp will not render a voice note that is not opus. Files are capped at 16MB, and `url` beats `base64` for anything sizeable. Counts as one message.\n\nLike every send: media to someone this number has never written to is QUEUED rather than sent, and comes back with `status: \"queued\"` and `scheduledAt`. The file is fetched when the slot comes up, so a `url` has to still be reachable then.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"type":{"type":"string","enum":["image","video","audio","document"],"description":"Which kind of media bubble to send."},"url":{"description":"Public https URL of the file — the better option. We stream the download and forward the bytes, so the size of the file never rides in your request.","type":"string","format":"uri"},"base64":{"description":"The file as base64, bare or as a `data:<mime>;base64,…` URI. Base64 inflates a request by a third, so prefer `url` for anything but small files.","type":"string","minLength":1},"mimeType":{"description":"Overrides the detected content type, e.g. `image/webp`.","type":"string","minLength":3},"caption":{"description":"Text shown under the media. Images and videos only — WhatsApp has no caption field for documents or audio.","type":"string","maxLength":1024},"fileName":{"description":"Documents: the filename WhatsApp shows on the bubble.","type":"string","minLength":1,"maxLength":255},"seconds":{"description":"Voice notes: duration, so the bubble shows the right length.","type":"integer","exclusiveMinimum":0,"maximum":600},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to","type"],"additionalProperties":false}},{"name":"send_batch","title":"Send one text to many recipients","description":"Send the same text to up to 25 recipients in one call — one message, and one message's worth of quota, per recipient. The answer reports each recipient separately: a batch where some land and some do not is the normal case, not an error. If something systemic stops the run (the number drops its session, the quota or rate limit refuses) the remaining recipients come back as `skipped` instead of being attempted. Send them again once that is cleared.\n\n**While the number paces its sends, which is the default, a batch does not send: it queues.** Every recipient comes back as `queued` with its own `scheduledAt`. The messages leave one at a time: minutes apart for anyone this number has never written to, seconds apart for people it already talks to. Twenty-five messages arriving in one second is precisely the pattern WhatsApp restricts numbers for, so the endpoint no longer does that. Watch `message.sent` on the account's webhook for the ids, `GET /v1/queue` for what is still waiting, and turn pacing off for the number if you truly want them at once. Paid plans only.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"to":{"minItems":1,"maxItems":25,"type":"array","items":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"description":"Recipients, up to 25. Each gets its own message; no duplicates."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"text":{"type":"string","minLength":1,"maxLength":4096,"description":"The message body — the same for everyone."},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to","text"],"additionalProperties":false}},{"name":"send_location","title":"Send a location pin","description":"Send a map pin over WhatsApp. Counts as one message, and — like every send — is queued rather than sent when it opens a conversation this number has never had.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"latitude":{"type":"number","minimum":-90,"maximum":90},"longitude":{"type":"number","minimum":-180,"maximum":180},"name":{"description":"Label shown on the pin.","type":"string","maxLength":200},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to","latitude","longitude"],"additionalProperties":false}},{"name":"send_contact","title":"Send a contact card","description":"Send a contact card (vCard) over WhatsApp, so the recipient can save the number with one tap. Counts as one message, and — like every send — is queued rather than sent when it opens a conversation this number has never had.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"name":{"type":"string","minLength":1,"maxLength":200,"description":"Display name on the card."},"vcard":{"type":"string","minLength":1,"description":"The vCard payload, e.g. `BEGIN:VCARD\\nVERSION:3.0\\n…\\nEND:VCARD`."},"skipQueue":{"description":"Send immediately instead of taking a slot in this number's queue. Only for a message the recipient is expecting right now — a reply, a code, a confirmation of something they just did. Outreach sent this way is exactly what gets WhatsApp numbers banned, which is why the queue exists. Default: off.","type":"boolean"},"humanize":{"description":"Whether to send like a person: come online, open what they last wrote, show “typing…” for as long as the message would take to type. On by default, which adds a few seconds to the call — `false` sends it bare and instantly. Configure the parts per number with update_number.","type":"boolean"}},"required":["to","name","vcard"],"additionalProperties":false}},{"name":"send_otp","title":"Send a verification code","description":"Generate a 6-digit code, send it over WhatsApp and return it. Returning the code is deliberate: verify it yourself, or hand it back to check_otp and keep nothing. Codes expire in 5 minutes by default and one number can only be sent a new code once a minute. Counts as one message.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"template":{"description":"Message body. Must contain `{code}`; `{minutes}` expands to the expiry. Default: “{code} is your verification code. It expires in {minutes} minutes.”","type":"string","minLength":1,"maxLength":1000},"expiresInSeconds":{"description":"How long the code stays valid. Default 300 (5 minutes).","type":"integer","minimum":60,"maximum":3600}},"required":["to"],"additionalProperties":false}},{"name":"check_otp","title":"Check a verification code","description":"Verify a code sent with send_otp and consume it, so it can never be used twice. A wrong or expired code answers 400 OTP_INVALID — deliberately an error, not a `verified: false`, so a missing check at the caller can never read as success. Five wrong guesses kill the code. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"code":{"type":"string","minLength":4,"maxLength":10,"description":"The code the user typed."}},"required":["to","code"],"additionalProperties":false}},{"name":"list_numbers","title":"List your WhatsApp numbers","description":"Every WhatsApp number on the account with its connection status. Start here when you do not know which number to send from, or to check whether one needs re-pairing. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{},"additionalProperties":false}},{"name":"create_number","title":"Add a WhatsApp number","description":"Create a new WhatsApp number slot on the account and return it. This is step one of two: the slot starts `disconnected` and cannot send until pair_number links a real WhatsApp account to it. How many can exist at once is what the plan sells. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"A label you choose, e.g. “Support” — only you ever see it."},"historySync":{"description":"Pull the phone's existing chat history on pairing. Off by default: it copies conversations we otherwise never see.","type":"boolean"},"age":{"description":"How long this WhatsApp line has been in real use — we cannot see it, so we ask. It sets how many NEW conversations the number may start per day: `new` (registered days ago) starts at 5/day and climbs, `established` (in use over a month) starts at the 20/day ceiling. Defaults to `new`, the safe answer. Overstating it only costs you the number.","type":"string","enum":["new","weeks","established"]}},"required":["name"],"additionalProperties":false}},{"name":"check_number","title":"Check whether a phone is on WhatsApp","description":"Ask WhatsApp whether a phone number has an account, and get its profile picture when it does. Brazilian mobiles are checked with and without the extra 9 and the answer reports the form that actually matched. If WhatsApp cannot be reached the answer assumes yes and sets `verified: false` — an outage must not block sends to real people. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"phone":{"type":"string","minLength":6,"description":"The phone number to look up, e.g. +5511988887777."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1}},"required":["phone"],"additionalProperties":false}},{"name":"number_status","title":"Get one number's status","description":"One number's current state: `connected` (ready to send), `pairing` (waiting for a scan), `disconnected` (session dropped — pair it again) or `banned` (WhatsApp restricted it; wait out the 24h window before reconnecting). Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"numberId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Id of the number, from list_numbers."}},"required":["numberId"],"additionalProperties":false}},{"name":"update_number","title":"Change a number's settings","description":"Rename a number, correct the age its owner declared, or change how it sends. The two sending policies are the interesting part:\n\n`pacing` is the queue. While it is on — the default — a message to someone this number has never written to is accepted and held, then sent 15–25 minutes later, one at a time. It is the single most effective thing keeping an unofficial number alive, so turn it off only if you are pacing the sends yourself.\n\n`humanize` is what one send looks like: online, reads what they wrote, types for as long as the reply is long. It costs a few seconds per call and buys a number that does not read as a script.\n\nEverything is optional and merges into what is already there. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"numberId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Id of the number, from list_numbers."},"name":{"description":"The label you chose.","type":"string","minLength":1,"maxLength":80},"age":{"description":"Correct how long this WhatsApp line has been in real use — it sets how many NEW conversations the number may start per day. Overstating it only costs you the number.","type":"string","enum":["new","weeks","established"]},"pacing":{"description":"How this number spaces the conversations it STARTS. Replies to people who wrote first are never held.","type":"object","properties":{"enabled":{"description":"Queue first approaches instead of sending them straight away. On by default. Turning it off makes every send immediate — and makes the pacing your problem, which is a real thing to take on: bursts of first approaches are what WhatsApp bans numbers for.","type":"boolean"},"minMinutes":{"description":"Shortest gap between two conversations this number starts, in minutes. Default 15.","type":"integer","minimum":1,"maximum":720},"maxMinutes":{"description":"Longest gap, in minutes. Default 25. The real gap is picked at random between the two, because a queue that fires exactly every twenty minutes is a metronome, and a metronome is a signature.","type":"integer","minimum":1,"maximum":720}},"additionalProperties":false},"humanize":{"description":"Which parts of the human act this number performs on every send. Each one is a pattern WhatsApp can read: a session that is never online, never reads and answers a long message instantly is describing itself as software.","type":"object","properties":{"enabled":{"description":"Send like a person at all. On by default.","type":"boolean"},"typing":{"description":"Show “typing…” for as long as the message would take to type, before sending it.","type":"boolean"},"presence":{"description":"Come online before sending, go offline after.","type":"boolean"},"markRead":{"description":"Put blue ticks on what the recipient last wrote before answering it. Turn this off if read receipts on your own chats are not wanted.","type":"boolean"}},"additionalProperties":false}},"required":["numberId"],"additionalProperties":false}},{"name":"pair_number","title":"Pair a number with WhatsApp","description":"Start (or resume) linking a WhatsApp account to one of your numbers. `qr` returns a QR code to scan from WhatsApp → Linked devices; `code` returns an 8-character linking code to type there instead, and needs the phone number being linked. A QR lives ~90 seconds and a new one can only be minted every few minutes, so call again to poll: `state: \"pairing\"` with no code yet simply means it is still being minted. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"numberId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Id of the number to pair, from list_numbers."},"mode":{"description":"`qr` (default) or `code` for the type-in linking code.","type":"string","enum":["qr","code"]},"phone":{"description":"Required with `mode: \"code\"` — the phone number being linked.","type":"string","minLength":8}},"required":["numberId"],"additionalProperties":false}},{"name":"typing","title":"Show the typing indicator","description":"Show (or clear) the “typing…” indicator in a chat. Free — it is not a message. WhatsApp clears it on its own after a few seconds, so call it again for a long reply.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"state":{"description":"`composing` shows the indicator, `paused` clears it. Default: composing.","type":"string","enum":["composing","paused"]},"recording":{"description":"Show “recording audio…” instead of “typing…”.","type":"boolean"}},"required":["to"],"additionalProperties":false}},{"name":"mark_read","title":"Mark messages as read","description":"Put blue ticks on messages your number received in a chat. Free. Message ids come from the inbound webhook.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"to":{"type":"string","minLength":1,"description":"Recipient — a phone number in international format (+5511988887777) or a WhatsApp address (5511988887777@s.whatsapp.net, an @lid contact, or a @g.us group)."},"from":{"description":"Which of your WhatsApp numbers sends this — its id or its phone number. Optional: with a single connected number it is resolved for you.","type":"string","minLength":1},"messageIds":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1},"description":"Ids of the received messages to acknowledge."}},"required":["to","messageIds"],"additionalProperties":false}},{"name":"get_webhook","title":"Get your webhook endpoint","description":"The endpoint events are being POSTed to, which ones it is subscribed to, and whether it is still healthy. `null` when none is registered. `disabledReason: \"failures\"` means we stopped delivering after a long run of dead attempts — fix the endpoint and set it again (or fire a test) to switch it back on. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{},"additionalProperties":false}},{"name":"set_webhook","title":"Register your webhook endpoint","description":"Point whatsapi.sh at an https URL and start receiving events there. The answer carries the signing secret ONCE, on first registration: keep it — every delivery is signed with it in the `x-whatsapi-signature` header (`t=<unix>,v1=<hmac-sha256 of \"<t>.<body>\">`), and that signature is how you know a request is really from us. Calling this again updates the URL or the subscription and keeps the same secret unless you ask to rotate it. Saving also re-enables an endpoint we had disabled. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"url":{"type":"string","format":"uri","description":"Where to POST events, e.g. https://api.yourapp.com/webhooks/whatsapp."},"events":{"minItems":1,"type":"array","items":{"type":"string","enum":["message.created","message.sent","message.failed","message.status","number.connected","number.disconnected","number.logged_out"]},"description":"Which events to receive: message.created, message.sent, message.failed, message.status, number.connected, number.disconnected, number.logged_out. Defaults to all of them."},"rotateSecret":{"description":"Mint a new signing secret and return it — the old one stops working.","type":"boolean"}},"required":["url"],"additionalProperties":false}},{"name":"test_webhook","title":"Fire a test event at your endpoint","description":"Send a `ping` event to your registered endpoint right now and wait for the answer, so you can verify the URL and your signature check without waiting for a real message. The result is the actual outcome: `ok: false` carries the HTTP status your server answered, or why we could not reach it. A successful ping also revives an endpoint we had auto-disabled. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{},"additionalProperties":false}},{"name":"list_queued_messages","title":"See what is waiting to be sent","description":"Every message currently holding a slot in a number's send queue, soonest first. Messages land here when they open a conversation the number has never had. They are accepted and charged straight away, then released 15–25 minutes apart, so a run of first approaches does not read as a broadcast. `scheduledAt` says when each one leaves, and `message.sent` fires on the account's webhook when it does. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"numberId":{"description":"Only this number's queue. Omit for every number on the account.","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"limit":{"description":"How many to return, soonest first. Default 200.","type":"integer","minimum":1,"maximum":200}},"additionalProperties":false}},{"name":"cancel_queued_message","title":"Cancel a message that hasn't been sent yet","description":"Take a waiting message back out of the queue. Its message quota and the day's new-conversation budget are both handed back, so it is as if it had never been asked for. A message that has already left cannot be cancelled — WhatsApp has it — and the answer says so rather than pretending. The id is the one the send call returned. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"Id of the queued message — what the send call answered with."}},"required":["id"],"additionalProperties":false}},{"name":"get_usage","title":"Check quota and usage","description":"Messages sent this month against the plan quota, today against the daily fair-use cap, the leftover credit balance, and the per-key breakdown. Read this before a bulk send. Free.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{},"additionalProperties":false}}]}