# Errors

Source: https://webagent-docs-staging.lookover.ai/reference/errors

> Every error code the API returns, its HTTP status, what it means and what to do.

Every failed request returns a JSON body with one `error` object, and an HTTP status that depends only on the code.

```json title="Error body"
{
  "error": {
    "code": "not_found",
    "message": "Session sess_us_... not found",
    "details": {},
    "requestId": "..."
  }
}
```

- Match on `error.code`, not on `message`. The message is for people and can change.
- `details` depends on the code. The column below lists the fields you can rely on.
- `requestId` identifies the request in our logs. Quote it when you ask for support.
- A client that sees a code it does not know should treat it like `internal` and use the HTTP status.
- In the MCP tools a failed call is a tool result marked as an error, with the text `[code] message`.
- The SDK throws `WebagentApiError` with the same `code`, `status`, `details` and `requestId`.

| Code | HTTP | Meaning | Details | What to do |
|---|---|---|---|---|
| `internal` | 500 | Bug or unexpected failure on our side |  |  |
| `timeout` | 504 | An operation ran past its time limit | `ms` | Retry. If it keeps happening, make the request smaller. |
| `invalid_input` | 400 | Request body, query or params failed validation | zod issues | Read `error.details` (the failed fields), fix the request, send it again. |
| `not_found` | 404 | No such resource (or not visible to this tenant) |  | Check the id and the project of your key. A resource you cannot see is also reported as not found. |
| `session_closed` | 410 | The session has ended |  | Start a new session. A closed session cannot be reopened. |
| `session_paused` | 409 | A human took over; agent actions wait for hand-back |  | A person took over in the live view. Wait for hand-back, or stop the session. |
| `browser_crashed` | 502 | The browser process died |  |  |
| `browser_launch_failed` | 503 | Chromium did not start | `launch` (why, retryable) | `error.details.launch` says why and whether a retry can help. |
| `page_crashed` | 502 | The tab's renderer crashed; `navigate`/`reload` recovers |  | Run `navigate` or `reload` on the tab. The browser is still alive. |
| `protocol_error` | 502 | CDP/Playwright returned an error | protocol message |  |
| `target_detached` | 409 | The tab or frame went away mid-action |  |  |
| `navigation_blocked` | 403 | URL refused by policy (SSRF guard, site rules) | `url`, `reason` | The address is private, loopback or otherwise refused. Use a public URL or a tunnel: see [Test a local app](/guides/local-apps). |
| `element_not_found` | 422 | No element matched the target | `target` |  |
| `element_not_visible` | 422 | Element exists but is not visible | `target` |  |
| `element_disabled` | 422 | Element is disabled | `target` |  |
| `capacity_exceeded` | 503 | No runner capacity, or a tenant limit reached | `limit` | Close sessions you no longer need, then retry. `error.details.limit` says which limit you hit. |
| `runner_unavailable` | 503 | The session's runner cannot be reached; retry briefly, then treat the session as lost | `reason` | Retry in a few seconds. If it keeps failing, treat the session as lost and start a new one. |
| `unauthorized` | 401 | Missing or bad API key / token |  | Check the API key (`Authorization: Bearer wa_...` or `x-api-key`). Make a new key if it was revoked. |
| `forbidden` | 403 | Authenticated but not allowed | `reason` | Your key or account is not allowed to do this. Read the message and `error.details.reason`; an admin may be needed. |
| `conflict` | 409 | State conflicts with the request (id in use) |  |  |
| `consent_required` | 403 | A third-party skill needs admin consent first | `skill`, `asks` | An admin has to accept what the skill asks for first (`error.details.asks`). |
| `unsupported_on_engine` | 422 | This engine cannot do the action | `engine`, `feature` | Use another engine, or drop the option. `error.details.engine` and `error.details.feature` say what is missing. |
| `wrong_region` | 421 | The API key belongs to another region; retry on that region's API | `region` | Send the request to the API of the region in `error.details.region`. |
| `rate_limited` | 429 | Too many requests; also sends `Retry-After` | `retryAfterS` | Wait `error.details.retryAfterS` seconds (also the `Retry-After` header), then retry. |
| `idempotency_key_reused` | 422 | Idempotency-Key already used with a different request |  | Use a new `Idempotency-Key` for a different request. |
| `idempotency_in_progress` | 409 | First request with this Idempotency-Key still running; retry later |  | The first request is still running. Wait, then retry with the same key. |
| `auth_unavailable` | 503 | Identity service unreachable (dashboard sign-in); retry |  | Retry in a few seconds. |
| `reauth_required` | 401 | Sensitive action needs a sign-in within the last 120 s; sign in again |  | Sign in again in the dashboard, then repeat the action. |
| `payload_too_large` | 413 | The request body is larger than the endpoint accepts | `limitBytes` | Send less data. `error.details.limitBytes` is the limit. |
| `method_not_allowed` | 405 | The route exists but not for this method; also sends `Allow` |  | Use another HTTP method. The `Allow` response header lists the ones the route accepts. |
| `org_suspended` | 403 | This organisation is suspended. Contact support. |  | Contact support. |

Two reasons share an existing code instead of having their own:

- A replayed `Idempotency-Key` whose first answer was too large to keep returns `conflict` with `details.reason: "idempotency_not_replayable"`.
- A dashboard sign-in by a user with no organization returns `forbidden` with `details.reason: "needs_org"`.
