Skip to content

Errors and retries reference

One error envelope on every endpoint: each code with its HTTP status and retryable flag, how the SDKs back off, and what 410 and 426 mean.

Last updated:

When a request cannot be served, every endpoint answers the same way — one envelope, machine-readable, with enough in it to decide what to do next. The examples below authenticate like everything else here: a bb_ API key presented as a bearer token.

The envelope

Every failure is error with code, message, retryable and optionally retryAfterMs, requestId and details. Branch on retryable, never on the code: the code enum is open, and a newer server may introduce codes your switch has not seen. requestId is what support will ask for; details carries structured specifics such as a workflow's full validation error list.

How the SDKs retry

Both SDKs honour retryAfterMs and back off with full jitter when it is absent. One deliberate exception: the TypeScript SDK does not retry a POST that failed at the connection level, because a create that timed out on the way back may have succeeded server-side — retrying it blindly would make two sessions where you asked for one.

Every code

The current vocabulary — and it stays open.

Name Type Description
unauthorized HTTP 401 No key, or a key that does not exist. Not retryable.
forbidden HTTP 403 The key is valid but not allowed to do this. Not retryable.
not_found HTTP 404 The resource does not exist for this organisation. Not retryable.
invalid_request HTTP 400 The body failed validation — requests are strict, so a typo'd field lands here. Not retryable.
disallowed_browser_arg HTTP 400 An `extraArgs` entry is outside the allowlist. Not retryable.
conflict HTTP 409 The request contradicts current state. Not retryable.
rate_limited HTTP 429 Too many requests. Retryable, usually with `retryAfterMs`.
quota_exceeded HTTP 402 A plan quota is exhausted. Not retryable — the fix is the plan, not a retry.
concurrency_limit HTTP 429 Too much running at once for the organisation. Retryable once something finishes.
queue_timeout HTTP 429 The request waited too long in the queue. Retryable.
capacity_unavailable HTTP 503 No capacity right now — or, self-hosted, a missing key provider. Retryable.
session_expired HTTP 410 The session reached its deadline. Not retryable — create a new session.
session_gone HTTP 410 The session was released or its container is gone. Not retryable.
launch_failed HTTP 502 The browser container failed to start. Retryable.
protocol_incompatible HTTP 426 Client and server cannot negotiate a protocol version. Not retryable.
navigation_failed 422 The task API could not open `startUrl` before starting the run. Not retryable as it stands: fix the URL, or the site’s refusal.
internal HTTP 500 Something went wrong on our side. Retryable.

426, and the two 410s

protocol_incompatible usually means your client speaks a newer minor than the server — negotiation demands majors match exactly and the server's minor be at least yours, so upgrade the server or pin an older client; retrying changes nothing. The two 410s differ in history: session_expired says the session hit a deadline it was always going to hit, while session_gone says it was released or its container no longer exists. Both end the same way — a new session — but the first is a planning signal and the second may deserve a look at shutdownReason.

See the envelope

A GET for a session that does not exist shows the envelope exactly as every other failure returns it.

Requires: api-key

# Two refusals, one envelope shape. not_found is terminal; retryable says so.
curl -s "https://browserberg.com/v1/sessions/sess_does_not_exist" \
  -H "Authorization: Bearer $BROWSERBERG_API_KEY" \
  | python3 -m json.tool

Adjacent

  • Protocol versioning Why 426 happens and how to avoid it
  • Authentication Keys, accounts and your first 401
  • Limits The numbers behind 429 and 402