Errors and status codes
How the API reports errors, covering HTTP status codes, problem+json bodies, task-level error events, and what to retry.
The API reports failures at three levels: HTTP errors (the request itself failed), task-level events (a Task hit a problem while running), and action results (an action was accepted but didn’t succeed). Handle all three.
Error body shape
Section titled “Error body shape”HTTP errors return an RFC 7807 problem document with Content-Type: application/problem+json:
{ "type": "about:blank", "title": "Payment Required", "status": 402, "detail": "Your organization is out of credits.", "instance": "/computers"}| Field | Meaning |
|---|---|
type | URI reference identifying the error category |
title | Short human-readable summary |
status | The HTTP status code, repeated in the body |
detail | Optional; specifics about this occurrence |
instance | Optional; the path or resource the error relates to |
Status codes
Section titled “Status codes”| Status | Meaning | What to do |
|---|---|---|
400 | Validation error, e.g. passing start_url on a desktop task, or an out-of-range parameter | Fix the request; don’t retry as-is |
401 | Missing or invalid API key | Check TZAFON_API_KEY; regenerate the key if needed |
402 | Out of credits | Top up at lightcone.ai/dashboard/usage |
403 | Key is valid but not allowed to do this | Check the key’s scope and your plan |
404 | Resource does not exist, or belongs to another organization | Verify the ID and that you’re using the right org’s key |
429 | Concurrent computer limit for your plan reached | Wait for a session to finish, or retry with backoff |
500 | Internal error | Retry with backoff; contact support if persistent |
502 | Bad gateway | Retry with backoff |
503 | Upstream busy | Retry with backoff |
Endpoints that don’t use problem+json
Section titled “Endpoints that don’t use problem+json”Several surfaces return errors in a different shape:
-
Lapsed-subscription
402returns{"error": "light_subscription_lapsed", "status": "read_only"}(see above), not problem+json. -
/execstreaming always respondsHTTP 200and streams newline-delimited JSON (application/x-ndjson). Each line is{"type": "stdout" | "stderr" | "exit" | "error", ...}. The stream ends with a single{"type": "exit", "code": <int>}line; code-1means the command timed out or terminated abnormally. Check theexitline, not the HTTP status. -
OpenAI-compatible endpoints return OpenAI-style error bodies, so existing OpenAI SDK error handling works unchanged.
-
Anthropic-compatible endpoints return Anthropic-style bodies:
{"type": "error", "error": {"type": "...", "message": "..."}}.
Task-level errors (SSE events)
Section titled “Task-level errors (SSE events)”A Task that starts successfully reports problems as stream events, not HTTP errors:
| Event | Meaning |
|---|---|
config_error | The task couldn’t start with the given configuration. Reasons include model_not_found, invalid_kind, and api_error |
error | A non-terminal problem; the task is still running and may recover |
failed | Terminal failure. Carries status, an error_code (e.g. session_expired, exec_failed, invalid_token), and a retryable flag |
max_steps | The task hit its step limit before finishing |
max_duration | The task hit its time limit before finishing |
cancelled | The task was cancelled |
Distinguish error (transient, keep listening) from failed (terminal). On failed, check retryable before resubmitting.
stream = client.agent.tasks.start_stream(instruction="...", kind="browser")for event in stream: if event.type == "failed": if event.retryable: pass # resubmit the task else: raise RuntimeError(f"Task failed: {event.error_code}")const stream = await client.agent.tasks.startStream({ instruction: "...", kind: "browser" });for await (const event of stream) { if (event.type === "failed") { if (event.retryable) { // resubmit the task } else { throw new Error(`Task failed: ${event.error_code}`); } }}Action-level failures
Section titled “Action-level failures”Computer actions (click, navigate, screenshot, …) return HTTP 200 even when the action itself fails. The ActionResult carries the outcome:
result = computer.navigate("https://example.com")if result.status != "success": print(f"Navigation failed: {result.error_message}")const result = await client.computers.navigate(id, { url: "https://example.com" });if (result.status !== "success") { console.error(`Navigation failed: ${result.error_message}`);}What to retry
Section titled “What to retry”| Error | Retry? | How |
|---|---|---|
429 | Yes | Exponential backoff, or wait for a running session to finish |
500 / 502 / 503 | Yes | Exponential backoff (the SDKs do this automatically) |
failed event with retryable: true | Yes | Resubmit the task |
error event (non-terminal) | No action | Keep consuming the stream; the task may recover |
400 | No | Fix the request first |
401 | No | Fix the key first |
402 | No | Add credits first |
failed event with retryable: false | No | Fix the underlying cause (e.g. re-authenticate for invalid_token) |
Next steps
Section titled “Next steps”- Troubleshooting: fixes for common failure modes
- Production checklist: timeouts, retries, and hardening
- API Reference: per-endpoint error details