Conversations
A conversation is a thread between one of your numbers and a contact (or a group). Threads are created for you automatically on the first message in either direction — you only create one explicitly to start an iMessage group chat.
Browse threads
GET/v1/chats
GET/v1/chats/:id
The list paginates newest-first with limit (1–100, default 50) and before (ISO timestamp), returning { data, has_more, request_id } where each item in data is a chat object:
{
"id": "7f2c9e1b-…",
"from_handle": "+18005551111",
"remote_handle": "+15555550123",
"is_group": false,
"title": null,
"last_message_at": "2026-06-11T18:21:04.000Z",
"created_at": "2026-06-10T02:11:40.000Z",
"updated_at": "2026-06-11T18:21:04.000Z"
}To fetch a thread’s messages, pass its id as conversation_id on GET /v1/messages.
Create an iMessage group
POST/v1/chats
curl -X POST https://api.msgbubbles.com/v1/chats \
-H "Authorization: Bearer sk_live_…" \
-H "content-type: application/json" \
-d '{
"from_handle": "+18005551111",
"addresses": ["+15555550123", "+15555550124"],
"message": "welcome to the group!",
"title": "Launch crew"
}'from_handle— one of your iMessage/SMS numbers. WhatsApp numbers can’t create groups (400 invalid_from_handle).addresses— 2 to 31 recipients (2+ remotes is what makes it a group).message— required; groups are established by sending their first message.title— optional display name, up to 100 characters. Naming is best-effort: if it doesn’t take, the group is still created and comes back withtitle: null— set it afterwards withPATCH /v1/chats/:id.
Pass an Idempotency-Key header to make retries safe: a repeated key returns the original group with 200 instead of creating a second one with 201.
The opening message is delivered as part of creating the group, but it isn’t recorded as a message: it won’t appear in GET /v1/messages or fire message.delivered or message.read. Send follow-ups into the group with conversation_id on POST /v1/messages — those are tracked like any other send.
SMS and mixed groups aren’t supported
POST /v1/chats creates an iMessage group only. You can’t start an SMS/MMS group, or a mixed group that includes an Android member, from the API.
These are the same case underneath: a group is either all-iMessage or a group MMS thread. The moment one non-iMessage (Android, green-bubble) member is in a group, the whole thread drops to group MMS — every member, including the iMessage ones, is now on SMS/MMS. There’s no per-member split, so “SMS group” and “mixed iMessage/Android group” both mean a group MMS thread, which the create endpoint can’t originate. Putting an unreachable number in addresses mints an iMessage group that can’t reach that member, not an MMS group.
You can still take part in such a group once it exists: if someone adds one of your numbers to an existing group (including an SMS or mixed one), that thread shows up in GET /v1/chats and you can reply to it with conversation_id on POST /v1/messages. The limit is only on creating one.
Receiving in groups
Any group one of your numbers is part of — iMessage, SMS/MMS, or WhatsApp — is tracked automatically the moment a message arrives: the thread shows up in GET /v1/chats with is_group: true (and the group’s title when it has one).
Inbound group messages are easy to tell apart. The message.received webhook carries is_group: true, and from is always the individual sender — the group itself is conversation_id. Each message also records its sender in metadata (sender_handle for iMessage/SMS, sender_jid for WhatsApp), so every message in the thread stays attributable when you read it back with GET /v1/messages. Title changes arrive as the conversation.renamed webhook event.
Several of your numbers in one group
When more than one of your numbers is a member of the same group, each number gets its own conversation for that group (same thread, different from_handle) — like two of your email addresses cc’d on one thread. Each conversation records its own copy of every group message and fires its own webhook events: one message.received per member number, distinguished by conversation_id and to. Receipts, edits, and reactions likewise arrive per conversation. Dedupe by conversation_id if you only want one copy.
WhatsApp groups
A WhatsApp group your number belongs to shows up in GET /v1/chats as soon as a message arrives in it. From then on you can reply with conversation_id on POST /v1/messages and react to its messages. You can’t create a WhatsApp group, rename one, or change its members from the API. Numbers on the WhatsApp Cloud API don’t support groups at all — a send into a group from one returns 400 unsupported.
Manage a group
PATCH/v1/chats/:id
Rename: { "title": "New name" }.
POST/v1/chats/:id/participants
DELETE/v1/chats/:id/participants/:handle
Add with { "handle": "+15555550125" }; remove by handle in the path. These work on iMessage groups only: a one-to-one thread or a WhatsApp group returns 409 not_a_group, and group MMS threads can’t be renamed or have their members changed. They run synchronously — the response is the messaging network’s verdict.
Read receipts
POST/v1/chats/:id/read
Marks the thread read on your side, so the people messaging you see read receipts. Works for iMessage and WhatsApp threads.
Typing indicators
POST/v1/chats/:id/typing
{ "typing": true } shows the typing bubble on the recipient’s side; false hides it. It auto-expires on the recipient’s device, so don’t worry about cleanup if your process dies mid-conversation. Inbound typing from your contacts arrives as the conversation.typing webhook event.