Send a message
Send a message to a recipient via SMS or WhatsApp.
Channel selection:
- If
channelis omitted andmessageTypeistext, defaults to SMS - If
messageTypeis anything other thantext, WhatsApp is used automatically
WhatsApp 24-hour window:
- Free-form messages (non-template) require an open 24h window
- Window opens when the user messages you first
- Use template messages to initiate conversations outside the window
Plan allowances and email billing:
- WhatsApp, Telegram, Instagram and Messenger share an allowance of 2,000 messages per month on Free. It counts messages in both directions: a message a contact sends you consumes one unit exactly as a message you send them does, so a project that has sent 300 and received 1,700 has used the whole allowance. Messages you send from the WhatsApp Business App on your own phone under coexistence are mirrored into your inbox but never counted, and neither are failed sends. Pay as you go: a Free team that has added funds to its balance is not stopped at the allowance. Past it, each started block of 1,000 messages is prepaid from the balance at $4 ($0.004 a message, either direction); only when the balance cannot cover the next block does the rule below apply. Otherwise, over the allowance, sends return 429 with code
a2p_limit_exceededand upgrade details, and inbound messages on those channels are held: kept, but not shown in the inbox and nomessage.inboundwebhook until the team adds funds or moves to a paid plan (or, for a pay-as-you-go team stopped by its spend cap, raises the cap). Held messages are then delivered in arrival order with their originalproviderTimestamp, counted like any other, and an AI agent does not reply to them. Up to 1,000 are held per team, each for 30 days; past either limit they are refused and not delivered later. The month resetting does not release them. The counter resets on the 1st of each month. Paid plans have no message caps - Email is billed from your prepaid balance in 1,000-message blocks: $0.40 per 1,000 transactional emails, $0.80 per 1,000 marketing (broadcast) emails. A block is charged when your monthly count crosses each 1,000 boundary, and at zero balance email sends return 402 with code
insufficient_balance. Free teams start with $2 of credit and additionally cap at 3,000 emails/month (lifted once the team has added funds: past it, email keeps billing from the balance in the same blocks) and 100/day. Email broadcasts (marketing email) need a paid plan: Free cannot send them, seePOST /v1/broadcasts/{broadcastId}/send. Teams on earlier plans keep their original email quotas instead - SMS and voice are billed per message from your balance on every plan
Daily limits:
- An account sends on every channel from its first minute, to any destination. Verification is not a permission to send: identity verification and business verification (KYB) raise the ceilings below. That includes
sms_oneway: it sends without verification, within the SMS ceiling, and every message passes the risk review below. KYB is still required to register a 10DLC brand and campaign, which every US and Canadian (+1) SMS destination needs — a carrier rule, answered separately with403 ten_dlc_required - Daily ceilings apply per channel group and rise with verification. An account that has verified nothing: 25/day across
sms+sms_oneway, 5/day forvoice, 100/day across WhatsApp, Telegram, Instagram and Messenger combined. Past that floor: 200/day for SMS, or 10,000/day once identity or business verification is approved (or a higher limit agreed for your account); 50/day voice and 250/day conversational on Free. Paid plans have no voice or conversational daily ceiling, and neither does a Free team that has added funds on conversational (voice keeps 50/day). Over a ceiling, sends return429with codedaily_limit_exceededanddetails.limit; the count resets at 00:00 UTC - The daily ceiling never reduces the monthly allowance: 100/day on the conversational group still reaches the 2,000 monthly A2P messages Free includes
- Email: a sender with a verified domain sends from day one, within the plan quota (100/day and 3,000/month on Free). Over the daily quota it returns
429with codedaily_limit_exceeded - Every plan, paid ones included, also has a rolling email ceiling by account age: 200 per hour and 1,000 per 24 hours for the first 7 days, 1,000 per hour and 10,000 per 24 hours until day 30, then 5,000 per hour and 50,000 per 24 hours. Past it the request is accepted and the message then fails with
errorCodeEMAIL_RATE_LIMIT_HOURLYorEMAIL_RATE_LIMIT_DAILY, visible onGET /v1/messages/{messageId}and themessage.failedwebhook. It does not reset at 00:00 UTC: it counts the last hour and the last 24 hours - Full reference: https://www.zavu.dev/docs/concepts/sending-limits
Risk review:
Every outbound sms, sms_oneway, email and voice message is read before it is sent — the content, and how this account has been sending. What is checked is the message, not who you are.
- A message can be held for a short review. It stays
queuedwhile it waits: no new status exists for this, andMessageStatusis unchanged. When it is approved it sends normally. - A message that is not approved moves to
failedand firesmessage.failed.errorCodesays which happened:RISK_REJECTED(a reviewer refused it),RISK_REVIEW_EXPIRED(the review window closed first — it is a couple of hours, because a code that arrives late is worse than one that does not arrive), orRISK_BLOCKED(refused outright, without a hold). An SMS that fails this way is not charged; the prepaid amount is returned. - A call is never held.
POST /v1/callsfails a call the review stops rather than placing it hours late. - A message whose content cannot be read — the check is briefly unavailable — is held rather than sent. An account with an approved business verification is unaffected, and so is one that has verified something, already sends real traffic, and has a clean recent record.
- Repeated refusals suspend an account's sending. While it is suspended every send is refused with
403and codesending_suspended,details.dashboardUrlpoints at support, and a message already queued fails witherrorCodeSENDING_SUSPENDED. - A broadcast is read once, on the broadcast itself, rather than per recipient — see
POST /v1/broadcasts/{broadcastId}/send.
Email recipient pre-flight:
Email messages are validated automatically before dispatch. Sends that would be a guaranteed hard bounce are failed instead of sent, protecting your bounce rate: the message transitions to failed (visible via GET /v1/messages/{messageId} and the message.failed webhook) with errorCode set to EMAIL_INVALID_RECIPIENT (malformed address), EMAIL_DOMAIN_NOT_FOUND (recipient domain has no MX or A records), or EMAIL_RECIPIENT_SUPPRESSED (address is on your suppression list after a previous bounce or complaint). Advisory signals (role addresses, disposable domains) do not block sends — check them beforehand with POST /v1/introspect/email.
Authorization
AuthorizationheaderrequiredBearer YOUR_API_KEY. Create a key in the dashboard.
Headers
Zavu-SenderstringOptional sender profile ID. If omitted, the project's default sender will be used.
Body
application/json
tostringrequiredRecipient phone number in E.164 format, email address, WhatsApp business-scoped user ID (BSUID, e.g.
US.13491208655302741918), or numeric chat ID (for Telegram/Instagram/Messenger). A BSUID is routed to WhatsApp and sent via therecipientfield; use it to message a contact who adopted a username and whose phone number is hidden.channelstringDelivery channel. Use 'auto' for intelligent routing. If omitted, channel is auto-selected based on sender capabilities and recipient type. For email recipients, defaults to 'email'.
One ofautosmssms_onewaywhatsapptelegramemailinstagrammessengervoiceDefaultautomessageTypestringType of message. Defaults to 'text'.
One oftextimagevideoaudiodocumentstickerlocationcontactbuttonslistcta_urlrequest_contact_infolocation_requestreactiontemplateDefaulttexttextstringText body for text messages or caption for media messages.
contentobjectAdditional content for non-text message types.
32 properties
mediaUrlstringURL of the media file (for image, video, audio, document, sticker).
mediaIdstringWhatsApp media ID if already uploaded.
mimeTypestringMIME type of the media.
filenamestringFilename for documents.
latitudenumberLatitude for location messages.
longitudenumberLongitude for location messages.
locationNamestringName of the location.
locationAddressstringAddress of the location.
contactsobject[]Contact cards for contact messages.
2 properties
namestringphonesstring[]
buttonsobject[]Interactive buttons (max 3).
Limitsmax items 32 properties
idstringrequiredLimitsmax length 256titlestringrequiredLimitsmax length 20
listButtonstringButton text for list messages.
Limitsmax length 20sectionsobject[]Sections for list messages.
2 properties
titlestringrequiredrowsobject[]requiredLimitsmax items 103 properties
idstringrequiredtitlestringrequiredLimitsmax length 24descriptionstringLimitsmax length 72
ctaDisplayTextstringButton label for cta_url messages.
Limitsmax length 20ctaUrlstring<uri>Destination URL opened in the device's default browser when the button is tapped. Used with messageType=cta_url. WhatsApp requires HTTPS in production.
ctaHeaderTypestringOptional header type for cta_url messages.
One oftextimagevideodocumentctaHeaderTextstringHeader text when ctaHeaderType is 'text'.
Limitsmax length 60ctaHeaderMediaUrlstring<uri>Public HTTPS URL of the header media when ctaHeaderType is 'image', 'video', or 'document'. WhatsApp fetches this URL — it must be publicly reachable and return the declared content type.
footerTextstringOptional footer text for cta_url messages.
Limitsmax length 60emojistringEmoji for reaction messages.
reactToMessageIdstringMessage ID to react to.
replyToMessageIdstringZavu message ID of the quoted message this message replies to.
When sending: set it to make the message a quoted reply to an earlier message in the same conversation, such as the
data.messageIdof amessage.inboundwebhook. Supported on WhatsApp (every message type exceptreaction) and Telegram. The quoted message must belong to this project (otherwise404), must have gone out on the same channel as this send, must belong to the conversationtoaddresses, must have been sent or received by the sender this message goes out from (pass it inZavu-Sender), and must have aproviderMessageId. A failed message and a Telegram button tap cannot be quoted. Any other case returns400instead of sending the message unquoted. The stored message then carries the rest of thereplyTo*fields.When receiving: present on inbound messages that quote an earlier message. Omitted when the quoted message is not found in Zavu (e.g. an old or unknown message) — use replyToProviderMessageId in that case.
replyToProviderMessageIdstringProvider message ID (WhatsApp WAMID, or Telegram message ID) of the quoted message. Present whenever an inbound WhatsApp message is a reply, even if the quoted message is not stored in Zavu, and on outbound quoted replies. Read-only: to quote when sending, set replyToMessageId.
replyToFromstringSender of the quoted message (phone number in E.164 format, or the Telegram chat ID). Read-only.
replyToTextstringTruncated snippet (up to 200 characters) of the quoted message's text, for display. Empty when the quoted message has no text (e.g. media). Read-only.
replyToMessageTypestringType of the quoted message (text, image, video, etc.). Read-only.
templateIdstringTemplate ID for template messages.
templateVariablesobjectVariables for body placeholders. Key them to match the template body: by position (
1,2, ...) for positional templates, or by name (e.g.customer_name) for named templates. Zavu detects the template's format and sends the correct payload to Meta. Named keys also resolve a named text-header variable. Do not mix positional and named keys in the same request. On WhatsApp, a value may not contain line breaks, tabs or more than 4 consecutive spaces: Meta refuses those at delivery, so the send is rejected with400 invalid_requestinstead (details.fieldnames the value).1 properties
{key}string
templateButtonVariablesobjectVariables for dynamic button placeholders (URL buttons and OTP buttons). Keys are the button index (0, 1, 2) in the template's
buttonsarray — not the placeholder name. Values substitute the{{1}}placeholder inside that button's URL.WhatsApp constraints: - URL buttons only accept
{{1}}— positional, numeric, no whitespace, no name. Named placeholders like{{token}}are stored as literal URL text by Meta and cannot be substituted. - At most one placeholder per URL button. - A template may have at most three buttons. - Static URL buttons (no placeholder) andquick_replybuttons are not included here.1 properties
{key}string
templateHeaderVariablesobjectValue for a text-header variable, keyed by
1(WhatsApp text headers allow at most one variable). Optional override. If omitted, Zavu resolves the header fromtemplateVariablesusing the header placeholder's name (e.g.novios). Static text headers need no value. The same WhatsApp rule astemplateVariablesapplies: no line breaks, tabs or more than 4 consecutive spaces.1 properties
{key}string
templateHeaderMediaUrlstring<uri>HTTPS URL of the media for this send, for a WhatsApp template whose header is
image,videoordocument. It replaces the file stored on the template for this message only; the template is not changed. WhatsApp fetches the URL when the message is sent, so it must be publicly reachable at that moment (a short-lived signed link works if it outlives the send). Without it, the template's stored header file is sent.A template whose header is
text, or that has no header, is refused with400 invalid_requestanddetails.fieldset tocontent.templateHeaderMediaUrl, before anything is sent or charged. A URL that is not HTTPS is refused with400 invalid_requestand adetails.errorsentry whosepathis["content", "templateHeaderMediaUrl"]. Ignored on channels other than WhatsApp.Limitsmax length 2048templateHeaderFilenamestringFile name WhatsApp shows the recipient for a
documentheader, e.g.invoice-1042.pdf. Works withtemplateHeaderMediaUrl, or on its own to name the template's stored file. Only valid when the template's header isdocument: any other header is refused with400 invalid_requestanddetails.fieldset tocontent.templateHeaderFilename, before anything is sent or charged. Ignored on channels other than WhatsApp.Limitsmin length 1 · max length 240referralobjectClick-to-WhatsApp (CTWA) ad attribution: where an inbound conversation came from.
WhatsApp only. Present on the first inbound message of a conversation opened from a Meta ad or post, and on no message after it — so store it when it arrives rather than expecting it again. Organic conversations never carry it.
Field names are camelCased to match the rest of this API; Meta sends them as snake_case (
ctwa_clid,source_id, ...). Fields that do not apply are omitted: apostsource has no click id, and an image ad has novideoUrl.10 properties
sourceTypestringWhere the click came from.
One ofadpostsourceIdstringIdentifier of the ad or post that produced the click.
sourceUrlstring<uri>Meta permalink to the ad or post.
headlinestringHeadline of the ad or post.
bodystringBody copy of the ad or post.
mediaTypestringType of media on the ad, when it had any.
One ofimagevideoimageUrlstring<uri>Image of the ad. Present when
mediaTypeisimage.videoUrlstring<uri>Video of the ad. Present when
mediaTypeisvideo.thumbnailUrlstring<uri>Thumbnail of the ad media.
ctwaClidstringClick-to-WhatsApp click identifier. This is the value Meta's Conversions API needs to credit a conversion back to the ad that produced the conversation. Present on
adsources; apostsource has none.
subjectstringEmail subject line. Required when channel is 'email' or recipient is an email address.
Limitsmax length 998htmlBodystringHTML body for email messages. If provided, email will be sent as multipart with both text and HTML.
replyTostring<email>Reply-To email address for email messages.
idempotencyKeystringOptional idempotency key to avoid duplicate sends.
metadataobjectArbitrary metadata to associate with the message.
1 properties
{key}string
fallbackEnabledbooleanWhether a failed WhatsApp text or template message is retried over SMS, when the sender also has SMS. When omitted, it is on if
channelis omitted orauto, and off if a channel is set explicitly: a send withchannel: "whatsapp"does not fall back unless this istrue. Media, buttons, lists and other WhatsApp-only types never fall back.voiceLanguagestringLanguage code for voice text-to-speech (e.g., 'en-US', 'es-ES', 'pt-BR'). If omitted, language is auto-detected from recipient's country code.
attachmentsobject[]Email attachments. Only supported when channel is 'email'. Maximum 40MB total size.
Limitsmax items 505 properties
filenamestringrequiredName of the attached file.
Limitsmax length 255contentstringContent of the attached file as a Base64-encoded string.
pathstring<uri>URL where the attachment file is hosted. The server will fetch the file.
content_typestringMIME type of the attachment. If not set, will be derived from the filename.
content_idstringContent ID for inline images. Reference in HTML as
<img src="cid:your_content_id">.Limitsmax length 100
Responses
202Message accepted for delivery.
messageobjectrequired20 properties
idstringrequiredtostringrequiredfromstringsenderIdstringchannelstringrequiredDelivery channel. Use 'auto' for intelligent routing.
One ofautosmssms_onewaywhatsapptelegramemailinstagrammessengervoicedirectionstringrequiredWho sent the message. Needed to render a thread:
statuscannot tell the two apart, because an inbound message is also stored asdelivered.One ofinboundoutboundmessageTypestringrequiredType of message. Non-text types are supported by WhatsApp and Telegram (varies by type).
location_requestasks the recipient to share their location and is WhatsApp-only. It takes nocontentobject — the prompt goes intext(max 1024 characters) and the button label is fixed by WhatsApp. The recipient's answer arrives as an inboundlocationmessage whosecontent.replyToMessageIdis the ID of the request.request_contact_infoasks the recipient to share their phone number and is WhatsApp-only. Likelocation_requestit takes nocontentobject — the prompt goes intext(max 1024 characters) and WhatsApp renders a fixed Share Contact Info button. The answer arrives as an inboundcontactmessage. Use it to recover the phone number of a contact who adopted a WhatsApp username and is only known by their business-scoped user ID (BSUID); when they share it, Zavu automatically links the phone number to that contact.One oftextimagevideoaudiodocumentstickerlocationcontactbuttonslistcta_urlrequest_contact_infolocation_requestreactiontemplatestatusstringrequiredOne ofqueuedsendingsentdeliveredreadfailedreceivedpending_url_verificationtextstringText content or caption.
contentobjectContent for non-text message types (WhatsApp and Telegram).
32 properties
mediaUrlstringURL of the media file (for image, video, audio, document, sticker).
mediaIdstringWhatsApp media ID if already uploaded.
mimeTypestringMIME type of the media.
filenamestringFilename for documents.
latitudenumberLatitude for location messages.
longitudenumberLongitude for location messages.
locationNamestringName of the location.
locationAddressstringAddress of the location.
contactsobject[]Contact cards for contact messages.
2 properties
namestringphonesstring[]
buttonsobject[]Interactive buttons (max 3).
Limitsmax items 32 properties
idstringrequiredLimitsmax length 256titlestringrequiredLimitsmax length 20
listButtonstringButton text for list messages.
Limitsmax length 20sectionsobject[]Sections for list messages.
2 properties
titlestringrequiredrowsobject[]requiredLimitsmax items 103 properties
idstringrequiredtitlestringrequiredLimitsmax length 24descriptionstringLimitsmax length 72
ctaDisplayTextstringButton label for cta_url messages.
Limitsmax length 20ctaUrlstring<uri>Destination URL opened in the device's default browser when the button is tapped. Used with messageType=cta_url. WhatsApp requires HTTPS in production.
ctaHeaderTypestringOptional header type for cta_url messages.
One oftextimagevideodocumentctaHeaderTextstringHeader text when ctaHeaderType is 'text'.
Limitsmax length 60ctaHeaderMediaUrlstring<uri>Public HTTPS URL of the header media when ctaHeaderType is 'image', 'video', or 'document'. WhatsApp fetches this URL — it must be publicly reachable and return the declared content type.
footerTextstringOptional footer text for cta_url messages.
Limitsmax length 60emojistringEmoji for reaction messages.
reactToMessageIdstringMessage ID to react to.
replyToMessageIdstringZavu message ID of the quoted message this message replies to.
When sending: set it to make the message a quoted reply to an earlier message in the same conversation, such as the
data.messageIdof amessage.inboundwebhook. Supported on WhatsApp (every message type exceptreaction) and Telegram. The quoted message must belong to this project (otherwise404), must have gone out on the same channel as this send, must belong to the conversationtoaddresses, must have been sent or received by the sender this message goes out from (pass it inZavu-Sender), and must have aproviderMessageId. A failed message and a Telegram button tap cannot be quoted. Any other case returns400instead of sending the message unquoted. The stored message then carries the rest of thereplyTo*fields.When receiving: present on inbound messages that quote an earlier message. Omitted when the quoted message is not found in Zavu (e.g. an old or unknown message) — use replyToProviderMessageId in that case.
replyToProviderMessageIdstringProvider message ID (WhatsApp WAMID, or Telegram message ID) of the quoted message. Present whenever an inbound WhatsApp message is a reply, even if the quoted message is not stored in Zavu, and on outbound quoted replies. Read-only: to quote when sending, set replyToMessageId.
replyToFromstringSender of the quoted message (phone number in E.164 format, or the Telegram chat ID). Read-only.
replyToTextstringTruncated snippet (up to 200 characters) of the quoted message's text, for display. Empty when the quoted message has no text (e.g. media). Read-only.
replyToMessageTypestringType of the quoted message (text, image, video, etc.). Read-only.
templateIdstringTemplate ID for template messages.
templateVariablesobjectVariables for body placeholders. Key them to match the template body: by position (
1,2, ...) for positional templates, or by name (e.g.customer_name) for named templates. Zavu detects the template's format and sends the correct payload to Meta. Named keys also resolve a named text-header variable. Do not mix positional and named keys in the same request. On WhatsApp, a value may not contain line breaks, tabs or more than 4 consecutive spaces: Meta refuses those at delivery, so the send is rejected with400 invalid_requestinstead (details.fieldnames the value).1 properties
{key}string
templateButtonVariablesobjectVariables for dynamic button placeholders (URL buttons and OTP buttons). Keys are the button index (0, 1, 2) in the template's
buttonsarray — not the placeholder name. Values substitute the{{1}}placeholder inside that button's URL.WhatsApp constraints: - URL buttons only accept
{{1}}— positional, numeric, no whitespace, no name. Named placeholders like{{token}}are stored as literal URL text by Meta and cannot be substituted. - At most one placeholder per URL button. - A template may have at most three buttons. - Static URL buttons (no placeholder) andquick_replybuttons are not included here.1 properties
{key}string
templateHeaderVariablesobjectValue for a text-header variable, keyed by
1(WhatsApp text headers allow at most one variable). Optional override. If omitted, Zavu resolves the header fromtemplateVariablesusing the header placeholder's name (e.g.novios). Static text headers need no value. The same WhatsApp rule astemplateVariablesapplies: no line breaks, tabs or more than 4 consecutive spaces.1 properties
{key}string
templateHeaderMediaUrlstring<uri>HTTPS URL of the media for this send, for a WhatsApp template whose header is
image,videoordocument. It replaces the file stored on the template for this message only; the template is not changed. WhatsApp fetches the URL when the message is sent, so it must be publicly reachable at that moment (a short-lived signed link works if it outlives the send). Without it, the template's stored header file is sent.A template whose header is
text, or that has no header, is refused with400 invalid_requestanddetails.fieldset tocontent.templateHeaderMediaUrl, before anything is sent or charged. A URL that is not HTTPS is refused with400 invalid_requestand adetails.errorsentry whosepathis["content", "templateHeaderMediaUrl"]. Ignored on channels other than WhatsApp.Limitsmax length 2048templateHeaderFilenamestringFile name WhatsApp shows the recipient for a
documentheader, e.g.invoice-1042.pdf. Works withtemplateHeaderMediaUrl, or on its own to name the template's stored file. Only valid when the template's header isdocument: any other header is refused with400 invalid_requestanddetails.fieldset tocontent.templateHeaderFilename, before anything is sent or charged. Ignored on channels other than WhatsApp.Limitsmin length 1 · max length 240referralobjectClick-to-WhatsApp (CTWA) ad attribution: where an inbound conversation came from.
WhatsApp only. Present on the first inbound message of a conversation opened from a Meta ad or post, and on no message after it — so store it when it arrives rather than expecting it again. Organic conversations never carry it.
Field names are camelCased to match the rest of this API; Meta sends them as snake_case (
ctwa_clid,source_id, ...). Fields that do not apply are omitted: apostsource has no click id, and an image ad has novideoUrl.10 properties
sourceTypestringWhere the click came from.
One ofadpostsourceIdstringIdentifier of the ad or post that produced the click.
sourceUrlstring<uri>Meta permalink to the ad or post.
headlinestringHeadline of the ad or post.
bodystringBody copy of the ad or post.
mediaTypestringType of media on the ad, when it had any.
One ofimagevideoimageUrlstring<uri>Image of the ad. Present when
mediaTypeisimage.videoUrlstring<uri>Video of the ad. Present when
mediaTypeisvideo.thumbnailUrlstring<uri>Thumbnail of the ad media.
ctwaClidstringClick-to-WhatsApp click identifier. This is the value Meta's Conversions API needs to credit a conversion back to the ad that produced the conversation. Present on
adsources; apostsource has none.
conversationIdstringID of the conversation (inbox thread) this message belongs to. Use it to build a direct dashboard link:
https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}. Omitted only on legacy messages created before conversation threading.providerMessageIdstringMessage ID from the delivery provider.
errorCodestringerrorMessagestringcostnumberZavu platform charge in USD for this message. Messaging is billed against your plan's monthly limits plus usage-based overage.
costProvidernumberCarrier and delivery cost in USD.
costTotalnumberTotal cost in USD (platform charge + delivery cost).
metadataobject1 properties
{key}string
createdAtstring<date-time>requiredupdatedAtstring<date-time>
400Invalid request, WhatsApp 24h window not open, a WhatsApp template parameter Meta would refuse (line break, tab or more than 4 consecutive spaces in `content.templateVariables` or `content.templateHeaderVariables`), or a quoted reply (`content.replyToMessageId`) that cannot be delivered as one: wrong channel, a reaction, a button tap, a failed message, a message from another conversation or sender, or one with no provider message ID yet.
codestringrequiredmessagestringrequireddetailsobject
401Unauthorized.
codestringrequiredmessagestringrequireddetailsobject
402Insufficient balance (`insufficient_balance`), or the team's monthly spend cap is reached (`spend_cap_reached`). Email is billed from your prepaid balance in 1,000-message blocks; SMS and voice are billed per message. When the balance cannot cover the send, the message is rejected until you add funds. The spend cap is set by the team in the dashboard's Billing: it limits what usage may take from the balance in a calendar month, never phone number or other renewals. Reached, SMS, voice, email and AI replies answer `402 spend_cap_reached` with `details.capCents`, `details.spentCents` and `details.dashboardUrl` until the 1st or until the cap is raised; a message that fails on it carries `errorCode` `SPEND_CAP_REACHED`. App messages (WhatsApp, Telegram, Instagram, Messenger) past the allowance on pay as you go answer `429 a2p_limit_exceeded` instead, with `details.payAsYouGo.reason` `spend_cap_reached`.
codestringrequiredmessagestringrequireddetailsobject
403Message blocked. `sending_suspended`: this account's sending is suspended after Zavu's automated review refused several of its messages, and `details.dashboardUrl` points at support. `url_not_verified` / `url_shortener_blocked`: the message contains URLs that are not allowed.
codestringrequiredmessagestringrequireddetailsobject
404Template, sender, or quoted message (`content.replyToMessageId`) not found.
codestringrequiredmessagestringrequireddetailsobject
409Two cases, told apart by the body.
Two cases, told apart by the body.
channel_not_connected(anError): the sender has no account for the channel asked for, or none at all forauto.details.channelnames it. Connect it, then activate it withPOST /v1/senders/{senderId}/channels/{channel}/activate. Same code and status as activation uses.- An idempotent replay (a
MessageResponse): theidempotencyKeywas already used in this project. Nothing is sent again: the body replays the message the first request created, in its current state, so a retry can tell the first attempt landed and read itsidandstatus. The replay is answered before any rate, daily or balance limit, since the original already consumed them.
422`sms_compliance_blocked`: an SMS or one-way SMS was refused by the content check, or the check could not run (then the message says so, nothing was charged, and a retry in a few minutes goes through). `details` carries `riskScore`, `messageType`, `complianceFailures`, `requiredFixes` and, when there is one, `suggestedCompliantVersion`. One-time codes (OTP) always pass.
codestringrequiredmessagestringrequireddetailsobject
429A sending limit was reached. `daily_limit_exceeded` is the channel group's daily ceiling described above: `details.limit` is the ceiling that applied, and the count resets at 00:00 UTC. Verifying your identity or adding a payment method raises it; for email, upgrading the plan does. Two other limits also answer 429 on this endpoint, both wrapped in an `error` object rather than at the top level: `a2p_limit_exceeded`, the Free plan's monthly allowance on WhatsApp, Telegram, Instagram and Messenger (for a team that has added funds, the balance or its spend cap cannot cover the next $4 block of 1,000; `details.payAsYouGo` then carries `blockSize`, `blockPriceCents`, `dashboardUrl` and `reason`, `insufficient_balance` or `spend_cap_reached`). When incoming messages are on hold, `details.heldInbound` carries `count`, `max` (1,000) and `nextExpiresAt` (ISO 8601, when the oldest one expires), with no content and no sender. An integration that only replies to `message.inbound` never sends while messages are held, so it will not see this: it reads the same figures from `GET /v1/balance` (`heldInbound`, `appMessages.paused`), and the team is emailed the first time messages are held in a month, and `rate_limit_exceeded`, one SMS per recipient per minute.
codestringmessagestringdetailsobjecterrorobject3 properties
codestringrequiredmessagestringrequireddetailsobject
500Server error.
codestringrequiredmessagestringrequireddetailsobject