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

# Errors & Troubleshooting

> Decode every status code the execution API can return and act on it correctly

Every request goes to `https://api.parse.bot`. This page is the definitive reference for what each status code means — especially for **executing endpoints** (`POST /scraper/{scraper_id}/{endpoint_name}`), where Parse wraps the outcome in a structured envelope so you can tell *whose* fault a failure is: yours, the target site's, or the scraper's.

## The execution envelope

A successful call returns the endpoint's own JSON (the shape described by its `return_schema`) with HTTP `200`:

```json theme={null}
{ "title": "A Light in the Attic", "price": 51.77, "rating": 3 }
```

A failed call returns a non-2xx status with an `error` envelope. The HTTP code tells you the **category** of failure, and the body carries a machine-readable `status` plus detail:

```json theme={null}
{
  "error": {
    "status": "upstream_error",
    "upstream_status_code": 404,
    "message": "The target site returned HTTP 404. See `snippet` for the upstream response body.",
    "snippet": "<html>Product not found</html>",
    "url": "https://example.com/products/invalid-sku"
  },
  "status_code": 502
}
```

The `status` field is the most useful signal — it maps 1:1 to the HTTP code:

| HTTP  | `status`         | What it means                                            | What to do                                                           |
| ----- | ---------------- | -------------------------------------------------------- | -------------------------------------------------------------------- |
| `200` | `success`        | The endpoint ran and returned data                       | Use the body                                                         |
| `422` | `stale_input`    | Your input was invalid, or the resource is gone upstream | Fix the input and retry                                              |
| `429` | —                | Rate limit hit (burst or daily)                          | Honor `Retry-After` and back off                                     |
| `502` | `upstream_error` | The target site returned a non-2xx response              | Inspect `upstream_status_code` + `snippet`; **do not blindly retry** |
| `503` | `blocked`        | Anti-bot blocked every proxy we tried                    | Retry later **only if** `retry_after` is present                     |
| `500` | `error`          | The scraper crashed or hit a bug                         | Request a revision of the API                                        |
| `400` | —                | Malformed request (bad header, missing session creds)    | Fix the request                                                      |
| `401` | —                | Missing/invalid/expired API key                          | Check your key                                                       |
| `404` | —                | Scraper (or pinned version) doesn't exist or isn't yours | Check the `scraper_id`                                               |

<Warning>
  **`502` is not a Parse outage and should not be auto-retried.** It means *the target site* answered the scraper with a non-2xx (e.g. a 404 for an ID that doesn't exist, a 401 for bad upstream credentials, a transient site 500). Read `upstream_status_code` and `snippet` to see what the site actually said before deciding whether to retry.
</Warning>

## 422 — `stale_input` (your input is the problem)

The scraper rejected your input before or during the upstream call — wrong type, missing required field, or a resource the site says is gone.

```json theme={null}
{
  "error": {
    "status": "stale_input",
    "kind": "input_format_invalid",
    "message": "trip_type must be 'one_way' or 'round_trip'",
    "upstream_status_code": 400,
    "url": "https://example.com/api/search"
  },
  "status_code": 422
}
```

* `kind: "input_format_invalid"` — a parameter is malformed. Fix it against the endpoint's `input_params`.
* `kind: "input_not_found"` — the specific thing you named (a job ID, a product SKU) was confirmed gone by the site (often a 410). Not retryable with the same input.

## 502 — `upstream_error` (the target site failed)

The scraper reached the site, but the site returned a non-2xx. The body tells you exactly what came back:

```json theme={null}
{
  "error": {
    "status": "upstream_error",
    "upstream_status_code": 503,
    "message": "The target site returned HTTP 503. See `snippet` for the upstream response body.",
    "snippet": "Service Unavailable"
  },
  "status_code": 502
}
```

Decide based on `upstream_status_code`: a `404`/`410` usually means your input pointed at nothing (don't retry); a `5xx` from the site may be transient (retry with backoff); a `401`/`403` may mean the API's stored credentials need attention (revise the API).

## 503 — `blocked` (anti-bot stopped us)

Parse rotated through proxies and every attempt hit the site's bot protection.

```json theme={null}
{
  "error": {
    "status": "blocked",
    "block_type": "datadome",
    "vendor": "datadome",
    "kind": "antibot_solvable",
    "attempts": 3,
    "retry_after": 60,
    "message": "Service temporarily unavailable - site protection blocking all proxies"
  },
  "status_code": 503
}
```

* `retry_after` present (and a `Retry-After` header) → transient; retry after that many seconds.
* `retry_after: null` (and **no** `Retry-After` header) → `kind` is `antibot_unsolvable` or `ip_burned`; retrying soon won't help. If it persists, the API may need a revision.

## 500 — `error` (the scraper bugged out)

The scraper crashed or detected an internal fault. This is the API's problem, not yours.

```json theme={null}
{
  "error": {
    "status": "error",
    "kind": "scraper_bug",
    "message": "The scraper crashed during execution. This could be due to bad input, broken scraper code, or a website update."
  },
  "status_code": 500
}
```

**Fix:** revise the API — `POST /dispatch/tasks/{task_id}/revise` with a description (e.g. "the get\_paper endpoint 500s on valid IDs"). If it's a marketplace/canonical API, the revision improves it for everyone.

## 429 — rate limited

```json theme={null}
{
  "error": {
    "error": "Rate limit exceeded",
    "message": "Too many requests in a short burst. Retry in 5s.",
    "limit_type": "burst",
    "retry_after": 5
  },
  "status_code": 429
}
```

* `limit_type: "burst"` — you exceeded the short-term token bucket. `retry_after` is seconds until the next token.
* `limit_type: "daily"` — you hit your daily cap. `retry_after` is seconds until UTC midnight.

Every execution response carries rate-limit headers so you can pace yourself *before* getting a 429:

| Header                                      | Meaning                                                 |
| ------------------------------------------- | ------------------------------------------------------- |
| `X-RateLimit-Remaining`                     | Tokens left in the burst bucket                         |
| `X-RateLimit-Reset`                         | Unix time when the next token is available              |
| `X-RateLimit-Daily-Remaining`               | Requests left today (tiers with a daily cap)            |
| `Retry-After`                               | Seconds to wait (sent on `429`, and on retryable `503`) |
| `X-Credits-Charged` / `X-Credits-Remaining` | Credits this call cost / your balance                   |

Authenticated login endpoints have a stricter cap (3 attempts/hour per API) — see [Authenticated APIs](/authenticated-apis).

## 400 / 401 / 404 — request problems

* **`400`** — a malformed request: an invalid `API-Snapshot-Version` header, or `session_id`/`encryption_key` sent only one of the pair. Body: `{"error": "<reason>", "status_code": 400}`.
* **`401`** — `{"error": "Missing X-API-Key header" | "Invalid API key" | "API key expired", "status_code": 401}`. Send a valid `pmx_…` key in `X-API-Key` (header name is case-insensitive; the value is not). See [Authentication](/authentication).
* **`404`** — `{"error": "Scraper with ID '…' not found", "status_code": 404}`. The scraper or pinned version doesn't exist, or isn't yours — Parse returns `404` rather than reveal another account's resource. Use the `scraper_id` from *your* completed task's `generated_api`.

<Note>
  A bare JSON `{"detail":"Not Found"}` (note: `detail`, not `error`) means the **path** doesn't exist, not the scraper. Check you're calling `POST /scraper/{scraper_id}/{endpoint_name}` exactly, on `https://api.parse.bot`.
</Note>

## Build failures (task `status: "failed"`)

A dispatch/revision task can finish `failed` rather than `completed`; the task's `error` field says why. Retry with a clearer `task` description, `revise` it, or — if `may_require_auth` was `true` — build it as an [Authenticated API](/authenticated-apis) from the dashboard.

## Still stuck?

* Confirm the base URL: **`https://api.parse.bot`**.
* Verify your key with a cheap call: `GET /dispatch/tasks`.
* **Reach out to support** from the [dashboard](https://parse.bot) — click the chat icon in the **bottom-right corner** to start a conversation with us. Grab the `X-Railway-Request-Id` response header from the failing call and include it; it lets us trace your exact request.
