Errors
The error body, what each HTTP status means, the upper-case codes that appear in the message text, and which failures are safe to retry.
The error body
Every error returns JSON with success set to false and a human-readable message:
{
"success": false,
"message": "Missing scope(s): contacts.write. This integration was granted: contacts.read"
}Rate-limit errors add an error field:
{
"success": false,
"message": "Daily rate limit exceeded: 200,000 requests per day (resets 00:00 UTC).",
"error": "RateLimitExceeded"
}Branch on the HTTP status first. Where a route can fail in more than one way with the same status, the message text names the case.
Status reference
| Status | Meaning | What to do |
|---|---|---|
| 400 | Validation. A field is missing, malformed or not allowed on this route. | Fix the request. The message names the field or the rule. |
| 401 | Invalid or expired access token. | Refresh the token with the refresh grant, then retry. See Authentication. |
| 403 | Missing scope(s): ... The token was not granted the scope this route needs. The message lists what was granted. | Re-install the app with the scope included. See Scopes. |
| 404 | The record does not exist, or it belongs to another installer. The API answers the same way in both cases so ids are not leaked. | Check the id and the workspace. |
| 409 | Conflict with the current state. On a send, the contact has outbound Do Not Disturb on. | Read the current state before you retry. For DND, do not send. |
| 422 | The line refused the change. The message carries LINE_REFUSED. | Read the message again. Apple's edit and unsend windows may have passed. |
| 429 | Rate limited. error is RateLimitExceeded. | Wait for Retry-After, then retry. See Rate limits. |
| 500 | Unexpected server error. | Retry with backoff. If it persists, contact support with the request details. |
| 502 | The message could not be handed to the messaging provider. | Retryable. Retry with backoff. |
| 503 | Sending is not available for this account, or the line is offline (LINE_UNREACHABLE). | Do not retry in a loop. Check the account or the line, then retry. |
| 504 | The line did not answer in time (LINE_TIMEOUT). | The change may still land. Read the message again before you retry. |
Codes in the message text
Some routes put an upper-case code in message. Match on it rather than on the surrounding words.
| Code | Status | Where | Meaning |
|---|---|---|---|
NO_ADVANCED_MODE_LINE | 400 | Reactions, threaded replies, effects, edit, unsend, notify anyway, Check iMessage | The workspace has no line with Advanced Mode on. |
RATE_LIMITED | 429 | Check iMessage | Each line takes 10 checks a minute. |
LINE_TIMEOUT | 504 | Check iMessage, edit, unsend, notify anyway | The line did not answer within the wait. The change may still land. |
LINE_REFUSED | 422 | Edit, unsend, notify anyway | Messages refused the change. |
LINE_UNREACHABLE | 503 | Edit, unsend, notify anyway | The line is offline. |
Retry rules
| Status | Retry? |
|---|---|
| 400, 403, 404, 409, 422 | No. The same request fails again. Fix the request, the scope or the state first. |
| 401 | Yes, once, after refreshing the token. |
| 429 | Yes, after Retry-After seconds. |
| 500, 502 | Yes, with exponential backoff. |
| 503 | Yes, after the cause is fixed. Not in a tight loop. |
| 504 | Read the record first. A send or an edit may already have landed. |
Treat an unknown 4xx as not retryable and an unknown 5xx as retryable.
Send failures
A send can be accepted with 200 and still end as failed. The API answers as soon as the message is queued. Poll GET /v1/messages/{id} and read status and error_message to see what happened after that.

