Addressing a conversation

The target shapes, and how to get the conversation id you need.

Every command has a target that says who it acts on. Most commands use one of three shapes. name_photo.get and name_photo.set are the exception — they address the line itself — and message.edit and message.unsend add target.messageId to a conversation target. A recipient or new-group target may also say which of your lines speaks, at target.lineId (see Choosing the line below). Picking the right shape is usually the first question you answer.

Each shape below carries a kind, and /v1 does not read it: the keys make the target. A conversationId makes a conversation target and an address a recipient target, whatever kind says, and group.create reads its participant list. Keep kind if it helps you read your own requests.

conversation — reply into an existing thread

The one you will use most.

{ "kind": "conversation", "conversationId": "a0000000-0000-4000-8000-000000000001" }

conversationId is a Mapier UUID, not an iMessage GUID. You get it from the message event on the response stream:

{
  "conversationId": "a0000000-0000-4000-8000-000000000001",
  "from": "+14155550100",
  "text": "is it here yet?",
  "externalId": "guid-1",
  "replyToExternalId": null,
  "occurredAt": "2026-08-24T18:03:11.000Z",
  "attachmentIds": [],
  "attachments": [],
  "messageId": "0192a7c4-6a02-7e11-8c4d-2b9f7e3a5d22"
}

Store that id against your own records — a customer, a ticket, a thread — and you can reply at any time. A conversation's id is stable for the life of the thread. A group chat is a conversation too — see Group chats below.

On an iMessage line, live or test, a reaction.tapback or a name_photo.share takes a conversation the transport has named — which it does when the contact first writes into it — and is refused with 422 validation_failed at target.conversationId otherwise. Each reference page says what its command takes.

A chat_background.set or a chat_background.remove takes a conversation with one person that the transport has named and also confirmed is with that one person — which it does when it reports the conversation, the first time it sees it — or a group chat it reported. A conversation with one person it has not yet confirmed is 422 validation_failed at target.conversationId with reason: conversation_not_confirmed: send the person a text first.

A conversation target is on its thread's channel, so it needs no channel of its own. A target that names an address instead — a recipient, or a new_group's participants — should name the channel it sends on, as every example below does: from x-mapier-version 2026-10-01 a target that names none is resolved from your project's lines, and a project holding lines on more than one channel refuses it with 422 validation_failed at target.channel.

A conversation id from another project or environment returns 404 not_found — the same response as an id that does not exist. If you are getting this unexpectedly, check the id came from your own stream.

recipient — start a new thread

When you have a phone number but no conversation yet.

{ "kind": "recipient", "channel": "imessage", "recipientExternalId": "+14155550100" }

Prop

Type

Two limits apply on a live iMessage line, and both surprise people.

  • Phone numbers only, until the contact answers. Unlike every other place the API names a person, a message.send on a live iMessage line's recipient path does not accept an email address: it answers 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 the recipient path, with nothing written. On the conversation path a message reaches the contact at whatever address the conversation is with, an email address included. Email handles work as group participants and as a handle-check address too. In test an email address sends.
  • Text alone, until the contact answers. A recipient send opens a thread the transport has neither confirmed nor reported as a group chat, so it takes the recipient path: text alone, with an effect refused at payload.effect, a subject at payload.subject, a reply at target.replyToMessageId, and any kind — a photo, a sticker, a rich link, a multipart, a location request or a poll — at the recipient key with reason: conversation_not_confirmed — each 422 validation_failed rather than delivered without it. See What a line carries. A text that is blank or over 16,384 bytes — a quarter of what the body allows — is refused too, with invalid_text as the refusal's reason.

Idempotency is not one of them. A recipient send honours Idempotency-Key like a conversation send: the platform records every message.send against its key before the line is asked, so a retry with the same key is never sent twice — it is adopted when a transport took the first attempt, and refused with 409 conflict when none did. The transport behind the recipient path does not honour the key itself, so a send whose answer was lost leaves its message needs_reconcile rather than being sent again.

The usual pattern is therefore: send a plain text opener, wait for the conversation to appear on the stream, then use its conversationId for everything after.

Check reachability first if you are unsure the address is on iMessage — POST /v1/handle-check answers synchronously and saves you a message that goes nowhere.

new_group — create a group

Only group.create accepts this.

{
  "kind": "new_group",
  "channel": "imessage",
  "participantExternalIds": ["+14155550100", "+14155550111"]
}

Between 2 and 32 other people, each either an E.164 phone number or a lower-case Apple ID email of at most 512 characters — unlike a message.send on a live iMessage line, email is accepted here. participants is read as the same list. Do not include yourself. Order does not matter.

The platform checks the list before anything is sent, in test and live alike. It reads each participant the way the channel reads an address and sends it in that canonical form, counting the same person written two ways once. A participant the channel cannot read, the first included, is 422 validation_failed at target.participantExternalIds[i] — or at the key and index you wrote, such as target.participants[i] — and a list of fewer than 2 or more than 32 people, once repeats are counted once, is 422 validation_failed at target.participantExternalIds. See group.create for every case.

lineId — choosing the line

A project can hold several lines, and one key sends from all of them. By default you never choose: a person is answered on the number they last wrote to (the whole order is in Replying), and somebody new gets the line the platform picks — the one whose connection answers, then the one with the fewest new people today, then the one with the most room. Every 202 tells you which line spoke and why, as lineId and lineSelection (named, reused_active_chat or new_best_line).

To choose, name the line beside a recipient or a participant list, by its id as GET /v1/whoami lists it under lines[]. Naming the line names the channel too; channel is still written in every sample here, as the quickstart writes it, so a sample copied into a project with lines on two channels keeps working when the lineId is taken out.

{
  "kind": "recipient",
  "channel": "imessage",
  "recipientExternalId": "+14155550100",
  "lineId": "0192a7b0-3e4f-7051-8c6d-7e8f9a0b1c2d"
}

What a named line does, in order:

  • Somebody new: they are reached from that line, and stay on it.
  • Somebody already on that line: nothing changes; lineSelection is named.
  • Somebody on another line: the send goes out on the line you named, and a second conversation with them opens there, beside the one they already have. Their first line does not move. A later send that names no line goes out on the number they last wrote to, so on this one only once they answer here. That person now sees two numbers from one business, which Apple reads as spam, so do this on purpose and sparingly. A command done to a thread — a tapback, a read, typing.start — cannot open that second conversation: it is 422 validation_failed at target.lineId until a message has.
  • Into a conversationId: a conversation stays on its line; a different lineId beside it is 422 validation_failed at target.lineId.

Refusals, all before anything is written: an id that is not one of your lines — another project's, the other environment's, or nobody's — is 403 line_not_allowed (details.reason: not_owned, details.lineId the id you sent); a line on another channel than target.channel names is 422 validation_failed at target.lineId; a line that is paused, released, full or whose connection is down is refused with the same 503 a send to that line gets.

The line itself

name_photo.get and name_photo.set address the line and nobody else: the target is {lineId} alone.

{ "lineId": "0192a7b0-3e4f-7051-8c6d-7e8f9a0b1c2d" }

An id that is not one of your lines is 403 line_not_allowed there too. A conversation id does not reach the line's name and photo.

Address formats

KindCanonical formExampleAlso read, and normalised on an iMessage line
PhoneE.164, leading +, 7–15 digits+141555501004155550100 and (415) 555-0100 — a bare ten digits is US
EmailLowercaseperson@example.comPerson@Example.com

An address that is neither is rejected with 422 validation_failed, on handle check and on a send alike. Email is accepted everywhere a handle appears except a message.send on a live iMessage line, which takes phone numbers only, on either target.

Group chats

An iMessage group chat is a conversation too. A message someone writes in a group arrives under the group's own conversationId, with isGroup: true, and a message.send to that id goes on the transport's conversation path and reaches every member. A group conversation the transport has not named yet is 422 validation_failed at target.conversationId.

group.update and group.leave take only a conversation the transport reported as a group, and refuse any other with 422 validation_failed at target.conversationId. group.create's settlement carries a null conversationId: it opens the thread the transport then reports. A test line has no group chat. See Groups.

On this page