Languages: every operation supports cURL, Node.js (TypeScript), Python, Go, Ruby, and PHP. The first 15 operations on this page show all six languages; the remaining 41 show cURL and TypeScript — the two most-used.
Messages API
Send and manage messages across SMS, WhatsApp, RCS, Viber, and Email channels Base path:/api/v1/messages
Endpoint count: 56
title: “Worked request and response samples” description: “Worked samples for the send-and-track chain: POST /messages/sms, GET /messages/ status polls, DLR webhook payloads, and the common send errors.”
Worked request and response samples
Correlated message-ID lookup —
GET /api/v1/messages accepts
?message_ids=<id1,id2,...> (up to 100) as a cross-channel delivery log
search. Each id is OR-matched against the internal id OR the
provider-assigned external_id (e.g. a Twilio MessageSid, a WABA message
id, a Resend email id), so the provider id you already hold resolves the
same row. Combine with channel / from_date / to_date to narrow.string
Search DSL — fielded
field:value clauses AND-ed together. Supported fields include message_id, external_id, status, channel, direction, from, to, body, created, updated, conversation_id, contact_id. message_id and external_id both look up the provider-side identifier (Jasmin/SMPP receipt ref, Meta wamid, Resend email id, jambonz CallSid, Telnyx fax id) from the provider console. String fields support * wildcards. Invalid syntax or unknown fields return 400. See the Search message history guide.{ error, meta } envelope. The chain a sender runs: send → poll → receive the delivery webhook.
1. Send an SMS
POST /api/v1/messages/smsid is the handle you poll and that webhook deliveries reference. Sandbox sends terminate at test_sent instead of queued/provider states; when you authenticate with a sandbox key the meta block carries test_mode: true and the message row settles at test_sent:
string
queued on live sends; test_sent on sandbox/test-mode sends. Poll GET /api/v1/messages/{id} (below) for transitions.2. Poll the message status
GET /api/v1/messages/{id}pending → queued → sent → delivered. Terminal failures land on failed / rejected / undelivered / expired. The full lifecycle, including every status the filter accepts, is on the status lifecycle page.
3. Delivery receipts via webhook
Terminal transitions also send amessage.delivered / message.failed event to your webhook_url or subscribed endpoint. The delivery-receipt wire shape (envelope, signature headers, retry behaviour) is documented in Webhook event payloads — subscribe to message.delivered and message.failed rather than polling.
4. Send errors to handle
Invalid recipient →422 INVALID_PHONE_NUMBER. The pre-send numbering-plan check rejects unrouteable destinations before a provider call:
422
402 INSUFFICIENT_BALANCE. Top up your balance and resend:
402
List messages
GET /api/v1/messages/string
Opaque cursor for the next page (from previous response meta.pagination.cursor)
integer
Number of items per page (1–200, default 25)
string (enum: sms|whatsapp|email|rcs|viber|voice|…)
Filter by channel
string (enum: pending|queued|scheduled|accepted|sending|sent|…)
Filter by delivery status. Accepts every status the messages.status column can hold across all channels — DAG states (pending/queued/scheduled/accepted/sending/sent), terminal successes (delivered/submitted_no_receipt/read), failure modes (failed/rejected/undelivered/bounced/expired), and operational sentinels (suppressed/test_sent/deleted/unknown). Email spam complaints are not a distinct status — a complaint DLR is recorded as status=failed with metadata.complaint_type set to “spam_report”. To isolate complaint-driven failures, filter on status=failed and inspect metadata.complaint_type.
string
Filter by recipient
string (enum: inbound|outbound)
Filter by direction
string
Filter by contact
string
Date range filter (e.g. 7d, 30d, 90d)
any
Custom date range start (ISO 8601 date or date-time)
any
Custom date range end (ISO 8601 date or date-time)
string
Filter by conversation
string (enum: conversation|contact|channel)
Group results
Get a message
GET /api/v1/messages/{id}string
required
—
string (enum: related)
—
List media attachments on a message (Twilio Media subresource parity)
GET /api/v1/messages/{id}/mediamedia_url + multi-attachment metadata.media_urls[] into a canonical list with per-attachment ids <msg>:<idx>. Returns 404 when the message id doesn’t exist; 200 with an empty array when the message exists but has no attachments.
string
required
—
Get a per-message RCS recipient-engagement timeline (read receipts + taps)
GET /api/v1/messages/{id}/rcs-eventsevents is the ordered receipt timeline (sent -> delivered -> read -> clicked); taps lists the suggested-action / carousel chips the handset tapped, each carrying the chip text + machine postback, correlated to this card by the same most-recent-card-wins rule the click-attribution stamper uses. Returns 404 when the id does not exist and 422 when the message is not on the rcs channel.
string
required
—
Get a per-message route-trace (Infobip/Sinch DLR-opacity closer)
GET /api/v1/messages/{id}/traceaccepted -> routed -> sent -> terminal (delivered|failed|undelivered|rejected|read|expired|submitted_no_receipt) plus one webhook_fanout event per customer-side webhook delivery. Carries provider, MCC/MNC, external_id, per-event latency_ms, http_code, and attempts. Aggregate summary returns accept_to_sent_ms, sent_to_delivered_ms, and accept_to_terminal_ms so the Infobip-parity ‘where did the time go’ question can be answered in one round-trip. Returns 404 when the message id does not exist.
string
required
—
List blocked destinations (Bandwidth + Twilio Blocked-Numbers parity)
GET /api/v1/messages/blocklistCHANNEL_BLOCKED_DESTINATION (422) BEFORE the platform-CSV and per-org-prefix layers run. Soft-deleted rows are hidden unless ?include_deleted=true is passed.
string (enum: true|false)
—
Read the tenant Fraud Shield configuration
GET /api/v1/messages/fraud-shieldRead the tenant’s in-app messages + content cards
GET /api/v1/messages/in-appenabled master switch, the content cards (classic / banner / captioned_image), and the in-app messages (modal / slideup / fullscreen / html). A never-configured tenant resolves to the disabled, empty-feed default. This is the owned, zero-carrier-cost engagement channel the Orbit web + React Native SDKs render; delivery is an SDK pull from /sdk/in-app/feed.
List inbound (MO) SMS routing rules
GET /api/v1/messages/inbound-routesnumber / sender / keyword / regex and dispatches the first match to a webhook / queue / inbox / team / ivr / auto_reply target; when no rule matches the message falls through to the default tenant-wide fan-out. Webhook targetConfig.secret values are masked in the response. Inbound-only — no outbound (MT) leg is originated, so invariant #45 is untouched. Owner / admin role required.
List still-scheduled messages (Twilio scheduled parity)
GET /api/v1/messages/scheduledto, scheduled_before (ISO timestamp upper bound), and channel. Ordered by scheduled_at ASC. Cap is 200 rows per call — for bigger exports use the generic /messages?status=scheduled list with cursor pagination.
string
—
string
—
string
—
integer
—
Get message stats
GET /api/v1/messages/statsGet the per-locale approval tracker for a template
GET /api/v1/messages/templates/{id}/localizationsstring
required
—
Get usage records (Twilio UsageRecord parity)
GET /api/v1/messages/usage/recordsCheck WhatsApp 24h freeform window status
GET /api/v1/messages/whatsapp/window-statusCancel a message
POST /api/v1/messages/{id}/cancelstring
required
—
Submit message feedback (Twilio Message Feedback parity)
POST /api/v1/messages/{id}/feedbackconfirmed) or explicitly did not (unconfirmed). Idempotent — repeated submissions with the same outcome are no-ops; flipping the outcome refreshes feedback_at for the audit trail. Accepts the Twilio wire field Outcome as an alias for the snake_case canonical outcome. Returns 404 when the message id does not exist.
string
required
—
Mark a message as read (cross-device sync)
POST /api/v1/messages/{id}/readstring
required
—
Redact a message (GDPR/CCPA PII purge)
POST /api/v1/messages/{id}/redactstring
required
—
Re-deliver a message’s lifecycle webhook to all endpoints
POST /api/v1/messages/{id}/replay-webhookstring
required
—
Retry a message
POST /api/v1/messages/{id}/retrystring
required
—
Send a batch of messages (persist-then-attempt)
POST /api/v1/messages/batchvariables for Liquid personalization (e.g. Hi {{ first_name | capitalize | default: "friend" }}!). Every recipient is pre-inserted as a pending row before the per-recipient send pipeline runs, so a pre-create rejection (validation / fraud / quota / sender-validation / country-compliance) flips the row to failed instead of throwing without persisting. Returns a per-recipient outcome map; HTTP 207 (Multi-Status) when at least one recipient failed AND at least one succeeded, 200 when every recipient succeeded.
Block a destination from outbound sends (Bandwidth + Twilio Blocked-Numbers parity)
POST /api/v1/messages/blocklistto parameter on every outbound send across channels — for SMS / MMS / voice / WhatsApp / RCS / Viber / fax submit a full E.164 number; for email / telegram / push / messenger / line / instagram submit the channel’s native destination identifier.
Bulk-cancel still-scheduled messages
POST /api/v1/messages/cancel-scheduledids array (≤1000) OR a filter by (to, scheduled_before, channel) (at least one narrowing predicate required when mode='filter'). Conditional UPDATE gated on status='scheduled' — rows the scheduler has already dequeued are left untouched. Returns { cancelled_count, cancelled: [{ id, channel, scheduled_at }] }.
Send an email
POST /api/v1/messages/emailany
required
—
string
—
string
required
—
string
—
string
—
string
—
any
—
any
—
object[]
—
object
—
string
—
Estimate message cost
POST /api/v1/messages/estimateExport messages
POST /api/v1/messages/exportSend a fax
POST /api/v1/messages/faxstring
required
Recipient fax number (E.164).
string
Sender fax number (E.164).
string
required
Public URL of the document to fax (PDF or TIFF).
string (enum: normal|high|very_high)
—
string
—
object
—
Submit bulk message feedback (Twilio /Messages/Feedback bulk parity)
POST /api/v1/messages/feedback/bulk{id, outcome} rows in a single envelope and persists every row in one Postgres UPDATE — roughly 1000× faster than per-row POSTs at 10k+ row import sizes. Per-row outcome enum (confirmed | unconfirmed) + per-row feedback_at refresh semantics are identical to the single-message endpoint. Twilio-shaped wire envelope {Items:[{Sid,Outcome},…]} is accepted as an alias. Response carries updated[] (rows the UPDATE actually touched), not_found[] (ids the caller supplied that did not match a row in the tenant’s messages table), and a summary counts block. Returns 200 even when not_found is non-empty — the batch as a whole succeeded; per-row outcomes are the caller’s to process.
Evaluate a destination against the tenant Fraud Shield
POST /api/v1/messages/fraud-shield/evaluateSend a group MMS to up to 20 recipients
POST /api/v1/messages/grouppending row before the per-recipient send pipeline runs, so a pre-create rejection (NANP gate, validation, fraud, quota, sender-validation, media SSRF, compliance) flips the row to failed instead of throwing without persisting. Per-leg Idempotency-Key is group-mms:${group_id}:${to}. Returns HTTP 207 (Multi-Status) when at least one recipient failed AND at least one succeeded; 200 when every recipient succeeded; 402 if the org balance cannot cover the group floor; 500 on bulk pre-insert failure.
Create an inbound (MO) SMS routing rule
POST /api/v1/messages/inbound-routesname, matchType (number | sender | keyword | regex), matchValue, targetType (webhook | queue | inbox | team | ivr | auto_reply), targetConfig (validated per target type — a webhook requires an HTTPS url plus optional signing secret), optional priority (default 100), optional enabled (default true), and optional description. A regex matchValue is compiled at write time so a bad pattern is rejected with 422. Returns 201 with the created rule (the webhook secret is returned once, in clear, on create). Owner / admin role required.
Send an Instagram DM
POST /api/v1/messages/instagramstring
required
Instagram-scoped user id (PSID).
string (enum: RESPONSE|UPDATE|MESSAGE_TAG)
required
—
string
—
object
required
—
object
—
string
—
Send a LINE message
POST /api/v1/messages/linestring
required
Recipient identifier (E.164 phone for SMS-like channels; chat id for Telegram).
string
Sender identifier (channel-specific).
string
required
—
string
—
string
—
object
—
string
—
Lint a draft message (compose-time policy + spam scan)
POST /api/v1/messages/lintSend a Messenger message
POST /api/v1/messages/messengerstring
required
Messenger-scoped user id (PSID).
string (enum: RESPONSE|UPDATE|MESSAGE_TAG)
required
—
string
—
object
required
—
object
—
string
—
Preview-render a template payload (Twilio Content Preview parity)
POST /api/v1/messages/preview-rendercomposition_errors array that surfaces the SAME problems the provider would reject on (Meta error_subcode 2388042, RBM INVALID_ARGUMENT), but BEFORE the wire call so no wallet credit is burned. Accepts EITHER a stored template_id or an inline template object for ad-hoc compose previews.
Send an RCS message
POST /api/v1/messages/rcsstring
required
Recipient phone number in E.164 format.
string
Sender identifier.
string
Plain text message body. Required for text sends; omit when sending a
rich_card or rich_card_carousel.string
—
string
—
object
Free-form string metadata. Caller-supplied keys win on conflict with provider flags translated from
rich_card/rich_card_carousel.string
—
object
Standalone rich card payload (Twilio / Verizon parity). Mutually exclusive with
rich_card_carousel.object
Horizontal carousel of 2-10 rich cards (Twilio / Verizon parity). Single-card carousels render as malformed on Android — use
rich_card for single cards.Score artificial-traffic / SMS-pumping risk for a destination
POST /api/v1/messages/risk-signalsRoute preview
POST /api/v1/messages/route-previewSend a message
POST /api/v1/messages/sendstring
required
Recipient identifier (E.164 phone or email)
string
required
Message body text
string (enum: sms|whatsapp|email|rcs|viber|auto)
Delivery channel — defaults to auto-routing when omitted
Smart send
POST /api/v1/messages/smart-sendSend an SMS
POST /api/v1/messages/smsstring
required
Phone number. Accepts E.164 (preferred) or display formats and PBX-extension suffixes (
x1234, ;ext=1234, ,ext=1234); normalized server-side to E.164 before send.string
required
SMS body text
string
DLR callback URL
object
Custom key-value metadata
boolean
When true, marks this send as a throwaway test. The provider call still happens and billing still runs, but the recipient is NOT auto-upserted into the contacts table. Use this for dashboard ‘Send Test SMS’ flows to avoid polluting the operator’s contacts list with verification numbers. TEST-USER P1 / DECISION — TEST_SMS_NO_CONTACT_CREATE_2026_05_28.
string
Twilio ValidityPeriod parity. Carrier-level expiry hint after which the SMSC drops the attempt. Suffixed grammar: s (seconds, default), m (minutes), h (hours), d (days). Examples: ‘30m’, ‘1h’, ‘24h’, ‘2d’. Consumed by Telnyx (valid_until), Jasmin (validity-period), and SMPP (validity_period hours). GAP2_CPaaS_33.
number
Twilio MaxPrice parity. Hard per-message USD ceiling. The send is rejected 422 MAX_PRICE_EXCEEDED if the resolved per-unit cost exceeds this cap — useful as an anti-fraud guard on international SMS to premium-rate destinations. Capped at 100 USD per message to defend against units mistakes. GAP2_CPaaS_33.
string
Twilio FallbackFrom parity. An optional secondary sender identity recorded on the message as
metadata.fallback_from for audit and record-keeping. Same allowed shape as from (E.164 / alphanumeric sender ID / __shared_generic__). Audit-only: the value is stored on the message record and is not currently auto-swapped in on a rejected attempt, so treat it as a record of intent rather than an active carrier-failover retry.Send a Telegram message
POST /api/v1/messages/telegramstring
required
Recipient identifier (E.164 phone for SMS-like channels; chat id for Telegram).
string
Sender identifier (channel-specific).
string
required
—
string
—
string
—
object
—
string
—
Auto-localize a template into multiple locales
POST /api/v1/messages/templates/{id}/localize(channel, name, language) row per locale through the existing carrier-approval write path, so each WhatsApp/RCS variant is carrier-submitted for approval exactly as a hand-authored variant is. Copy-only — it selects no route and sends nothing (invariant #45 untouched). Requires the templates:write scope.
string
required
—
Reputation-scan URLs in a draft message (anti-smishing)
POST /api/v1/messages/url-scanstring
Draft message body to scan for URLs. URL-free bodies return an empty, clean verdict.
Send a Viber message
POST /api/v1/messages/viberstring
required
Recipient identifier (E.164 phone for SMS-like channels; chat id for Telegram).
string
Sender identifier (channel-specific).
string
required
—
string
—
string
—
object
—
string
—
Send a WhatsApp message
POST /api/v1/messages/whatsapptemplate type is permitted; the API rejects freeform text/media with 422 in that case.
string
required
E.164 phone number OR WhatsApp Business Solution UID (5-130 alphanumeric chars).
string
Sender E.164 (overrides default WABA number).
string (enum: text|template|image|document|audio|video|…)
required
Message kind.
text and the media kinds (image/document/audio/video/location/contact) are only permitted inside Meta’s 24h customer-service window; outside it, use template with an approved template or the send is rejected with 422.object
Freeform text body — required when
type is text. Only permitted inside Meta’s 24h customer-service window.object
Approved template to send — required when
type is template. language.code MUST match the locale the template was approved under in WhatsApp Manager (e.g. en_US, en_GB, es_ES, pt_BR), or the send is rejected with 422 WHATSAPP_TEMPLATE_NOT_FOUND (Meta 132001). Supply variables via either components (Meta-native) or the flat template_params sibling field below — not both.object
Flat string→string map filling the template’s positional placeholders (keys ‘1’..’N’). Alternative to the Meta-native
template.components shape.object
Media payload — required when
type is image, document, audio, or video. url must be a signed HTTPS URL (e.g. one returned by POST /api/v1/files/upload).string
DLR callback URL (HTTPS). Optional — an empty string is treated as ‘no webhook’. Non-empty values are validated server-side and must be https:// and pass the SSRF guard.
object
Arbitrary string→string metadata, echoed back on delivery-status webhooks and message lookups.
string
ISO 8601 timestamp to schedule delivery instead of sending immediately (e.g.
2026-08-01T15:00:00Z); a datetime-local value without a timezone suffix is also accepted.Send a WhatsApp template to up to 256 recipients (broadcast list)
POST /api/v1/messages/whatsapp/broadcastpending row before the per-recipient send pipeline runs, so a pre-create rejection (template-not-approved, parameter-count mismatch, 24h-window block, fraud guard, quota, sender-validation) flips the row to failed instead of throwing without persisting. Per-recipient template_params override the batch-level shared template_params. Returns HTTP 207 (Multi-Status) when at least one recipient failed AND at least one succeeded; 200 when every recipient succeeded; 402 if the org balance cannot cover the broadcast floor; 500 on bulk pre-insert failure.
Notify — multi-channel fan-out or waterfall
POST /api/v1/notify/fanout (send every binding immediately, returning per-binding delivery handles) and waterfall (send the richest binding first, then auto-escalate down the ordered bindings asynchronously when the active channel reports undelivered within the TTL). Returns HTTP 200 when every binding succeeds (fanout) or at least one binding queues (waterfall); 207 Multi-Status when at least one binding failed AND at least one succeeded (fanout only); 422 when every binding failed (fanout) or every binding rejected synchronously (waterfall).
object[]
required
Array of per-binding payloads. Each specifies a channel and destination. Minimum 2 bindings for waterfall mode.
string
Shared message body — applied to every binding that doesn’t carry its own.
string
Shared sender identifier — applied to every binding that doesn’t carry its own.
string
Shared template name — applied when neither shared nor per-binding
body is set.object
Shared template parameters — applied to every binding that doesn’t carry its own.
object
Shared metadata bag — merged onto every binding’s metadata.
string (enum: fanout|waterfall)
Delivery strategy.
fanout (default): send every binding immediately. waterfall: send the first binding; auto-escalate remaining bindings on DLR undelivered within the TTL.integer
Waterfall freshness window in seconds. An undelivered DLR landing after this window does NOT escalate. Ignored in
fanout mode. Default 24h; floor 30s, ceiling 24h.Update the tenant Fraud Shield configuration
PUT /api/v1/messages/fraud-shieldorganizations.settings.fraud_shield. Every field is optional and merges over the current defaults: enabled (bool), mode (alert|block), block_threshold / review_threshold (0-100), velocity_ceiling / geo_spread_ceiling (int or null to keep the platform default), and blocked_countries / allowed_countries (ISO-3166-1 alpha-2). The lists are the Shield’s own advisory rules — tenant-scoped, never a global gate. This configures scoring only; it sends nothing and routes nothing.
Author the tenant’s in-app messages + content cards
PUT /api/v1/messages/in-appenabled (bool master switch), cards (up to 100 content cards) and messages (up to 100 in-app messages). Each card/message is validated + normalised — a classic card requires a title, a banner requires an imageUrl, a captioned_image requires both; an html message requires html, every other type requires body; all image/link URLs must be https. Each surface may carry segmentLabels (up to 50): when set, that surface is delivered only to a visitor whose resolved primary segment matches; omitted/empty means it is shown to every visitor. Duplicate ids within a surface are rejected. Persists to organizations.settings.in_app (tenant-isolated); the assembled feed is then served to the SDKs at /sdk/in-app/feed. Sends nothing and routes nothing.
Edit the body and/or scheduled_at of a scheduled message (Twilio Message.update parity)
PATCH /api/v1/messages/{id}status='scheduled' so an edit cannot race the scheduler dequeueing the row. Returns 409 MESSAGE_NOT_EDITABLE when the row is past the scheduled gate (queued / sending / sent / failed / cancelled) or does not exist. No billing side-effect — billing is debited on the send-path AFTER the scheduler picks the row up, so the new body is what gets accounted for + dispatched. At least one of body or scheduled_at must be supplied; scheduled_at is bounded to the same 35-day future horizon as the create path.
string
required
—
string
—
string
—
Update an inbound (MO) SMS routing rule
PATCH /api/v1/messages/inbound-routes/{routeId}description clears it while an omitted key leaves the column untouched. Returns 404 when the route id does not exist, 422 on a malformed match/regex or target config. Owner / admin role required.
string
required
—
Delete a message
DELETE /api/v1/messages/{id}string
required
—
Un-block a destination (Bandwidth + Twilio Blocked-Numbers parity)
DELETE /api/v1/messages/blocklist/{id}string
required
—
Delete an inbound (MO) SMS routing rule
DELETE /api/v1/messages/inbound-routes/{routeId}string
required
—