MsgBubblesDocs

Messages

One endpoint sends everything. Address a recipient with to + from_handle, or send into an existing thread with conversation_id. Whether the recipient gets an iMessage or an SMS is decided per recipient automatically; WhatsApp is opted into per message with channel.

Send a message

POST/v1/messages

Send a text
curl -X POST https://api.msgbubbles.com/v1/messages \
  -H "Authorization: Bearer sk_live_…" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: order-1042-confirmation" \
  -d '{
    "to": "+15555550123",
    "from_handle": "+18005551111",
    "text": "your order shipped!"
  }'

Body parameters

FieldTypeNotes
tostringRecipient — E.164 phone number or iMessage email. Required unless conversation_id is set.
from_handlestringOne of your numbers (see GET /v1/numbers). Required unless conversation_id is set.
conversation_iduuidSend into an existing thread instead of to + from_handle. Required for group chats.
textstringMessage body. text or attachment_ids (or both) must be present.
attachment_idsuuid[]Up to 10 staged attachments — see Attachments.
reply_touuidSend as a threaded reply to one of your message ids.
effectstringiMessage bubble/screen effect id (e.g. confetti).
channel"whatsapp"Force the WhatsApp channel. Needed when the from number serves both iMessage/SMS and WhatsApp — iMessage/SMS wins by default.
voice_memobooleanSend audio attachments as native voice-note bubbles instead of file attachments.

Idempotency

Pass an Idempotency-Key header to make retries safe. A repeated key returns the original message with 200 instead of sending twice; a first send returns 202.

The message object

202 Accepted
{
  "data": {
    "id": "0d4b1f3a-…",
    "conversation_id": "7f2c9e1b-…",
    "direction": "outbound",
    "status": "queued",
    "channel": null,
    "text": "your order shipped!",
    "effect": null,
    "reply_to": null,
    "error": null,
    "metadata": null,
    "attachments": [],
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "created_at": "2026-06-11T18:21:04.000Z"
  },
  "request_id": "req_…"
}

Message lifecycle

queued → sent → delivered → read, with failed as the terminal error state and unsent after a successful unsend. Each transition also fires a webhook, so you rarely need to poll. channel resolves once the message is sent — imessage, sms, or whatsapp.

List and fetch

GET/v1/messages

Paginates newest-first. Query params: conversation_id (filter to a thread), limit (1–100, default 50), and before (ISO timestamp cursor). The response is { data, has_more, request_id } — pass the oldest created_at as the next before.

GET/v1/messages/:id

Check reachability

GET/v1/handles/availability

Look up whether a recipient is reachable on iMessage — whether a send will go blue (iMessage) or fall back to green (SMS) — before you send. Pass the recipient as a handle query param (E.164 number or iMessage email).

Check a handle
curl https://api.msgbubbles.com/v1/handles/availability?handle=+15555550123 \
  -H "Authorization: Bearer sk_live_…"
200 OK
{
  "data": { "handle": "+15555550123", "imessage": true, "channel": "imessage" },
  "request_id": "req_…"
}

imessage is whether the handle is registered with iMessage; channel is the channel a send would use (imessage or sms). "Not on iMessage" usually means Android, but also de-registered or never-activated Apple numbers — we can only see iMessage registration.

Actions on a sent message

React

POST/v1/messages/:id/react

Tapback or emoji
{ "reaction": "love" }          // tapbacks: love, like, dislike, laugh, emphasize, question
{ "reaction": "🔥" }            // or any single emoji
{ "reaction": "love", "remove": true }

Reactions target a delivered message (409 before then) and work on iMessage and WhatsApp.

Edit and unsend (iMessage only)

POST/v1/messages/:id/edit

POST/v1/messages/:id/unsend

Body for edit: { "text": "updated body" }. Both return 409 for SMS — carriers offer no edit or recall — and before the message has actually sent.

Retry a failed send

POST/v1/messages/:id/retry

Re-queues a message in status failed (for example, sent while the number was offline past the retry window). Parts that already went out are never re-sent.