API reference
Base URL, request and response conventions, and the shape of every Mapier /v1 endpoint.
The Mapier API is a small HTTP surface over iMessage. You queue commands,
and you read the results back on a single server-sent events stream. There
are four endpoints and no client library — every example in these docs is a
plain fetch or curl you can paste into a terminal.
Base URL
Every example in these docs reads the host from an environment variable:
export MAPIER_BASE_URL="https://<the host issued with your credentials>"Every path is prefixed with /v1. The version is part of the path; there is no
header or query-parameter negotiation, and a future /v2 would sit alongside
/v1 rather than replacing it in place.
Unconfirmed
There is no stable public hostname yet. During preview the host is issued to you along with your credentials, and it will change when the production environment lands — so keep it in configuration rather than hard-coding it. These docs deliberately do the same.
The shape of every request
curl $MAPIER_BASE_URL/v1/commands/message.send \
-H "Authorization: Bearer $MAPIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"target": { "kind": "recipient", "recipientExternalId": "+14155550100" },
"payload": { "text": "hello" }
}'Three things are always true of a command request:
- It is a
POSTwith a JSON object body. Anything else — an array, a string, a number — is rejected withbody_not_object. - The body has exactly two top-level keys:
target(who) andpayload(what). Both must be objects. See Addressing a conversation. - The body is at most 65,536 bytes. Larger is rejected with
body_too_largebefore any parsing happens.
See Authentication for how requests are identified.
The shape of every response
Successful command submissions return 202 Accepted:
{
"commandId": "3f1a0c2e-8b47-4d19-9a5e-2c6f0b8d4e71",
"status": "queued",
"disposition": "queued"
}Prop
Type
Errors are a bare JSON object with a single error key:
{ "error": "conversation_not_found" }A few carry one extra field — unknown_command adds command,
rate_limited adds retryAfter, and handle_check_unavailable adds reason.
There is no envelope, no message, and no request id. See
Error codes for the complete list.
Unconfirmed
The error body has no stable envelope yet. If you are writing a client, key on the HTTP status and
the error string, and tolerate additional fields appearing alongside them.
202 does not mean it worked
This is the single most important thing to understand about this API.
A 202 means the command was durably queued, not that it was delivered.
The command is dispatched to a Mac, executed against iMessage, and settles
asynchronously — success or failure arrives on the
response stream as a command event, typically within
a few seconds.
Request payloads are not validated at the API boundary. A malformed payload is accepted with
202 and fails later with a failed settlement. If you are testing a new payload shape, watch
the stream — the HTTP response will not tell you it was wrong.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /v1/commands/:type | Queue a command |
GET | /v1/response_stream | Settlements and inbound messages (SSE) |
GET | /v1/attachments/:id | Download an inbound attachment |
POST | /v1/handle-check | Test whether an address is reachable on iMessage |
Conventions
Identifiers. Phone numbers are E.164 (+14155550100). Email addresses are
lowercase and NFC-normalised. Conversation ids are Mapier UUIDs, not iMessage
GUIDs — you get them from inbound message events.
Timestamps. ISO 8601 with a Z suffix. occurredAt on an inbound message
is when iMessage says it was sent, not when Mapier received it.
Forward compatibility. Fields are added to responses and events without a
version bump. Parse defensively: ignore keys you do not recognise, and treat
enum values you do not recognise — especially errorCode — as unknown rather
than impossible.