> ## Documentation Index
> Fetch the complete documentation index at: https://webscrape.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> HTTP status codes, the error envelope shape, stable error codes, and how to handle them.

Every endpoint returns one of three envelope shapes: `completed`, `queued`, or `error`. Check the top-level `status` first, then read `data` (success or queued) or `error` (failure).

## Status codes

| Code                       | Meaning                                               | `error.code`                             | Action                                                |
| -------------------------- | ----------------------------------------------------- | ---------------------------------------- | ----------------------------------------------------- |
| `200 OK`                   | Synchronous success                                   | n/a                                      | Read `data`                                           |
| `202 Accepted`             | Async dispatch (SmartBrowse)                          | n/a                                      | Read `data.run_id` and poll                           |
| `400 Bad Request`          | Malformed request                                     | `invalid_request`                        | Fix the request                                       |
| `401 Unauthorized`         | Missing or revoked API key                            | `unauthorized`                           | Check `X-API-Key`                                     |
| `402 Payment Required`     | Out of credits                                        | `insufficient_credits`                   | Top up at [Billing](https://webscrape.ai/app/billing) |
| `402 Payment Required`     | Email not verified                                    | `email_verification_required`            | Verify your email                                     |
| `403 Forbidden`            | Authenticated, but not allowed to perform this action | `forbidden`                              | Check the key's scope or the resource owner           |
| `404 Not Found`            | Resource doesn't exist                                | `not_found`                              | Verify the ID                                         |
| `409 Conflict`             | Resource in a state that disallows the action         | `conflict` / `account_deletion_pending`  | E.g. spending while account deletion is pending       |
| `422 Unprocessable Entity` | Schema validation failed                              | `validation_failed`                      | Adjust the JSON schema or prompt                      |
| `429 Too Many Requests`    | Rate limit hit                                        | `rate_limited`                           | Back off. See [Rate limits](/docs/concepts/rate-limits)    |
| `500 / 502`                | Internal error                                        | `internal_error` / `service_unavailable` | Retry with backoff; failed requests cost 0 credits    |

## Success envelope

```json theme={null}
{
  "status": "completed",
  "data": { "...": "..." },
  "credits_used": 5,
  "credits_remaining": 495,
  "request_id": "req_aB3xY9Kp"
}
```

`data` holds the payload — extracted JSON for `/v1/smartscraper`, fetched content for `/v1/scrape`. `credits_used` is what this call cost after deduction.

## Queued envelope

Returned by the async-dispatch endpoint (`POST /v1/smartbrowse/recipes/:id/run`). No credit fields here because the work hasn't finished and nothing's been billed yet.

```json theme={null}
{
  "status": "queued",
  "data": {
    "run_id": "k7Xb9dRmQ2p",
    "recipe_id": "m3Yc2tFvN8q",
    "run_status": "running",
    "poll_url": "/v1/smartbrowse/runs/k7Xb9dRmQ2p",
    "created_at": "2026-05-21T17:42:01Z"
  },
  "request_id": "req_aB3xY9Kp"
}
```

Poll `data.poll_url` (i.e. `GET /v1/smartbrowse/runs/:id`) until the inner field hits a terminal state. We named it `run_status` so it doesn't clash with the envelope's outer `status`:

* `data.run_status` is one of `queued | running | completed | failed | cancelled`

Or set up a webhook in [**Settings** > **Webhooks**](https://webscrape.ai/app/settings) and skip polling entirely.

## Error envelope

```json theme={null}
{
  "status": "error",
  "error": {
    "code": "validation_failed",
    "message": "Output did not match the requested schema after one repair attempt.",
    "details": { "...": "optional structured context" }
  },
  "request_id": "req_aB3xY9Kp"
}
```

`error.code` is stable — branch on it. `error.message` is human-readable and can change between releases, so log it for debugging but don't pattern-match on the text. `error.details` is optional structured context (e.g. `{balance, required}` for `insufficient_credits`).

The `request_id` also comes back as the `X-Request-ID` response header on every response, success or failure. Include it when you ping support.

## Retrying

Transient failures (timeouts, `429`, `5xx`) are safe to retry. Since failed requests cost **0 credits**, a capped backoff loop with jitter costs you nothing beyond the eventual successful call. Don't retry on `400`, `401`, `402`, `403`, `404`, or `422` — those won't change until you fix the request.
