Messages

Sending messages

One command, the payload shapes a line takes, and the ones it carries.

Everything you send — a plain sentence, a link card, a photo, a poll — goes through one command: POST /v1/commands/message.send.

The body is always two objects. target says where the message goes, payload says what it is.

{
  "target": {
    "kind": "conversation",
    "conversationId": "a0000000-0000-4000-8000-000000000001"
  },
  "payload": { "text": "Your table is ready." }
}

A call the API accepts returns 202 with an id you can correlate later:

202 Accepted
{ "commandId": "cmd-1", "status": "queued", "disposition": "queued" }

202 means queued, not delivered

The 202 tells you the command was durably queued and nothing more — the outcome settles on the response stream. What a line cannot carry never gets that far: a payload key it does not take that carries a value, a kind included, is refused with 422 validation_failed at payload.<key> before anything is queued.

The API checks the body before anything is queued, contents included. A body over 65,536 bytes is 413 payload_too_large, and one that is not JSON is 400 bad_request. A body, target or payload that is not an object is 422 validation_failed, and so is a target that names nobody (details points at target.recipientExternalId), a payload with no text (payload.text) and a payload key the line does not carry (payload.<key>).

The payload shapes

What a payload may carry is decided per channel, by the line the send resolves to. An iMessage line, live or test, carries plain text — with an effect and a subject beside it on a conversation the contact has written into, and textFormatting for bold, animated or mention ranges — and six kinds in place of the text: a photo or file by link (media), an emoji sticker, a rich_link, a multipart — text and photos as one message — a location_request and a poll.

kindWhat it sendsTargetWhere it can go
omittedPlain text — with effect and subject on a confirmed conversation or a group chatconversation or recipientAny thread
mediaA photo or file by https linkconversationInto a confirmed conversation or a group chat
rich_linkA URL that Messages renders as a preview cardconversationInto a confirmed conversation or a group chat
stickerAn emoji or an uploaded image, optionally placed on a messageconversationThe emoji, or an image by link, into a confirmed conversation or a group chat
multipartText and photos as one message, in the order givenconversationInto a confirmed conversation or a group chat
location_requestApple's "Share My Location?" cardconversationInto a confirmed conversation
pollA question with 2 to 12 choicesconversationInto a confirmed conversation or a group chat

See Stickers, Rich links and What a line carries.

Two ways to address a send

message.send accepts two of the three target kinds. The target says who the message is for; it does not decide how the message travels.

A conversation target replies into a thread you already know about. Its id comes from an inbound message event on the stream.

A recipient target opens a new thread from an address.

On a live iMessage line the thread, not the target, chooses the path. A thread the transport has confirmed is with that one contact — it confirms one the first time the contact writes into it — or has reported as a group chat takes the conversation path; into a group chat a message reaches every member, and every kind goes but a location request. That path carries every kind in the table, and an effect, a subject, textFormatting and a quoted reply; a command on it that the line has not taken within 30 seconds settles expired. Every other thread takes the recipient path, whichever target named it. That path lets a command wait five minutes, and it:

  • carries plain text alone — an effect, a subject or textFormatting beside it is refused with 422 validation_failed at payload.<key>, a kind as described below, and a reply at target.replyToMessageId, before anything is written;
  • refuses a text that is blank or over 16,384 bytes: 422 validation_failed, with invalid_text as the entry's reason;
  • sends to a phone number in E.164 form, never an email address: an email is 422 validation_failed at the target field you wrote it at (target.recipientExternalId, or whichever of its spellings you used), and at target.conversationId when it is the peer of a thread you named that takes this path. A group conversation the transport has not named yet is refused at target.conversationId too.

In test an email address sends. A media link, a sticker, a rich link, a multipart, a location request or a poll into a thread the transport has neither confirmed nor reported as a group chat is 422 validation_failed with reason: conversation_not_confirmed, at target.conversationId or the recipient key you wrote: send the person a text first. A location request into a group chat is refused the same way, and no text changes that: send it into a member's own one-to-one conversation.

Both targets honour Idempotency-Key: the platform records every send's key before the line is asked, so a retried send is never delivered twice. It is adopted when a transport took the first attempt, and refused with 409 conflict when none did — see Idempotency.

The usual pattern is to open with a plain recipient send, wait for the conversation to appear on the stream, then use its conversationId for everything after. See Addressing a conversation.

Pick a payload

On this page