Zernio
Zernio
API Reference

Messages

Conversations

List conversationsGETSearch conversationsGETCreate conversationPOSTGet conversationGETUpdate conversation statusPUTMark a conversation as readPOSTHand a conversation to or from Meta Business AgentPOST

Messages

List messagesGETSend messagePOSTEdit messagePATCHDelete messageDELETEUpload media filePOSTResolve message attachmentGET

Reactions & Typing

Add reactionPOSTRemove reactionDELETESend typing indicatorPOST
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
Messages

Send message

Send a message in a conversation. Supports text, attachments, quick replies, buttons, templates, and message tags. Attachment and interactive message support varies by platform.

WhatsApp per-recipient rate limit: WhatsApp caps how many messages you may send to the same recipient in a short window and rejects the excess with error code 131056 ("Too many messages sent to this recipient"). Pace sends to a single recipient at roughly 10 per minute; bursts above that return a 400 with code 131056. Sends to other recipients are unaffected, so parallelise across recipients rather than flooding one.

WhatsApp template messages: to send an approved template into this conversation (required when the 24-hour customer-service window is closed), use the template field with a single element carrying the template reference: { "elements": [{ "name": ..., "language": ..., "components": [...] }] }. See the template field below for the exact shape. To send a template to a phone number you have no conversation with yet, use the create-conversation endpoint (POST /v1/inbox/conversations) instead.

Zernio resolves the exact APPROVED template name and language before any WhatsApp template send. A failed lookup or missing exact definition sends no message and returns code platform_api_error, type platform_error, and platform whatsapp. Sanitized Meta code, message, and error_data.details are returned in platformError; details identifies phase: template_lookup, the query-free endpoint, upstream status, and only safe provider usage or retry headers.

WhatsApp rich interactive messages (list, CTA URL, Flow, location request) are available via the interactive field. Tap events are delivered through the message.received webhook with WhatsApp-specific metadata fields (interactiveType, interactiveId, flowResponseJson, flowResponseData).

Idempotency: send an Idempotency-Key header to make retries safe (e.g. after a client-side timeout where delivery is unknown): same key + same body replays the original response (with Idempotent-Replayed: true) instead of sending the message a second time; same key + different body returns 422; a key still in flight returns 409. Works for JSON and multipart (file upload) requests alike. Keys are retained for 24 hours.

Only successful (2xx) responses are stored for replay: if the request throws or returns a non-2xx status, the key is released so the same key can be retried once the problem is fixed. The header therefore protects the "request succeeded but the response was lost" case. For an ambiguous failure (a 5xx or a network timeout), reconcile before retrying: a failure after the platform already accepted the message also releases the key, and a blind retry could send it twice. List the conversation's messages first, and treat an empty result as inconclusive rather than as proof nothing was sent, since a send that failed while being recorded leaves no trace on our side.


POST
/v1/inbox/conversations/{conversationId}/messages

Authorization

bearerAuth
AuthorizationBearer <token>

API key authentication: send your Zernio API key in the Authorization header, prefixed with Bearer.

In: header

Path Parameters

conversationId*string

Opaque conversation identifier, accepted verbatim from the list endpoint or from the conversationId on inbox webhooks. Format not to be assumed.

Header Parameters

Idempotency-Key?string

Optional client-generated unique key (e.g. a UUID) that makes retries safe. Same key + same body replays the original response; same key + different body → 422; key still processing → 409.

Lengthlength <= 255

Request Body

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

{  "success": true,  "warnings": [    {      "code": "ignored_field",      "param": "replyTo",      "message": "string"    }  ],  "data": {    "messageId": "string",    "conversationId": "string",    "attachments": [      {        "type": "string",        "url": "string"      }    ],    "messageIds": [      "string"    ],    "partialFailure": {      "part": "text",      "error": "string",      "platformError": {        "code": 0,        "subcode": 0,        "fbtraceId": "string",        "type": "string"      }    }  }}
Was this page helpful?

List messages

Fetch messages for a specific conversation, with cursor-based pagination and ordering control. Pagination: pass `pagination.nextCursor` from a prior response back as the `cursor` query param to fetch the next page. The cursor is opaque; do not parse or construct it client-side. Sort order: defaults to `asc` (oldest first, chat style). For the "show me the latest messages" pattern, pass `?sortOrder=desc&limit=N`. X, Instagram, Telegram, WhatsApp and Reddit honor the requested order from the local message store. For Facebook and Bluesky, the upstream APIs only return newest-first and have no order parameter, so sort order is best-effort and only reverses items within a single page (pages still walk newest→oldest). The response field `sortOrderApplied` tells you what was actually applied. Reddit threads are paginated client-side because Reddit's API has no per-thread cursor. Very long threads may be upstream-truncated by Reddit's inbox/sent windows (~100 most-recent items each); this is a Reddit platform limitation. Instagram and Facebook conversations include history from before the account was connected, replayed from Meta. That replay covers the 500 most recent messages per conversation: a longer thread keeps its newest 500 and older messages are not retrievable. Messages that arrived after the account was connected are unaffected. Replayed messages are stored as already read and emit no webhooks. X limitation: X's encrypted "X Chat" messages are not accessible via the API. Conversations where the other participant uses encrypted X Chat may only show your outgoing messages. See the list conversations endpoint for more details. This endpoint is read-only and does NOT mark messages as read or send read receipts. To mark a conversation read (and send WhatsApp blue ticks on eligible accounts), call `POST /v1/inbox/conversations/{conversationId}/read`.

Edit message

Edit the text and/or reply markup of a previously sent Telegram message. Only supported for Telegram. Returns 400 for other platforms.

accountId*string

Account ID

message?string

Message text

attachmentUrl?string

URL of the attachment to send (image, video, audio, or file). The URL must be publicly accessible. For binary file uploads, use multipart/form-data instead. On WhatsApp, combining an image, video, or file with buttons renders the media as the header of one interactive reply-button message; audio cannot be combined with buttons.

category?"utility"

WhatsApp only (Meta Direct Send). Sends this message as a business-initiated UTILITY message without an approved template, for example outside the 24-hour customer service window; Meta matches or auto-creates a template asynchronously. The WhatsApp Business Account must be eligible for Direct Send, otherwise the send fails with an error telling you to use an approved message template instead. Supported only for text messages (link preview ok) and interactive messages (reply buttons, CTA URL buttons, voice-call button, header of text/image/video/document). Cannot be combined with template, attachments, location, or contacts. Utility messages only; marketing content is not allowed under this category. Accepted on the JSON body only, not on multipart requests.

Value in

  • "utility"
linkPreview?boolean

WhatsApp only. Set false to send the message without a link-preview thumbnail for the first URL in the text. Defaults to true, which is how every WhatsApp text has been sent to date. Ignored on other platforms. Accepted on the JSON body only, not on multipart requests.

Defaulttrue
attachmentType?string

Type of attachment. Defaults to file if not specified.

Value in

  • "image"
  • "video"
  • "audio"
  • "file"
attachmentName?string

WhatsApp only. Display name for a document sent via attachmentUrl with attachmentType: file (e.g. "Report.pdf"). Maps to the recipient's file name; without it WhatsApp derives the name from the URL and shows "Untitled". Ignored for image/video/audio and for binary uploads (which use the uploaded file's name).

voiceNote?boolean

WhatsApp only. When true on an audio attachment, the message is sent as a voice message (PTT): the recipient sees the waveform + voice-note UI instead of a basic audio attachment. The audio file MUST be .ogg encoded with the OPUS codec (mono) per Meta's voice-message contract; other formats are rejected by WhatsApp. Ignored for non-audio attachments.

quickReplies?array<>

Quick reply buttons. Mutually exclusive with buttons. Max 13 items.

Itemsitems <= 13
buttons?array<>

Action buttons. Mutually exclusive with quickReplies. Max 3 items.

Instagram / Facebook: also mutually exclusive with template. A Meta message carries one body shape, so sending both is a 400 rather than a silent drop of the buttons.

WhatsApp: buttons always render as interactive reply buttons. Only title and payload are used; type, url, and phone are ignored (WhatsApp has no URL/phone button in this field; use the interactive field with type: cta_url for a link button). payload becomes the button reply ID delivered on the message.received webhook when the user taps. To send a simple reply-button message, provide title + payload and set type: postback, e.g. { "type": "postback", "title": "Yes", "payload": "yes" }.

Combine buttons with attachmentUrl and attachmentType image, video, or file to render one WhatsApp message with a media header, body text, and reply buttons. Audio is not a supported interactive header and returns 400 when combined with buttons.

Itemsitems <= 3
template?

Platform-dependent template payload. Ignored on Telegram.

Instagram / Facebook: a generic template (carousel). Set type: generic and provide up to 10 elements, each with a title (required) and optional subtitle, imageUrl, and buttons. Mutually exclusive with the top-level buttons field (sending both is a 400); put the card's buttons on its elements instead. On Facebook, imageAspectRatio (horizontal, the default, or square) sets how Messenger renders the element images; Instagram has no such setting and rejects it.

WhatsApp: sends an approved WhatsApp template message, the only message type WhatsApp accepts when the 24-hour customer-service window is closed. Provide exactly one element carrying the template reference: { "elements": [{ "name": "order_update", "language": "en_US", "components": [...] }] } (type is ignored on WhatsApp). components is optional and is forwarded unchanged as the template.components array of Meta's Cloud API send payload; use it to fill body/header variables and button parameters, e.g. [{ "type": "body", "parameters": [{ "type": "text", "text": "John" }] }]. Templates with media headers (image, video, document) must include the header component with its media link here at send time. To send a template to a phone number with no existing conversation, or to have media headers filled in automatically from the template definition, use the create-conversation endpoint (POST /v1/inbox/conversations) instead.

interactive?

WhatsApp-only. Rich interactive payload for list messages, CTA URL buttons, Flow prompts, location requests, voice-call buttons, and commerce messages (single product, product list, catalog, and carousel). When set, takes priority over buttons and quickReplies. The shape mirrors Meta's Cloud API interactive object for the types in the enum below.

Use buttons / quickReplies for simple button replies (WhatsApp's interactive.type: "button"): the abstraction caps at 3 buttons and handles the auto-conversion for you. Use this field only for the types listed in the enum below.

All interactive messages are session messages: they can only be sent inside the 24-hour customer service window opened by the user's last inbound message.

Commerce types (product, product_list, catalog_message, and product carousels) require a Meta catalog connected to the WhatsApp Business Account in Commerce Manager. Media carousels (image/video cards) do not need a catalog.

For product, body is optional (WhatsApp renders the product card itself) and header is not allowed (the product image is the header). For product_list, a header with type: "text" is required. For carousel, top-level header/footer are not supported; media goes on each card instead.

For voice_call, the message renders WhatsApp's native call button; tapping it starts a voice call to your business number. Requires WhatsApp Business Calling to be enabled on the sending number. The optional parameters.payload string is echoed back on the calls webhook (as cta_payload) for attribution.

For location_request_message, action may be omitted (we default it to { "name": "send_location" }). WhatsApp renders a localized "Send location" button; the user's reply arrives as a regular location message in the conversation.

For request_contact_info, action may be omitted (we default it to { "name": "request_contact_info" }). WhatsApp renders a localized share button that cannot be relabelled, so put the reason for asking in body.text: this is a consent prompt, and a bare request converts badly. The reply arrives as an inbound contacts message with metadata.contactsOrigin set to contact_request, and we fold the shared number back into the contact automatically. A contacts message with origin other is a card the user picked from their address book and is NOT proof of their own number.

For catalog_message, action may also be omitted (we default it to { "name": "catalog_message" }).

For address_message, parameters.country is required (Meta rejects the whole send without it); everything else in parameters (values, saved_addresses, validation_errors) is forwarded to Meta as-is. This is Meta's native structured shipping-address capture, generally available in India as of 2026-08; check Meta's documentation for current country availability before relying on it elsewhere. The submitted address arrives as an nfm_reply on the message.received webhook, same as a Flow submission, but with metadata.nfmReplyName set to address_message so you can tell the two apart.

Tap events come back via the message.received webhook with metadata.interactiveType set to list_reply or nfm_reply. Carts submitted from commerce messages arrive as metadata.order; product inquiries arrive as metadata.referredProduct.

replyMarkup?

Telegram-native keyboard markup. Ignored on other platforms.

messagingType?string

Facebook messaging type. Required when using messageTag.

Value in

  • "RESPONSE"
  • "UPDATE"
  • "MESSAGE_TAG"
messageTag?string

Facebook message tag for messaging outside 24h window. Requires messagingType MESSAGE_TAG. Instagram only supports HUMAN_AGENT.

Value in

  • "CONFIRMED_EVENT_UPDATE"
  • "POST_PURCHASE_UPDATE"
  • "ACCOUNT_UPDATE"
  • "HUMAN_AGENT"
replyTo?string

Platform message ID to quote-reply to. For WhatsApp, pass the wamid; for Telegram, the Telegram message ID (delivered as message.platformMessageId on webhooks, and as id on each entry of the list-messages endpoint). On Slack it threads the reply (thread_ts) instead of quoting. Instagram and Facebook Messenger do not support send-side quote replies: the message is sent without a quote and the successful response includes a warnings entry with code ignored_field and param replyTo. Other platforms without send-side reply support ignore this field.

location?

WhatsApp-only. Send a location pin.

contacts?array<>

WhatsApp-only. Send one or more contact cards.