Error codes
Every HTTP error code and every asynchronous settlement code, with what to do about each.
Errors reach you in two places, and the difference matters.
Synchronous errors are HTTP responses to your request. The command was
never queued. The body is { "error": "<code>" }.
Asynchronous errors arrive later on the response
stream as a command event with status: "failed" and
an errorCode. The command was queued, dispatched, and then something went
wrong on the way to iMessage.
A client that only handles the first kind will believe every message was delivered.
HTTP errors
400 — the request was malformed
| Code | Meaning | What to do |
|---|---|---|
invalid_url | The request target could not be parsed. | Fix the URL. |
body_too_large | Body exceeded 65,536 bytes. | Shorten the payload. Text itself is capped at 64 KiB. |
body_not_object | Body parsed as JSON but was not an object. | Send {...}, not an array or scalar. |
target_and_payload_required | target or payload missing, or not an object. | Include both as objects. payload may be {} where the command takes none. |
invalid_conversationId | conversationId is not a UUID. | Use the conversationId from an inbound message event, not an iMessage GUID. |
recipient_send_requires_recipientExternalId_and_text | A recipient-target send was missing an address or text. | Recipient sends accept { "text": "..." } only — no rich kinds. |
group_create_requires_new_group_target_and_firstMessage | group.create had the wrong target kind or no firstMessage. | Use a new_group target and a non-empty firstMessage. |
group_command_requires_group_conversation | A group verb was aimed at a DM. | Group commands need a group conversationId. |
invalid_id | Attachment id could not be decoded. | Use an id from an inbound message's attachmentIds. |
invalid_address | Handle-check address failed validation. | E.164 (+14155550100) or a lowercase email. |
401 — not authenticated
| Code | Meaning | What to do |
|---|---|---|
unauthenticated | No credential was presented. | See Authentication. |
404 — not found
| Code | Meaning | What to do |
|---|---|---|
unknown_command | The :type segment is not a known command. Body includes command. | Check the spelling against the command list. |
conversation_not_found | No such conversation, or it belongs to another account. | The two cases are deliberately indistinguishable. Verify the id came from your own stream. |
not_found | No such attachment, or it belongs to another account. | Same reasoning as above. |
no_connector | Your account has no Mac connector. | Contact Mapier — this is provisioning, not something you can fix in code. |
A request to a path that matches no route returns a bare 404 with no body.
405 — wrong method
| Code | Meaning | What to do |
|---|---|---|
method_not_allowed | Right path, wrong verb. | Commands and handle-check are POST; the stream and attachments are GET. |
422 — rejected by validation
| Code | Meaning | What to do |
|---|---|---|
| (varies) | The message from an internal validation failure, e.g. a malformed recipient address on a cold send. | Read the error string; it describes the specific failure. |
Unconfirmed
422 fires only for validation performed inside the queueing layer — mostly address grammar. It
does not fire for a malformed command payload, which is accepted with 202 and fails
asynchronously instead. This is sometimes described as 422 as the general "invalid payload"
response; that is not what the service does.
429 — rate limited
| Code | Meaning | What to do |
|---|---|---|
rate_limited | Too many requests. Body includes retryAfter in seconds; a Retry-After header carries the same value. | Wait, then retry. See Rate limits. |
500 — server error
| Code | Meaning | What to do |
|---|---|---|
internal | Persistence or an unhandled failure. | Retry with the same Idempotency-Key. If it persists, contact Mapier. |
503 — temporarily unavailable
| Code | Meaning | What to do |
|---|---|---|
no_live_connector | No Mac currently holds a live lease. | Transient. Retry with backoff. |
connector_unavailable | The connector is not reachable. | Transient. Retry with backoff. |
outbound_paused | Sending is administratively paused. | Do not retry in a tight loop; this clears operationally. |
stream_unavailable | The response-stream watcher is not configured. | Contact Mapier. |
handle_check_unavailable | Handle check could not run. Body includes reason. | See the reason table on handle check — all reasons are retry-later except invalid_target. |
Settlement error codes
These appear as errorCode on a command event with status: "failed", and
occasionally on ambiguous or no_effect. Grouped by what you should do.
Fix the request and resend
errorCode | Meaning |
|---|---|
invalid_target | The target does not resolve on the Mac. |
stale_target | The referenced message or participant set no longer matches. Common after a group's membership changes — wait for a new message in the group, then use the fresh conversationId. |
payload_conflict | The payload contradicts the recorded command identity. |
unsupported_capability | The Mac does not support this operation. |
capability_not_negotiated | The capability was not agreed at handshake. |
capability_constraint_violation | The capability exists but a parameter is out of its declared bounds. |
invalid_session_capabilities | The session advertised an unusable capability set. |
Retry — the effect did not happen
errorCode | Meaning |
|---|---|
gateway_rejected | iMessage refused the operation. |
send_returned_not_ok | The send call returned a failure. |
preparation_dispatch_exhausted | Dispatch attempts were exhausted before execution. |
command_claim_invalid | The claim was not valid when execution began. |
stale_fence | A newer session superseded this one. |
expired | The command passed its deadline before dispatch. |
outbound_paused | Sending was paused before the command ran. |
Investigate — the effect may or may not have happened
These settle ambiguous more often than failed. Do not blindly resend;
you may duplicate a message that was in fact delivered.
errorCode | Meaning |
|---|---|
accepted_echo_unconfirmed | iMessage accepted the send but no echo confirmed it. |
connector_crash_after_effect_start | The connector died mid-effect. |
gateway_timeout | No answer within the deadline. |
gateway_response_lost | The response was lost in transit. |
reaction_unverified · tapback_unverified | The reaction could not be verified afterwards. |
poll_vote_unverified | The vote could not be verified. |
group_content_unverified · group_update_unverified · group_leave_unverified | The group operation could not be verified. |
moderation_unverified | The moderation action could not be verified. |
journal_corrupt | The durability journal was unreadable. |
Not an error
errorCode | Meaning |
|---|---|
operator_cancelled | A human cancelled the command. |
Unconfirmed
This list is assembled from the service's source rather than a published registry, and it is not a
closed set. Treat an unrecognised errorCode as a generic failure rather than assuming it
cannot occur — new codes can appear without a version change.
Retry rules of thumb
429— honourRetry-After.503— exponential backoff; these are genuinely transient.500— retry once with the sameIdempotency-Key, then escalate.- Any other
4xx— do not retry. The request will fail identically. - A
failedsettlement — consult the tables above. Only the first two categories are safe to resend, and only with a new idempotency key if you changed the payload.
Retries are only safe from double-sending if you supply an Idempotency-Key
and the command uses the idempotent routing path. See
Idempotency and retries — this is a real trap, and
it does not apply to every command.