Messagesv2

Send a message

POST/chats/{chatId}/messages

Send a message to a chat. The chatId can be: (1) E.164 phone number, (2) email address, (3) group ID (grp_xxxx), or (4) comma-separated list of phone/email for multi-recipient chats. For multi-recipient, an unnamed group is automatically created or reused if the exact participant combination already exists. For explicit groups, the group must be linked to an existing iMessage chat.

iMessage send-with-effect: set the optional effect field to attach an Apple expressive send (slam, loud, gentle, invisible-ink) or screen effect (echo, spotlight, balloons, confetti, love, lasers, fireworks, celebration). Effects are an iMessage-only feature — when the recipient is on SMS/RCS the message is delivered without the animation. Effects are not supported in multipart (parts) mode.

Threaded replies (iMessage inline reply): set the optional reply_to field to send the outgoing message as a reply to a specific earlier message. Two shapes are accepted: { "message_id": "msg_…" } references a Blooio-minted message in the same chat (most common — the message_id returned by an earlier send or surfaced on a message.received webhook), or { "guid": "…", "part_index": 0 } references the raw iMessage GUID for the rare case where the parent wasn't recorded by Blooio. The reply must target the same chat and the same from-number as the new send, and the parent must be no older than 30 days (the iMessage on-device retention horizon). Reply support is iMessage-only and is rejected on Twilio, dashboard-Twilio, and hybrid send paths; it's also rejected on multi-message fan-outs (text array or per-part URL-balloon batch). See the 400 responses for the full set of reply_target_* error codes.

Path parameters

chatIdRequiredstring

Chat identifier. Can be: (1) phone number in E.164 format (e.g., +15551234567), (2) email address, (3) group ID (grp_xxxx), or (4) comma-separated list of phone numbers/emails for multi-recipient group chats (e.g., +15551234567,+15559876543). All values should be URL-encoded.

Headers

AuthorizationRequiredstring

Your API key, sent as a bearer token: Authorization: Bearer <api_key>. Editing this stays in sync with the API key box on the right.

Bearer
Idempotency-Keyoptionalstring

Unique key to prevent duplicate message sends. If the same key is used again, the original message_id and status are returned.

Body parameters

JSON
textoptionalstring | string[]

Message text. Can be a single string or array of strings (each becomes a separate message)

formatoptionalstring

How to interpret text (and each parts[].text). Defaults to plain, which sends the string exactly as given. With markdown, four constructs are parsed and delivered as real iMessage rich text — the recipient sees styled text, not delimiters: | Construct | Syntax | | --- | --- | | Bold | **bold** or __bold__ | | Italic | *italic* or _italic_ | | Underline | ++underline++ | | Strikethrough | ~~strike~~ | They nest freely (**bold and _italic_**). Everything else Markdown can express — headings, lists, links, code spans, blockquotes, images — is NOT styling iMessage can carry, so it is passed through as literal characters: [Blooio](https://blooio.com) is delivered with its brackets and URL intact, and # Heading keeps its #. Escape a delimiter with a backslash (\*not italic\*) to send it literally. The styling travels in the message's attributed body, so the stored text and the text returned on reads and webhooks is always the plain string the recipient sees, with the delimiters removed. The Markdown itself comes back as formatted_text, re-serialized into a normalized spelling rather than echoed verbatim (__bold__ returns as **bold**). Only valid on Blooio iMessage channels — 400 format_unsupported_for_channel_type on any other channel type, since no other channel type has a rich-text equivalent and would otherwise deliver your delimiters as literal text. Rich text also requires the message to be delivered over iMessage: a Blooio send that falls back to SMS arrives as unstyled plain text (the text string), because SMS cannot carry styling. Applies to a text send and to parts. Rejected with 400 invalid_content when combined with attachments — a media caption is not a styled bubble, so send the media and the styled text as two messages — when set without text or parts, when the Markdown source exceeds 20000 characters, or when it compiles to more than 256 distinct formatting ranges.

"plain""markdown"
attachmentsoptionalstring | object[]

Array of attachment URLs or objects with url/name. **Voice memos:** a single audio file (.mp3, .m4a, .wav, .aac, .opus, .ogg) is automatically sent as a voice memo (the native waveform/scrubber bubble), not a plain audio-file attachment — no extra field is needed. A voice memo is a standalone bubble, so it cannot be combined with text or any other attachment; send the voice memo and the text as two separate messages.

Array of string | object

string
object
urlRequiredstring
nameoptionalstring
use_typing_indicatoroptionalboolean

Whether to show typing indicator before sending. Defaults to org preference.

from_numberoptionalstring

E.164 phone number to send from. For Twilio API keys, this is optional — if omitted, the first assigned Twilio number is auto-selected. For Blooio (iMessage) API keys, this selects a specific number from your pool. Must be a number assigned to your API key.

share_contactoptionalboolean

If true, the contact card (Name & Photo) will be shared with this message. The contact card is piggybacked onto the outgoing message. Defaults to false. ⚠️ Only available on **Dedicated Commercial** and **Dedicated Enterprise** plans — other plans receive a 403.

partsoptionalobject[]

Ordered array of message parts. Two modes: 1. **Multipart mode** — parts sent as a single unified iMessage bubble (mix of text and attachment parts). This is the default. 2. **URL-balloon batch mode** — triggered when any part has a link_preview object. Each part becomes its own rich-link-preview iMessage; parts are sent sequentially in array order. In batch mode every part must be text-only with text being a single http(s) URL. Response contains message_ids[] + count instead of message_id.

Array of object

textoptionalstring

Text content for this part. Mutually exclusive with 'url'.

mentionoptionalstring

Participant phone number or email to @-mention. Only valid with 'text'. The entire text of the part is rendered as the mention.

urloptionalstring

URL to an attachment for this part. Mutually exclusive with 'text'.

nameoptionalstring

Filename for the attachment. Only valid with 'url'.

effectoptionalstring

Optional. Attach an iMessage send-with-effect to the outgoing message. **Bubble effects** (apply to a single text bubble): - slam — Slam - loud — Loud - gentle — Gentle - invisible-ink — Invisible Ink **Screen effects** (full-screen animation in the recipient's chat): - echo — Echo - spotlight — Spotlight - balloons — Balloons - confetti — Confetti - love — Love (heart) - lasers — Lasers - fireworks — Fireworks - celebration — Celebration (sparkles) Values are case-insensitive and accept either dashes or spaces ("Invisible Ink" and "invisible-ink" both work). Pass "none" or omit the field to send without an effect. **Limitations:** - iMessage-only — when the chat is delivered as SMS or RCS the message is sent without an animation. - Not supported alongside the parts array (multipart bubbles cannot carry an effect). Use the top-level text field instead. - When text is an array, every message in the array is sent with the same effect.

"slam""loud""gentle""invisible-ink""echo""spotlight""balloons""confetti""love""lasers""fireworks""celebration""none"
reply_tooptionalReplyToRequest

Optional. Send this message as an iMessage inline reply targeting a specific earlier message. iMessage-only — rejected on Twilio, hybrid, and multi-message fan-outs (text array or URL-balloon batch).

message_idoptionalstring

Blooio message_id of the parent. Must belong to the same chat, same from-number, and be no older than 30 days. Returns 404 reply_target_not_found if unknown.

guidoptionalstring

Raw iMessage GUID of the parent. When supplied without a message_id, Blooio attempts to look up the parent via provider_message_guid; if the parent isn't in our table the send still proceeds (Lava will thread on the device when possible) and the response carries parent_unresolved: true.

part_indexoptionalinteger

Which part of the parent to reply to. Defaults to 0 (covers the 99% case of replying to a single-part text message).

Returns

message_idoptionalstring

ID of the sent message (single-message sends)

message_idsoptionalstring[]

IDs of sent messages. Present when text is an array or when parts uses per-part link_preview (URL-balloon batch mode).

countoptionalinteger

Number of messages sent. Only present in URL-balloon batch mode.

statusoptionalstring

Initial status of the message(s). queued = accepted for delivery (the normal 202 result); failed = rejected before dispatch. Subsequent transitions (sentdelivered, or failed) are reported via the status endpoint and message.status webhooks.

"queued""failed"
group_idoptionalstring

Group ID when sending to multi-recipient (new or existing)

group_createdoptionalboolean

True if a new unnamed group was created for this multi-recipient message

participantsoptionalstring[]

List of participants (present for multi-recipient)

parent_unresolvedoptionalboolean

Present (and true) only when reply_to.guid was supplied without a message_id and the GUID didn't map to any Blooio-minted row. The send still proceeds and the device may still thread it; this flag signals that Blooio couldn't link the new message to a known parent.

Response codes

200Duplicate request (idempotency key matched)
202Message accepted for sending
400Bad request. Threaded-reply failures use the response body's `code` field: - `reply_target_invalid` — `reply_to` was supplied but missing both `message_id` and `guid`, or the resolved parent has no provider GUID. - `reply_target_chat_mismatch` — the parent message lives in a different chat than the new send. - `reply_target_device_mismatch` — the parent was sent or received on a different line (allocation). Switch the from-number to match. - `reply_target_expired` — the parent is older than the 30-day iMessage on-device retention window and may have been purged from the device. - `reply_target_not_supported` — `reply_to` was used on a path that doesn't support inline replies (Twilio, hybrid, or multi-message fan-out). - Other validation failures (missing content, invalid recipient, etc.) — no `code` field.
401Authentication required or invalid
403Forbidden. The response body's `code` field disambiguates the specific failure: - `inbound_only_no_prior_inbound` — the sender is on the Inbound plan and the recipient has never messaged this number first. Inbound numbers are reply-only. Body also includes `allocation_id` and `external_id`. - `conversation_content_restricted` — messaging safety: links, media, and attachments are not allowed before the recipient's first written reply (or in a re-engagement of an inactive conversation). An emoji reaction does not unlock them. Send plain text first. - `safety_new_conversations_paused` — messaging safety: brand-new conversations from this number are temporarily paused; replies to existing conversations are unaffected. - `safety_reply_only` — messaging safety: outbound from this number is restricted to conversations with prior inbound. - `safety_account_review` — messaging safety: the number is blocked pending account review. - Emergency number - Integration-assigned number that can't be sent from manually Number-level safety actions (`safety_*`) clear automatically as the underlying sending pattern ages out.
404Not found. Threaded-reply failures use the response body's `code` field: - `reply_target_not_found` — `reply_to.message_id` did not match any message in this organization. - Other 404s (e.g., chat not found) have no `code`.
429Too many requests. Possible `code`s on this endpoint: - `outbound_limit_reached` — org-level new-contact cap (configured in Settings) tripped. Body includes `limit`, `current`, `mode`, and on per-number mode `allocation_id` + `sender_number`. - `new_conversation_limit_reached` — shared plan's daily new-conversation cap reached. Body includes `plan_id`, `cap`, `current`. Existing conversations continue to send. - `conversation_awaiting_reply` — messaging safety: up to 3 messages may be sent to a new recipient before they respond. Body includes `pre_reply_outbound_count` and `limit`. - `conversation_streak_limit` — messaging safety: consecutive-message cap since the recipient's last response was reached. Body includes `current_streak` and `limit`. - `conversation_inactive_paused` — messaging safety: no response in 14+ days and the single re-engagement message was already sent. Conversation-state limits lift as soon as the recipient responds, and an emoji reaction counts: a tapback clears the consecutive-message counter and keeps a thread from going inactive. Only writing back raises the cap and unlocks links, media, and attachments.
503No active number available

Sends a live request with your values and shows the real response below. Your key is stored only in this browser.

Request
curl -X POST https://api.blooio.com/v2/api/chats/chat_a1b2c3d4/messages \
Body object
Response objectexample
{  "message_id": "msg_a1b2c3d4",  "message_ids": [    "string"  ],  "count": 3,  "status": "queued",  "group_id": "grp_a1b2c3d4",  "group_created": false,  "participants": [    "string"  ],  "parent_unresolved": false}