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 one envelope on every route:

{ "error": { "code": "<code>", "message": "<a sentence for a person>", "details": … } }

Switch on code — a closed set, each value mapped to exactly one HTTP status. On a 422 validation_failed, details is an array of { path, message, … } entries, one per field. On every other refusal that has details, it is an object whose reason says which refusal this is — the 403, 404, 409 and 503 below all carry one.

Asynchronous errors arrive later on the response stream as a command event with status: "failed" and an errorCode, or as a message.failed webhook. 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

Every code a /v1 route can answer with, and the status it answers with it. The OpenAPI document lists, per operation, exactly which of these that operation can produce.

400 — the request was malformed

CodeMeaningWhat to do
bad_requestThe body is not JSON, or the transport refused the request as malformed — details.reason is then its own word. A body that is JSON but not an object, and a command that names nobody, are 422 instead.Send valid JSON: { "target": {…}, "payload": {…} }.

401 — not authenticated

CodeMeaningWhat to do
invalid_credentialsNo key, a malformed key, an unknown key, a revoked key or an expired one — deliberately one code for all five.See Authentication. Failed authentications are budgeted per calling address, 30 a minute.

403 — refused

CodeMeaningWhat to do
line_not_allowedtarget.lineId is not a line of this project in the environment of the key — another project's, the other environment's, or nobody's; the API does not say which, because a key's lines are exactly the list GET /v1/whoami hands it. details.reason is not_owned and details.lineId the id sent.Pick a line from GET /v1/whoami, or leave target.lineId out.
recipient_not_allowedThe contact cannot be sent to, and details.reason says why: opted_out (they sent STOP or UNSUBSCRIBE, and every send is refused until they opt back in), blocked or paused (set on the contact in the console), or outside_service_window (a channel whose window for free-form messages has closed; details.channel and details.serviceWindowExpiresAt say which and when). details.contactId names them. It is never about the address's form: an address a line cannot send to is a 422.Do not retry an opted_out. Honouring it is the platform's job and yours.

404 — not found

CodeMeaningWhat to do
unsupported_commandThe :type segment is not a command the gateway accepts — or it is one the line's channel never carries, which says details.reason: not_on_channel with the channel and the capability it lacks.Check the spelling against the command list. Contrast failed / unsupported_capability on the stream, which is a type the channel carries and this line cannot execute.
not_foundNo such conversation, message or attachment — or it belongs to another project or the other environment.The cases are deliberately indistinguishable. Verify the id came from your own stream, with a key of the same environment.

409 — conflict

CodeMeaningWhat to do
project_archivedThe project this key belongs to has been archived in the console.Restore it, or use a key from a live project.
conflictEither the Idempotency-Key is taken and there is nothing to adopt — details.reason says how: idempotency_key_in_flight (the first attempt is still running); idempotency_key_failed (it was a message.send no transport took — it was refused, and nothing was sent); idempotency_key_unresolved (a message.send whose outcome is unknown — its message is needs_reconcile, or its attempt died before any answer and has not been reconciled yet); or idempotency_key_reused (the key already named a different command — in test, and on a live line depending on the type and the transport, where it may instead be adopted as the first) — or the transport refused the command for a reason of its own, such as group_has_no_messages_yet: a member change into a group with no message yet — send a message into it first. With failed and unresolved, details.messageId is the message the first attempt wrote and details.errorCode the code it settled with (null until it is reconciled), and failed adds details.failureClass. The reason is an open string: a transport may supply its own, and when it refused the command only for now it adds details.retryAfter, the seconds to wait before trying it again.In flight: wait, then send again with the same key. Failed: send again under a new key. Unresolved: check the conversation before any resend — repeating it could deliver it twice. Reused: send the original body, or use a new key.

413 — too large

CodeMeaningWhat to do
payload_too_largeThe body exceeded 65,536 bytes. Refused before the key is checked.Shorten the payload; attachments are sent by reference.

422 — rejected by validation

CodeMeaningWhat to do
validation_failedA field is wrong, and details says which: [{ "path": "target.recipientExternalId", "message": "…" }]. On message.send that includes, in both environments, a payload key the line does not carry and a missing text (payload.<key>), and on a live iMessage line's recipient path an email address (the target field you wrote it at, or target.conversationId for a thread on that path whose peer is one). On an iMessage line, live or test, it includes a reaction.tapback or name_photo.share into a conversation the transport has not named (target.conversationId), a tapback naming by messageId a message the line sent or another conversation holds (payload.targetMessageExternalId), and a group.create whose participants the channel cannot take or number outside 2 to 32 (target.participantExternalIds, or target.participantExternalIds[i] for one the channel cannot read). On every command it includes an Idempotency-Key outside its rule (Idempotency-Key) and an unknown x-mapier-version, whose entry lists the versions this build answers to in supported. A transport's own refusal carries its word in the entry's reason. Other commands' payloads are checked where they run — see What is checked before anything is queued.Fix the field details names.

429 — rate limited

CodeMeaningWhat to do
rate_limitedToo many requests for this route's budget — your project's command ceiling (per environment) on POST /v1/commands/{type}, this key's budget on the other routes. A Retry-After header says when the window reopens.Wait, then retry. See Rate limits.
sandbox_budget_exhaustedThe test world's daily action budget is spent.Wait for the UTC day to roll over, or reset the world. Test only.

500 — server error

CodeMeaningWhat to do
internal_errorAn unhandled failure inside the service. It has been reported.Retry with the same Idempotency-Key — not a live group.create, typing.start or message.read, which ignore it. If it persists, send it in.

503 — temporarily unavailable

CodeMeaningWhat to do
no_lineThe project has no active line in this environment.Request a line from the console's Lines page.
no_line_capacityEvery line is at its contact capacity. details.reason is all_lines_full.Request another line. A new contact needs a slot; existing ones are unaffected.
capacity_exhaustedThe same refusal as no_line_capacity, in the transport-neutral words a request at x-mapier-version: 2026-10-15 or later receives instead. You get one of the two, never both.As for no_line_capacity.
line_unavailableThe line cannot send right now; details.reason says why: line_paused (with details.lineId), line_released (with details.lineId, or details.conversationId when the thread's line has gone), relay_down, relay_unconfigured, channel_not_available or sandbox_world_missing. From 2026-10-15, line_retired, transport_down and transport_unconfigured replace line_released, relay_down and relay_unconfigured. The reason is an open string.line_paused and relay_down (transport_down) clear operationally — retry with backoff and the same key. line_released (line_retired) and channel_not_available do not: a released line's thread takes nothing more, so start a new one with the recipient.
upstream_unavailableThe transport behind the line did not take the command, or answered with something this platform could not use; details.reason carries the transport's own word — on an iMessage line connector_unavailable when the line's device is offline, outbound_paused when sending is paused — or timeout, network or status_5xx when no usable answer came back.Retry with backoff and the same key — except a live message.send, whose key is spent: it was recorded and settled before this answer, so a same-key retry sends nothing and answers 409 conflict (idempotency_key_failed, or idempotency_key_unresolved when no usable answer came back). Resend it under a new key, after checking the conversation when details.reason is timeout, network or status_5xx.
service_busyThe platform had no database connection free for this request within its own wait. The failure is ours and clears on its own, and nothing the request asked for was done. details.retryAfter (and Retry-After) says how many seconds to wait. Any route can answer it.Wait that long, then send the same request — repeating it is safe. See Rate limits.

Settlement error codes

These appear as errorCode on a command frame that did not settle succeeded — failed, cancelled or expired, and ambiguous or no_effect when the transport could not prove what the command changed — and on a message.failed webhook. errorCode is the transport's own word, verbatim: the tables below are an iMessage line's.

Switch on failureClass first

Beside every asynchronous errorCode rides a failureClass, from a closed set that is the same for every transport. Branch on it, and read errorCode for the detail or to quote to support. It is null unless the status is failed, cancelled or expired.

failureClassWhat it means
targetWho or what the command is aimed at cannot take it.
contentThe payload was refused — fix it, then send.
capabilityThis line cannot carry the command.
lineThe line cannot send right now.
rate_limitedWait, then send.
not_runIt never ran — sending again is safe.
transportThe transport failed on its side.
unknownRead errorCode.

The iMessage codes, grouped by what you should do:

Fix the request and resend

errorCodeMeaning
invalid_targetThe line could not find the target.
stale_targetThe referenced message is no longer in the history the transport holds for that conversation — it aged out, or the conversation the transport named is a group's and the message was sent one to one — or a group was addressed right after its membership changed, before its next message: wait for that message, then send again under a new Idempotency-Key.
payload_conflictThe payload contradicts the recorded command identity.
unsupported_capabilityThe line cannot carry out this command.
capability_not_negotiatedThe line's device has not announced this capability. See Capabilities.
capability_constraint_violationThe line cannot carry this command as sent: a value outside its bounds, or a feature its device has not announced.
invalid_session_capabilitiesA fault on the line's side, not in your request.
command_claim_invalidThe change does not apply to the target as the line last saw it — for a group.update, an add of someone already in the group or a remove of someone who is not.

Retry — the effect did not happen

errorCodeMeaning
gateway_rejectediMessage refused the operation.
send_returned_not_okThe send call returned a failure.
preparation_dispatch_exhaustedThe command could not be handed to the line before the platform gave up.
stale_fenceThe line was interrupted before it ran this command.
expiredThe command passed its deadline before the line took it.
command_expired_before_executionThe command's time ran out before it ran; nothing changed. The usual companion of an expired status.
outbound_pausedSending was paused before the command ran.

Investigate — the effect may or may not have happened

These arrive on a command frame whose status is ambiguous more often than failed — and a send they end leaves its message needs_reconcile, the message.needs_reconcile webhook. Do not blindly resend; you may duplicate a message that was in fact delivered.

errorCodeMeaning
outbound_echo_unconfirmediMessage accepted the send, but nothing confirmed it went out.
connector_crash_after_effect_startThe line's device stopped mid-effect.
gateway_timeoutNo answer within the deadline.
gateway_response_lostThe response was lost in transit.
tapback_unverified · poll_vote_unverifiedThe reaction or vote could not be verified afterwards.
group_content_unverified · group_update_unverified · group_leave_unverifiedThe group operation could not be verified.
chat_background_unverifiedA chat background change began and could not be read back, and this API cannot read one back. Ask someone in the conversation.
moderation_unverifiedAn edit or an unsend could not be verified.
name_photo_set_unverifiedA name/photo set started or may have started, and the read-back did not prove it. hasPhoto is not that proof. Do not retry blindly.
journal_corruptThe line lost its record of the operation, so the outcome is unknown.

Not an error

errorCodeMeaning
operator_cancelledA human cancelled the command.

This list 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 — honour Retry-After.
  • 503 service_busy — honour retryAfter, then send the same request.
  • Any other 503 — exponential backoff and the same key, except after a live message.send's upstream_unavailable: that key is spent, and the resend needs a new one. See Handling errors.
  • 500 — retry once with the same Idempotency-Key, then escalate.
  • 409 conflict with idempotency_key_in_flight — wait, then retry with the same key. The other three reasons are not worth repeating as they stand; the 409 table says what each needs.
  • Any other 4xx — do not retry. The request will fail identically.
  • A failed settlement — 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 sends nothing: it is adopted, returning 202 with the original commandId — or, for a message.send no transport ever took, refused with 409 conflict.

Retries are only safe from double-sending if you supply an Idempotency-Key and send the same one on every attempt — and never for group.create, typing.start or message.read on a live line, which ignore it. See Idempotency and retries — this is a real trap, and it does not apply to every command.

On this page