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.sendon a live iMessage line's recipient path does not accept an email address: it answers422 validation_failedat the target field you wrote it at (target.recipientExternalId, or whichever of its spellings you used), and attarget.conversationIdwhen 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-checkaddresstoo. 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:
textalone, with an effect refused atpayload.effect, a subject atpayload.subject, a reply attarget.replyToMessageId, and anykind— a photo, a sticker, a rich link, a multipart, a location request or a poll — at the recipient key withreason: conversation_not_confirmed— each422 validation_failedrather 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, withinvalid_textas the refusal'sreason.
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;
lineSelectionisnamed. - 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 is422 validation_failedattarget.lineIduntil a message has. - Into a
conversationId: a conversation stays on its line; a differentlineIdbeside it is422 validation_failedattarget.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
| Kind | Canonical form | Example | Also read, and normalised on an iMessage line |
|---|---|---|---|
| Phone | E.164, leading +, 7–15 digits | +14155550100 | 4155550100 and (415) 555-0100 — a bare ten digits is US |
| Lowercase | person@example.com | Person@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.
Related
The console
app.mapier.ai — where keys are minted, lines are requested, the sandbox lives, and every event and command is on the record.
Projects with several lines
How a project with many numbers decides which one speaks, how to choose one yourself, and which number to show somebody who will text you first.