Skip to main content
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 lookupGET /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.
Copy a request as written, substitute your own ids, and compare the response envelope. Errors follow Devotel Orbit’s { error, meta } envelope. The chain a sender runs: send → poll → receive the delivery webhook.

1. Send an SMS

POST /api/v1/messages/sms
Request
The returned id 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}
Typical transition order: pendingqueuedsentdelivered. 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 a message.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
Empty wallet → 402 INSUFFICIENT_BALANCE. Top up your balance and resend:
402

List messages

GET /api/v1/messages/
Retrieve messages with cursor-based pagination and optional channel/status filters.
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}/media
Twilio /Messages/<sid>/Media parity. Returns the MMS attachments addressable on the message, merging the single- column media_url + multi-attachment metadata.media_urls[] into a canonical list with per-attachment ids &lt;msg&gt;:&lt;idx&gt;. 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-events
Returns the recipient-level RCS engagement timeline for one outbound RCS card, assembled from data already persisted by the Dotgo DLR + P2A webhooks. events 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}/trace
Returns the ordered routing+DLR timeline assembled from the data we already persist on the message row and its webhook_deliveries fan-out. Events: accepted -> 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/blocklist
Returns the tenant’s active outbound-blocklist rows. Outbound sends to any active destination are rejected with CHANNEL_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-shield
Returns the tenant’s Fraud Shield config — enforcement mode (alert/block), risk-score block/review thresholds, per-prefix velocity + 24h geo-spread ceilings, and country allow/block lists. A never-configured tenant resolves to the all-defaults config (Shield disabled).

Read the tenant’s in-app messages + content cards

GET /api/v1/messages/in-app
Returns the tenant’s authored in-app content — the enabled 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-routes
Returns the tenant’s inbound-message routing rules in evaluation order (priority ascending, then created-at). Each rule matches an incoming (MO) message by number / 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/scheduled
Returns the tenant’s still-scheduled (status=‘scheduled’) messages. Optional narrowing via to, 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/stats
Returns aggregated stats for a channel over a time window.

Get the per-locale approval tracker for a template

GET /api/v1/messages/templates/{id}/localizations
Returns the per-locale carrier-submission tracker for a template — every language variant sharing this template’s (channel, name), each with its own carrier-approval status, external_id, and any submission_error. The :id may be any variant’s id; it resolves the shared (channel, name) and returns all sibling variants. Requires the templates:read (or templates:write) scope. Returns 404 when the id does not exist for the tenant.
string
required

Get usage records (Twilio UsageRecord parity)

GET /api/v1/messages/usage/records
Returns per-category usage aggregates (sms-inbound, sms-outbound, mms-inbound, mms-outbound, voice-inbound, voice-outbound) with optional per-country splits. Twilio Messages /v1/Usage/Records parity.

Check WhatsApp 24h freeform window status

GET /api/v1/messages/whatsapp/window-status
Returns whether the recipient/sender pair is within Meta’s 24h re-engagement window. Used by the dashboard to gate freeform vs template-only composition.

Cancel a message

POST /api/v1/messages/{id}/cancel
Transitions a queued or scheduled message to ‘cancelled’. Terminal statuses (sent / delivered / failed / read / deleted) return 409 CONFLICT so the caller can surface an actionable error. No billing reversal is issued for messages cancelled before dispatch.
string
required

Submit message feedback (Twilio Message Feedback parity)

POST /api/v1/messages/{id}/feedback
Twilio-parity caller-side conversion ping. Records whether the message produced the intended business outcome (confirmed) 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}/read
string
required

Redact a message (GDPR/CCPA PII purge)

POST /api/v1/messages/{id}/redact
Twilio /Messages/<sid>/redact parity. Permanently purges body, media_url, and recipient/sender phone numbers from a message row while preserving billing-grade metadata (status, channel, segments, price, currency, mccmnc, timestamps). Idempotent — a second call observes the historic redacted_at. Terminal-status gated — returns 409 if the message is still in flight (queued / sending / scheduled). Owner/admin-only.
string
required

Re-deliver a message’s lifecycle webhook to all endpoints

POST /api/v1/messages/{id}/replay-webhook
Re-dispatches the message’s current-state lifecycle webhook (message.created / message.sent / message.delivered / message.failed / message.read, mapped from the persisted status) to every active subscribed endpoint, reusing the live send pipeline’s signing + retry path. Returns 202 with the resolved event type and the number of endpoints the replay was enqueued against (0 when no active endpoint subscribes). The deliveries land asynchronously and surface in the message-detail Webhook Fan-out card. Distinct from the webhook-endpoint-scoped POST /webhooks//deliveries//replay, which re-fires a single stored delivery to one endpoint.
string
required

Retry a message

POST /api/v1/messages/{id}/retry
string
required

Send a batch of messages (persist-then-attempt)

POST /api/v1/messages/batch
Bulk-sends a single channel to up to 10000 recipients (raised from 1000 for Twilio-parity, 2026-05-29). Each recipient may carry per-row variables 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/blocklist
Adds an outbound destination to the tenant blocklist. Returns 409 BLOCKED_NUMBER_ALREADY_EXISTS when the destination is already on the active list. The destination is matched verbatim against the to 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-scheduled
Twilio-style mass-cancel for a leaky scheduled fan-out. Accepts EITHER an explicit ids 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/email
Send a transactional email via the configured provider (Resend by default). Either html or text must be supplied. Attachments cap at 25 MB total / 10 MB per file with a MIME allowlist.
any
required
string
string
required
string
string
string
any
any
object[]
object
string

Estimate message cost

POST /api/v1/messages/estimate
Preview estimated cost for sending a message without actually sending it.

Export messages

POST /api/v1/messages/export

Send a fax

POST /api/v1/messages/fax
Send a fax via Telnyx. Supports PDF + TIFF, capped at 50 MB per Telnyx’s hard limit.
string
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
Bulk variant of POST /messages/:id/feedback for conversion-outcome imports from mobile-measurement partners, CRMs, and attribution vendors. Accepts 1 to 1000 {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/evaluate
Scores a destination with the artificial-traffic scorer using the tenant’s configured velocity / geo-spread ceilings, then applies the Shield’s score thresholds and country allow/block lists to return a terminal decision (allow/review/block), whether that block is enforceable (mode=block + decision=block), the composite score + band, and the machine-readable reason codes that drove it. Read-only + advisory — the Devotel softswitch remains the sole outbound path.

Send a group MMS to up to 20 recipients

POST /api/v1/messages/group
Fans a single MMS payload (body + media_urls) out to up to 20 individual recipients in one operation. Each recipient is pre-inserted as a pending 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-routes
Creates a routing rule. Body fields - name, 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/instagram
Send a direct message on Instagram via Meta Messenger Platform. Rate limited to 200/hour (matches Meta’s per-app cap; the older 150/hour cap rejected sends Meta would have accepted).
string
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/line
Send a LINE message via the LINE Messaging API.
string
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/lint
Returns policy + spam-keyword violations for a draft message without sending. Used by compose dialogs to surface inline warnings before send.

Send a Messenger message

POST /api/v1/messages/messenger
Send a Facebook Messenger message via Meta’s Messenger Platform API.
string
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-render
Renders the final outbound payload — WhatsApp template-with-buttons, RCS rich card, SMS/MMS/email body — with positional and Liquid-style variable substitution applied. Returns the rendered shape plus a structured composition_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/rcs
Send an RCS Business Messaging message (text, image, standalone rich card, or horizontal carousel of 2-10 rich cards).
string
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.
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-signals
Returns a 0-100 artificial-traffic / SMS-pumping risk score, band (low/elevated/high/critical), advisory recommendation (allow/review/block), and a transparent per-factor breakdown for a destination. Composes the tenant’s existing internal signals — destination-prefix patterns (IRSF / premium-rate ranges, NANP +1 555-NXX test range), recent send-history velocity to the destination prefix, recent delivery-conversion rate, 24h geo-spread, and the self-service outbound blocklist — into a Lookup-style score the caller can query BEFORE committing spend. This is a read-only scoring surface: it sends nothing and routes nothing.

Route preview

POST /api/v1/messages/route-preview
Preview smart routing recommendation without sending. Returns recommended channel, fallback chain, cost estimate, and engagement score.

Send a message

POST /api/v1/messages/send
Send a message across any channel. When channel is omitted or “auto”, the smart router selects the optimal channel.
string
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-send
AI-powered channel recommendation and delivery.

Send an SMS

POST /api/v1/messages/sms
Send an SMS message to an E.164 phone number.
string
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/telegram
Send a message via the Telegram Bot API.
string
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
Auto-translates one canonical template into 1–25 target-locale variants and bulk-creates one (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-scan
Scans every URL in a draft message body against URL/link-reputation heuristics (phishing/malware/smishing) without sending. Used by compose dialogs to flag dangerous destination links before send. Read-only and advisory — it sends nothing, routes nothing, and enforces nothing; the caller decides what to do with the verdict. Rate-limited to 120 requests/minute per tenant.
string
Draft message body to scan for URLs. URL-free bodies return an empty, clean verdict.

Send a Viber message

POST /api/v1/messages/viber
Send a Viber Business Messages message.
string
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/whatsapp
Send a WhatsApp text, template, or media message. Outside Meta’s 24h re-engagement window only template 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/broadcast
Fans out a single approved WhatsApp template to up to 256 individual recipients in one operation. Each recipient is pre-inserted as a pending 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/
Send a single payload to multiple recipients across heterogeneous channels (SMS, WhatsApp, email, RCS, push, etc.) in one API call. Each recipient is addressed by channel and destination (e.g., phone number or email). Supports two delivery strategies: 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-shield
Persists the tenant’s Fraud Shield config to organizations.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-app
Replaces the tenant’s in-app content. Body: enabled (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}
Twilio-parity edit of a still-scheduled message body and/or scheduled_at. The DB conditional UPDATE gates on 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}
Partially updates a routing rule by id. Any subset of the create fields may be supplied; the rule’s match and target config are re-validated as a coherent whole, and a provided null 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}
Soft-deletes the blocklist row by id. The destination becomes send-eligible immediately. Returns 404 when the id does not exist or has already been soft-deleted (a re-delete is a no-op intent and surfaces the same outcome as a missing id).
string
required

Delete an inbound (MO) SMS routing rule

DELETE /api/v1/messages/inbound-routes/{routeId}
Removes a routing rule by id. Subsequent inbound messages that would have matched fall through to the next rule (or the default tenant-wide fan-out). Returns 404 when the route id does not exist. Owner / admin role required.
string
required