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 |
|---|---|---|
recipient_not_allowed | A recipient-target send addressed something that is not an E.164 phone number. | Use a phone number, or send into a conversation target instead. |
invalid_participants | group.create had fewer than two participants, or a duplicate. | Deduplicate the list and check it has at least two entries. |
invalid_text | A recipient send's text or a group.create firstMessage was empty or over 16,384 bytes. | Trim, and split anything longer across several messages. |
422 fires only for the handful of checks the queueing layer performs on admin-path commands —
address grammar, participant lists and those two text fields. It does not fire for a malformed
command payload, which is accepted with 202 and fails asynchronously instead. This is
sometimes described as 422 being 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, send it in. |
503 — temporarily unavailable
| Code | Meaning | What to do |
|---|---|---|
no_live_connector | No Mac holds a live lease. The agent-turn path's wording for it. | Transient. Retry with backoff. |
connector_unavailable | The same condition on the admin path — the two paths report it under different names. | 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. No reason is a verdict on the address. |
Settlement error codes
These appear as errorCode on a command event with status: "failed", and
also on ambiguous, cancelled and expired. 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. |
command_expired_before_execution | The expiry sweep terminated it. The usual companion of an expired status. |
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 |
|---|---|
outbound_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 always under a new idempotency key, whether or not you changed the payload. A key stays bound to its command once that command has settled, so a same-key resend is adopted, returns202carrying the old terminal status, and sends nothing.
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.