Errors and recovery
A request error has an error object with code, message, next, retryable, and sometimes retry_after. Read next first. A retryable error can succeed later; it does not guarantee that replaying a state-changing request is safe.
{
"error": {
"code": "box_full",
"message": "Illustrative capacity error",
"next": "Wait before retrying.",
"retryable": true,
"retry_after": 15
}
}This illustrates the shape; use the actual response’s guidance and delay. HTTP errors are separate from per-action errors: POST actions can return HTTP 200 with results[i].ok=false. The batch stops at the first failure and returns the current screen.
Decide what failed
| Signal | Meaning | Next step |
|---|---|---|
| 401 unauthorized | Missing, wrong or revoked bearer key | Correct the API key; a browser cookie is not an API key |
| 400 invalid_request | Invalid query/body, unknown field or type | Correct the shape; include Content-Type for JSON |
| 409 idempotency_conflict | This create key already made a phone with other options | Resend the original options, or use a new key |
| 410 phone_deleted | The phone this create key made was deleted | Use a new key for a new phone |
| 413 body_too_large | JSON exceeds 256 KB | Reduce the payload |
| 413 apk_too_large | Install upload exceeds 300 MiB | Supply a smaller compatible build |
| 429 phone_limit | Project running/total phone cap | Inspect phones; park or intentionally delete one |
| 503 box_full | Shared host cannot accept another phone | Respect Retry-After / retry_after |
| stale_ref | Target belongs to an old screen | Observe again and select a fresh target |
| type_not_applied | Text did not arrive in a focused field | Observe, focus or target the field, then type |
| signature_mismatch | Installed app and update have different signatures | Obtain the matching build or intentionally uninstall with data loss |
| batch_timeout | Batch stopped starting actions at its time budget | Inspect completed results and current screen |
The table is a recovery guide, not an exhaustive error-code registry. Backend device/app failures carry more specific next guidance. Do not interpret every non-200 response as a transient host failure.
A create response still says creating
wait=ready waits within a bounded request, not indefinitely. Save the returned id and poll GET /v1/phones/{id}?wait=ready&timeout=50. Continue while creating/starting, stop on ready or failed, and enforce your own overall deadline. Creating a second phone does not accelerate the first one.
If a create request fails at the network boundary before you receive a response, repeat it with the same Idempotency-Key header and options (MCP: idempotency_key): within 24 hours you get the phone the first request made, with no second charge. Without a key, list phones before repeating it; a second POST can create a second billable phone. See retry a create safely.
An action failed after earlier actions succeeded
Earlier actions remain applied. Do not replay a full batch containing a submit, message send, delete, or purchase. Inspect results and the returned screen; continue from the last confirmed state. Verify the intended outcome before deciding to retry a side effect.
References are intentionally short-lived. Prefer current text, resource id, or content-description targets. Use screenshots for UI absent from the element tree. See targeting and recovery.
The MCP call did not throw
Protocol success does not prove application success. Tool errors use isError with recovery text. The act tool reports per-action results in text; a failed action can be part of a normal tool result. Read the entire result and new screen before planning the next call.
Cleanup after a failure
Park when app data should remain; delete when the sandbox is disposable. Do this even when the main task failed. Server idle/duration limits are a backstop, not a reason to skip cleanup. Keep the phone id somewhere the cleanup path can reach, and report a cleanup error separately from the original failure.
Source: docs/ERRORS.md in the repo, rendered at build time.