Skip to content

Send a message

View .md
POST

Send a message to a recipient via SMS or WhatsApp.

Channel selection:

  • If channel is omitted and messageType is text, defaults to SMS
  • If messageType is anything other than text, 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_exceeded and upgrade details, and inbound messages on those channels are held: kept, but not shown in the inbox and no message.inbound webhook 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 original providerTimestamp, 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, see POST /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 with 403 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 for voice, 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 return 429 with code daily_limit_exceeded and details.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 429 with code daily_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 errorCode EMAIL_RATE_LIMIT_HOURLY or EMAIL_RATE_LIMIT_DAILY, visible on GET /v1/messages/{messageId} and the message.failed webhook. 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 queued while it waits: no new status exists for this, and MessageStatus is unchanged. When it is approved it sends normally.
  • A message that is not approved moves to failed and fires message.failed. errorCode says 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), or RISK_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/calls fails 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 403 and code sending_suspended, details.dashboardUrl points at support, and a message already queued fails with errorCode SENDING_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

  • Authorizationheaderrequired

    Bearer YOUR_API_KEY. Create a key in the dashboard.

Headers

  • Zavu-Senderstring

    Optional sender profile ID. If omitted, the project's default sender will be used.

Body

application/json

  • tostringrequired

    Recipient 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 the recipient field; use it to message a contact who adopted a username and whose phone number is hidden.

  • channelstring

    Delivery 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_onewaywhatsapptelegramemailinstagrammessengervoice
    Defaultauto
  • messageTypestring

    Type of message. Defaults to 'text'.

    One oftextimagevideoaudiodocumentstickerlocationcontactbuttonslistcta_urlrequest_contact_infolocation_requestreactiontemplate
    Defaulttext
  • textstring

    Text body for text messages or caption for media messages.

  • contentobject

    Additional content for non-text message types.

    32 properties
    • mediaUrlstring

      URL of the media file (for image, video, audio, document, sticker).

    • mediaIdstring

      WhatsApp media ID if already uploaded.

    • mimeTypestring

      MIME type of the media.

    • filenamestring

      Filename for documents.

    • latitudenumber

      Latitude for location messages.

    • longitudenumber

      Longitude for location messages.

    • locationNamestring

      Name of the location.

    • locationAddressstring

      Address of the location.

    • contactsobject[]

      Contact cards for contact messages.

      2 properties
      • namestring
      • phonesstring[]
    • buttonsobject[]

      Interactive buttons (max 3).

      Limitsmax items 3
      2 properties
      • idstringrequired
        Limitsmax length 256
      • titlestringrequired
        Limitsmax length 20
    • listButtonstring

      Button text for list messages.

      Limitsmax length 20
    • sectionsobject[]

      Sections for list messages.

      2 properties
      • titlestringrequired
      • rowsobject[]required
        Limitsmax items 10
        3 properties
        • idstringrequired
        • titlestringrequired
          Limitsmax length 24
        • descriptionstring
          Limitsmax length 72
    • ctaDisplayTextstring

      Button label for cta_url messages.

      Limitsmax length 20
    • ctaUrlstring<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.

    • ctaHeaderTypestring

      Optional header type for cta_url messages.

      One oftextimagevideodocument
    • ctaHeaderTextstring

      Header text when ctaHeaderType is 'text'.

      Limitsmax length 60
    • ctaHeaderMediaUrlstring<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.

    • footerTextstring

      Optional footer text for cta_url messages.

      Limitsmax length 60
    • emojistring

      Emoji for reaction messages.

    • reactToMessageIdstring

      Message ID to react to.

    • replyToMessageIdstring

      Zavu 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.messageId of a message.inbound webhook. Supported on WhatsApp (every message type except reaction) and Telegram. The quoted message must belong to this project (otherwise 404), must have gone out on the same channel as this send, must belong to the conversation to addresses, must have been sent or received by the sender this message goes out from (pass it in Zavu-Sender), and must have a providerMessageId. A failed message and a Telegram button tap cannot be quoted. Any other case returns 400 instead of sending the message unquoted. The stored message then carries the rest of the replyTo* 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.

    • replyToProviderMessageIdstring

      Provider 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.

    • replyToFromstring

      Sender of the quoted message (phone number in E.164 format, or the Telegram chat ID). Read-only.

    • replyToTextstring

      Truncated 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.

    • replyToMessageTypestring

      Type of the quoted message (text, image, video, etc.). Read-only.

    • templateIdstring

      Template ID for template messages.

    • templateVariablesobject

      Variables 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 with 400 invalid_request instead (details.field names the value).

      1 properties
      • {key}string
    • templateButtonVariablesobject

      Variables for dynamic button placeholders (URL buttons and OTP buttons). Keys are the button index (0, 1, 2) in the template's buttons array — 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) and quick_reply buttons are not included here.

      1 properties
      • {key}string
    • templateHeaderVariablesobject

      Value for a text-header variable, keyed by 1 (WhatsApp text headers allow at most one variable). Optional override. If omitted, Zavu resolves the header from templateVariables using the header placeholder's name (e.g. novios). Static text headers need no value. The same WhatsApp rule as templateVariables applies: 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, video or document. 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 with 400 invalid_request and details.field set to content.templateHeaderMediaUrl, before anything is sent or charged. A URL that is not HTTPS is refused with 400 invalid_request and a details.errors entry whose path is ["content", "templateHeaderMediaUrl"]. Ignored on channels other than WhatsApp.

      Limitsmax length 2048
    • templateHeaderFilenamestring

      File name WhatsApp shows the recipient for a document header, e.g. invoice-1042.pdf. Works with templateHeaderMediaUrl, or on its own to name the template's stored file. Only valid when the template's header is document: any other header is refused with 400 invalid_request and details.field set to content.templateHeaderFilename, before anything is sent or charged. Ignored on channels other than WhatsApp.

      Limitsmin length 1 · max length 240
    • referralobject

      Click-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: a post source has no click id, and an image ad has no videoUrl.

      10 properties
      • sourceTypestring

        Where the click came from.

        One ofadpost
      • sourceIdstring

        Identifier of the ad or post that produced the click.

      • sourceUrlstring<uri>

        Meta permalink to the ad or post.

      • headlinestring

        Headline of the ad or post.

      • bodystring

        Body copy of the ad or post.

      • mediaTypestring

        Type of media on the ad, when it had any.

        One ofimagevideo
      • imageUrlstring<uri>

        Image of the ad. Present when mediaType is image.

      • videoUrlstring<uri>

        Video of the ad. Present when mediaType is video.

      • thumbnailUrlstring<uri>

        Thumbnail of the ad media.

      • ctwaClidstring

        Click-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 ad sources; a post source has none.

  • subjectstring

    Email subject line. Required when channel is 'email' or recipient is an email address.

    Limitsmax length 998
  • htmlBodystring

    HTML 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.

  • idempotencyKeystring

    Optional idempotency key to avoid duplicate sends.

  • metadataobject

    Arbitrary metadata to associate with the message.

    1 properties
    • {key}string
  • fallbackEnabledboolean

    Whether a failed WhatsApp text or template message is retried over SMS, when the sender also has SMS. When omitted, it is on if channel is omitted or auto, and off if a channel is set explicitly: a send with channel: "whatsapp" does not fall back unless this is true. Media, buttons, lists and other WhatsApp-only types never fall back.

  • voiceLanguagestring

    Language 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 50
    5 properties
    • filenamestringrequired

      Name of the attached file.

      Limitsmax length 255
    • contentstring

      Content 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_typestring

      MIME type of the attachment. If not set, will be derived from the filename.

    • content_idstring

      Content ID for inline images. Reference in HTML as <img src="cid:your_content_id">.

      Limitsmax length 100

Responses

202Message accepted for delivery.
  • messageobjectrequired
    20 properties
    • idstringrequired
    • tostringrequired
    • fromstring
    • senderIdstring
    • channelstringrequired

      Delivery channel. Use 'auto' for intelligent routing.

      One ofautosmssms_onewaywhatsapptelegramemailinstagrammessengervoice
    • directionstringrequired

      Who sent the message. Needed to render a thread: status cannot tell the two apart, because an inbound message is also stored as delivered.

      One ofinboundoutbound
    • messageTypestringrequired

      Type of message. Non-text types are supported by WhatsApp and Telegram (varies by type).

      location_request asks the recipient to share their location and is WhatsApp-only. It takes no content object — the prompt goes in text (max 1024 characters) and the button label is fixed by WhatsApp. The recipient's answer arrives as an inbound location message whose content.replyToMessageId is the ID of the request.

      request_contact_info asks the recipient to share their phone number and is WhatsApp-only. Like location_request it takes no content object — the prompt goes in text (max 1024 characters) and WhatsApp renders a fixed Share Contact Info button. The answer arrives as an inbound contact message. 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_requestreactiontemplate
    • statusstringrequired
      One ofqueuedsendingsentdeliveredreadfailedreceivedpending_url_verification
    • textstring

      Text content or caption.

    • contentobject

      Content for non-text message types (WhatsApp and Telegram).

      32 properties
      • mediaUrlstring

        URL of the media file (for image, video, audio, document, sticker).

      • mediaIdstring

        WhatsApp media ID if already uploaded.

      • mimeTypestring

        MIME type of the media.

      • filenamestring

        Filename for documents.

      • latitudenumber

        Latitude for location messages.

      • longitudenumber

        Longitude for location messages.

      • locationNamestring

        Name of the location.

      • locationAddressstring

        Address of the location.

      • contactsobject[]

        Contact cards for contact messages.

        2 properties
        • namestring
        • phonesstring[]
      • buttonsobject[]

        Interactive buttons (max 3).

        Limitsmax items 3
        2 properties
        • idstringrequired
          Limitsmax length 256
        • titlestringrequired
          Limitsmax length 20
      • listButtonstring

        Button text for list messages.

        Limitsmax length 20
      • sectionsobject[]

        Sections for list messages.

        2 properties
        • titlestringrequired
        • rowsobject[]required
          Limitsmax items 10
          3 properties
          • idstringrequired
          • titlestringrequired
            Limitsmax length 24
          • descriptionstring
            Limitsmax length 72
      • ctaDisplayTextstring

        Button label for cta_url messages.

        Limitsmax length 20
      • ctaUrlstring<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.

      • ctaHeaderTypestring

        Optional header type for cta_url messages.

        One oftextimagevideodocument
      • ctaHeaderTextstring

        Header text when ctaHeaderType is 'text'.

        Limitsmax length 60
      • ctaHeaderMediaUrlstring<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.

      • footerTextstring

        Optional footer text for cta_url messages.

        Limitsmax length 60
      • emojistring

        Emoji for reaction messages.

      • reactToMessageIdstring

        Message ID to react to.

      • replyToMessageIdstring

        Zavu 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.messageId of a message.inbound webhook. Supported on WhatsApp (every message type except reaction) and Telegram. The quoted message must belong to this project (otherwise 404), must have gone out on the same channel as this send, must belong to the conversation to addresses, must have been sent or received by the sender this message goes out from (pass it in Zavu-Sender), and must have a providerMessageId. A failed message and a Telegram button tap cannot be quoted. Any other case returns 400 instead of sending the message unquoted. The stored message then carries the rest of the replyTo* 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.

      • replyToProviderMessageIdstring

        Provider 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.

      • replyToFromstring

        Sender of the quoted message (phone number in E.164 format, or the Telegram chat ID). Read-only.

      • replyToTextstring

        Truncated 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.

      • replyToMessageTypestring

        Type of the quoted message (text, image, video, etc.). Read-only.

      • templateIdstring

        Template ID for template messages.

      • templateVariablesobject

        Variables 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 with 400 invalid_request instead (details.field names the value).

        1 properties
        • {key}string
      • templateButtonVariablesobject

        Variables for dynamic button placeholders (URL buttons and OTP buttons). Keys are the button index (0, 1, 2) in the template's buttons array — 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) and quick_reply buttons are not included here.

        1 properties
        • {key}string
      • templateHeaderVariablesobject

        Value for a text-header variable, keyed by 1 (WhatsApp text headers allow at most one variable). Optional override. If omitted, Zavu resolves the header from templateVariables using the header placeholder's name (e.g. novios). Static text headers need no value. The same WhatsApp rule as templateVariables applies: 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, video or document. 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 with 400 invalid_request and details.field set to content.templateHeaderMediaUrl, before anything is sent or charged. A URL that is not HTTPS is refused with 400 invalid_request and a details.errors entry whose path is ["content", "templateHeaderMediaUrl"]. Ignored on channels other than WhatsApp.

        Limitsmax length 2048
      • templateHeaderFilenamestring

        File name WhatsApp shows the recipient for a document header, e.g. invoice-1042.pdf. Works with templateHeaderMediaUrl, or on its own to name the template's stored file. Only valid when the template's header is document: any other header is refused with 400 invalid_request and details.field set to content.templateHeaderFilename, before anything is sent or charged. Ignored on channels other than WhatsApp.

        Limitsmin length 1 · max length 240
      • referralobject

        Click-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: a post source has no click id, and an image ad has no videoUrl.

        10 properties
        • sourceTypestring

          Where the click came from.

          One ofadpost
        • sourceIdstring

          Identifier of the ad or post that produced the click.

        • sourceUrlstring<uri>

          Meta permalink to the ad or post.

        • headlinestring

          Headline of the ad or post.

        • bodystring

          Body copy of the ad or post.

        • mediaTypestring

          Type of media on the ad, when it had any.

          One ofimagevideo
        • imageUrlstring<uri>

          Image of the ad. Present when mediaType is image.

        • videoUrlstring<uri>

          Video of the ad. Present when mediaType is video.

        • thumbnailUrlstring<uri>

          Thumbnail of the ad media.

        • ctwaClidstring

          Click-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 ad sources; a post source has none.

    • conversationIdstring

      ID 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.

    • providerMessageIdstring

      Message ID from the delivery provider.

    • errorCodestringnullable
    • errorMessagestringnullable
    • costnumbernullable

      Zavu platform charge in USD for this message. Messaging is billed against your plan's monthly limits plus usage-based overage.

    • costProvidernumbernullable

      Carrier and delivery cost in USD.

    • costTotalnumbernullable

      Total cost in USD (platform charge + delivery cost).

    • metadataobject
      1 properties
      • {key}string
    • createdAtstring<date-time>required
    • updatedAtstring<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.
  • codestringrequired
  • messagestringrequired
  • detailsobject
401Unauthorized.
  • codestringrequired
  • messagestringrequired
  • detailsobject
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`.
  • codestringrequired
  • messagestringrequired
  • detailsobject
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.
  • codestringrequired
  • messagestringrequired
  • detailsobject
404Template, sender, or quoted message (`content.replyToMessageId`) not found.
  • codestringrequired
  • messagestringrequired
  • detailsobject
409Two cases, told apart by the body.

Two cases, told apart by the body.

  • channel_not_connected (an Error): the sender has no account for the channel asked for, or none at all for auto. details.channel names it. Connect it, then activate it with POST /v1/senders/{senderId}/channels/{channel}/activate. Same code and status as activation uses.
  • An idempotent replay (a MessageResponse): the idempotencyKey was 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 its id and status. 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.
  • codestringrequired
  • messagestringrequired
  • detailsobject
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.
  • codestring
  • messagestring
  • detailsobject
  • errorobject
    3 properties
    • codestringrequired
    • messagestringrequired
    • detailsobject
500Server error.
  • codestringrequired
  • messagestringrequired
  • detailsobject