All errors follow a standard format:
doc_url links to the docs page for that error type. Include request_id when you contact support.
Error Types
The four you'll actually hit while building:
| Status | Type | SDK Exception | Description |
|---|
| 401 | authentication_error | AuthenticationError | Invalid or missing API key |
| 402 | billing_error | BillingError | Run or cost limit reached, or balance depleted |
| 422 | validation_error | ValidationError | Invalid request parameters |
| 429 | rate_limit_error | RateLimitError | Too many requests |
Every SDK exception maps to one status. Catch the specific class you can recover from:
The SDK retries 429 and 500+ responses with exponential backoff for idempotent methods (GET, PUT, DELETE), and for the run-creating POSTs, which carry an idempotency key so a retry returns the original run instead of starting a second one. Any other POST or PATCH is never retried, to prevent duplicate side effects.
All error types and `error.details` conventions
| Status | Type | SDK Exception | Description |
|---|
| 400 | invalid_request_error | ValidationError | Invalid request |
| 403 | permission_error | PermissionDeniedError | Insufficient permissions |
| 404 | not_found | NotFoundError | Resource not found |
| 409 | conflict_error | ConflictError | Resource conflict (duplicate, not active) |
| 500+ | api_error | APIError | Server error (automatically retried for GET/PUT/DELETE) |
- Errors that carry a machine-readable app code (e.g.
feature_disabled, from_template_conflict, RUN_LIMIT_REACHED) put it in error.details.error_code, with any recovery context (missing, connect_urls, rejected_fields, …) as sibling keys under error.details. The Python SDK surfaces it as the exception's .code.
- Query params are strict: an unknown query parameter is rejected with
422 (error.details.error_code = "unknown_query_parameter", with a did-you-mean hint). Analytics params (utm_*, gclid, fbclid, _) are ignored. Unknown fields in request bodies are silently ignored.
Billing Error Codes
A 402 billing_error carries a machine-readable code plus an error.details object with the context you need to recover (BillingError.code / BillingError.details in the SDK). The two you must handle:
| code | Meaning | Key details fields |
|---|
RUN_LIMIT_REACHED | Monthly included runs exhausted | runs_used, runs_limit, period_end, overage_available (whether overage can actually be enabled for your account — false without an active subscription to bill it to) |
TOKEN_BALANCE_DEPLETED | Prepaid token balance exhausted (API/developer billing). Top up, or connect your Claude subscription for local development | balance_micros, balance_usd, topup_url, connect_claude_url |
All billing codes, plus the two 429 guards
| code | Meaning | Key details fields |
|---|
OVERAGE_CAP_REACHED | Usage overage spend cap hit | overage_used_cents, overage_cap_cents, period_end, overage_settings_url (raise the cap or upgrade) |
TRIAL_EXPIRED | Time-boxed trial has ended | trial_ends_at, upgrade_url |
COST_LIMIT_REACHED | Fair-usage cost backstop hit | runs_used, runs_limit, period_end |
SUBSCRIPTION_BLOCKED | Payment issue (past_due / unpaid) | subscription_status |
END_USER_RUN_LIMIT_REACHED | One end-user hit your per-end-user run sub-cap (other end-users unaffected) | end_user_id, runs_used, runs_limit, period_end |
END_USER_COST_LIMIT_REACHED | One end-user hit your per-end-user cost sub-cap | end_user_id, cost_used_cents, cost_limit_cents, period_end |
Two more guards return 429 rate_limit_error (not 402):
END_USER_RATE_LIMITED: an end-user burst past your per_end_user_rate_per_minute setting. details carry end_user_id, limit_per_minute, retry_after, plus a Retry-After header. Configure all three sub-caps via client.settings.update(...); see Users.
SANDBOX_CONCURRENCY_LIMIT: too many runs in flight at once. details.scope says which ceiling: account (your own burst; details carry your concurrent_runs and limit) or platform (overall capacity, no counts). Wait for in-flight runs to finish and retry after the Retry-After header. Need more concurrency? Contact support to raise your account cap.
Run-Level Failures
The exceptions above cover problems reaching the API. A run can also fail upstream: an expired Claude credential, an exhausted quota, a model rate limit. The HTTP request succeeds (no exception), but the run ends with status "failed", the message in run.output, and a machine-readable class in run.error_code. Always check it before trusting output:
Run `error_code` reference
Transient codes are retry-safe; credential codes need re-auth first.
| code | Meaning | Run status | Retry safe? |
|---|
oauth_revoked | The connected Claude subscription credential was revoked or expired | failed | After reconnecting OAuth |
subscription_quota_exhausted | The connected Claude subscription hit its usage quota | failed | After the quota window resets |
model_unavailable | The requested model is not available on the connected Claude plan | failed | After switching model or upgrading the plan |
api_credit_balance | The upstream provider key ran out of credit | failed | After the balance is restored |
rate_limited | Upstream model rate limit hit mid-run (auto-retried on scheduled runs) | failed | Yes |
overloaded | The upstream provider was overloaded or down (5xx/overload, auto-retried on scheduled runs) | failed | Yes |
sandbox_boot_timeout | The sandbox never started; nothing executed | failed | Yes (auto-retried on scheduled runs) |
sandbox_quota_exhausted | The execution platform was at capacity, so no sandbox could be created; nothing executed | failed | Not immediately — the same limit will refuse a retry; wait, then retry |
incomplete_stream | The stream died before finishing; the agent may have partially executed | failed | Review output first |
internal_timeout | An internal service timed out while the run was starting or executing | failed | Yes |
service_unreachable | A service the run depends on was unreachable | failed | Yes |
sandbox_unavailable | The sandbox could not be provisioned, or was lost mid-run | failed | Yes (auto-retried on scheduled runs when nothing had executed yet) |
storage_error | A temporary storage error interrupted the run | failed | Yes |
internal_error | Unclassified platform error; reported automatically on our side | failed | Yes, then contact support |
unknown | Unclassified upstream error; the message is in run.output | varies | Review output first |
Next: Limits · Going Live