Skip to content
Go to Dashboard

Errors & retries

This page lists WebAgent's error codes and tells you which ones to retry, which to surface to the user, and which to fix in your own code.

Every error response has the same shape:

json
{
  "code": "rate_limit_exceeded",
  "detail": "Per-key concurrency limit (10) reached.",
  "extra": { "limit": 10, "active": 10 }
}

The HTTP status tells you the category; the code field is the stable contract — switch on it, never on detail (English prose, may change).

Code matrix

StatusCodeRetry?What to do
400bad_requestFix the request body. Check the OpenAPI spec for the field.
401unauthorizedKey is missing, malformed, expired, or revoked past its 1-hour grace. Create a new one.
402insufficient_creditsTop up via Settings → Billing, or enable auto-recharge.
402budget_exceededPer-task max_cost_usd cap hit. Raise the cap or split the work.
403forbiddenKey valid, but the project doesn't grant it access to this resource.
403safety_boundary_violatedThe agent refused on safety grounds. Read extra.reason; reword the instruction.
404session_not_found, task_not_found, profile_not_found, …The id is wrong or the resource was deleted.
409conflictState mismatch (e.g. cancel on a terminal task). Re-read state and decide.
422validation_errorSchema-level — extra.errors[] lists the offending fields.
429rate_limit_exceededHonour Retry-After; exponential back-off if absent.
429too_many_concurrent_sessionsWait for an in-flight session to free up, or upgrade plan.
5xxinternal_errorSame call, exponential back-off. Capped at 3–5 attempts.
(network)Connection reset / timeout — retry idempotently.

✅ = safe to retry without reasoning. ❌ = will keep failing until you change something.

Retry policy we recommend

python
import time, random

def with_retries(fn, *, attempts=4, base=0.5, cap=8.0):
    for i in range(attempts):
        try:
            return fn()
        except WebAgentError as e:
            if e.code not in {"rate_limit_exceeded",
                              "too_many_concurrent_sessions",
                              "internal_error"}:
                raise                          # not safe to retry
            if i == attempts - 1:
                raise
            sleep_s = min(cap, base * 2**i) + random.uniform(0, 0.25)
            time.sleep(e.retry_after_seconds or sleep_s)

The Python and TypeScript SDKs ship this loop by default; the table above is for when you're calling the API directly.

Idempotency keys

Every mutating endpoint (POST /sessions, POST /sessions/{sid}/tasks, POST /messages, …) accepts an Idempotency-Key header:

http
POST /v1/projects/proj_demo_0001/do_anything/sessions
Idempotency-Key: 9b2f7c1e-…-uuid

Replay the same UUID within 24 hours and you'll get the same response back (same session_id, same status code) — even after a network blip. Generate one UUID per logical action, not per retry.

Inside the task lifecycle

Errors during agent execution don't always fail the task — many are recoverable:

  • Tool error — emitted as task.action.failed; the agent decides to retry the tool, choose a different one, or fail the whole task.
  • Captcha / 2FA — emitted as task.input_request; you answer via POST /intervene.
  • Hard cap hit — task transitions to failed with error.code = budget_exceeded or duration_exceeded. Spent credits are billed.
  • Safety refusal — task transitions to failed with error.code = safety_boundary_violated. No credits charged for the refused step.

You see all four through the SSE stream.

SSE-specific failure modes

SymptomCauseFix
Stream stalls > 60 sNetwork drop or proxy bufferingReconnect with Last-Event-ID: <last_seen_id>.
Last-Event-ID ignoredBuffer expired (older than 1 hour)Re-fetch task state via GET /sessions/{sid}/tasks/{tid} and resume from current.
Duplicate events on reconnectAt-least-once deliveryDedupe by event id (monotonic per-task).

Next steps