# Errors

URLpipe uses conventional HTTP status codes and a consistent JSON error shape, so failures are easy to detect and handle.

## Status codes

| Status | Meaning |
| --- | --- |
| `200 OK` | The request succeeded. Async requests return 200 with a token. |
| `401 Unauthorized` | Missing or invalid API key. |
| `403 Forbidden` | The key is valid, but the email address on the account hasn't been confirmed. |
| `422 Unprocessable Entity` | The operation failed or a parameter was invalid — see the error field. |
| `429 Too Many Requests` | Too many of your requests already running (concurrency\_limit), sending too fast (rate\_limited), or — Free plan only — out of credits (quota\_exceeded). A paid plan is never refused for credits; it keeps serving at the overage rate. |

## Error shape

When an operation fails, the response is JSON with a single `error` field holding a human-readable message:

422 Unprocessable Entity

```
{
  "error": "The request timed out."
}
```

Three responses carry extra fields: the [quota](https://urlpipe.dev/docs/credits) 429 (with `limit`, `used`, `needed`, `resets_at`), the [parallel-requests](https://urlpipe.dev/docs/credits#concurrency) 429 (with `limit` and `running`), and the invalid-parameter 422 shown below.

## Authentication errors

A `401 Unauthorized` means the `Authorization` header is missing or the token doesn't match an active project key; its code is `invalid_api_key`, and the message says which of the two it was. A `403 Forbidden` with the code `email_unverified` means the key is fine but none of the organization's admins has confirmed their email address yet — resending the key or rotating it will not help; an admin clicking the link we emailed them will. See [Authentication](https://urlpipe.dev/docs/authentication).

## Validation errors

Bad parameters return a `422` with a machine-readable `error` code:

- **Name**
  : `invalid_url`
  **Description**
  : The `url` is missing or not acceptable. It must be a valid `http`/`https` URL for a public domain — not an IP address, `localhost`, an internal hostname, or a custom (non-default) port. It must not carry credentials: `https://user:pass@example.com` is rejected.
- **Name**
  : `invalid_max_age`
  **Description**
  : The `max_age` value couldn't be parsed. Use a number of seconds or a duration like `"2 hours"`.
- **Name**
  : `invalid_options`
  **Description**
  : A [page option](https://urlpipe.dev/docs/page-options) or [screenshot option](https://urlpipe.dev/docs/screenshot#options) is out of range, of the wrong type or not one we know, and the message names which — `"screenshot_options.viewport_width must be a whole number from 320 to 1920."`, for example.
- **Name**
  : `invalid_labels`
  **Description**
  : [labels](https://urlpipe.dev/docs/labels) must be an object of up to 16 keys with string values, and the message names what to change — `"labels.client must be a string."`, for example.
- **Name**
  : `invalid_idempotency_key`
  **Description**
  : The [Idempotency-Key](https://urlpipe.dev/docs/retries#idempotency-key) must be 1 to 255 printable ASCII characters, with no spaces.
- **Name**
  : `idempotency_key_reused`
  **Description**
  : This `Idempotency-Key` came with a different request in the last 24 hours. Nothing ran: send a new key for a new request. See [retries & duplicates](https://urlpipe.dev/docs/retries#errors).
- **Name**
  : `report_to …`
  **Description**
  : A `report_to` was given that we will not deliver to, and the message names the reason — the same [rules the URL being analysed is held to](https://urlpipe.dev/docs/async#endpoint-rules), so an IP address, a `localhost` address, credentials in the URL or a custom port are all refused. Omitting it entirely is not an error: see [where results go](https://urlpipe.dev/docs/async#where-results-go).

## Common failure messages

Operational failures — network issues, unreachable or oversized pages — come back as a `422` with one of these messages:

| Message | Cause |
| --- | --- |
| The request timed out. | The page took too long to load. |
| The connection to the server timed out. | A connection to the host could not be established in time. |
| The requested page was not found. | The host couldn't be resolved, or the page returned HTTP 404. |
| An internal server error occurred in the requested page. | The page returned an HTTP 5xx status. |
| The request was invalid. | The page returned another 4xx status (e.g. 401, 403, 410). |
| There was a problem with the SSL certificate. | The host's TLS certificate could not be validated. |
| The server refused the connection. | The host actively refused the connection. |
| The page is rate-limiting requests. | The page returned HTTP 429. We slow down and try again ourselves before you ever see this. |
| Too many redirects occurred while processing the request. | A redirect loop was detected. |
| No internet connection was detected. | A network connectivity problem occurred. |
| The request was blocked by the client. | The request was blocked (e.g. by ad-blocking rules). |
| The requested URL resolved to an address that is not publicly reachable. | The URL, or a redirect from it, pointed at a private or loopback address. Only public web addresses can be analysed. |
| The requested URL is not a web page. | The URL returned something other than HTML — a PDF, an image or a download, for example. |
| The page asked us to complete a bot check before it would load. | The site served an anti-bot challenge instead of the page. We work the challenge before you see this — waiting it out and retrying — and most of them clear; a handful of sites put a CAPTCHA in front of every visitor that is not a person. |
| The page could not be loaded. | The page never came up, so there was nothing to analyse. |
| An unexpected error occurred while processing the request. | Something failed that we do not have a specific answer for. These are reported to us automatically; retrying is usually worthwhile. |
| The page is too big to be processed. | The HTML exceeds the 10 MB limit, or the page holds more content than an AI operation can return in one response. |
| No residential exit was free to load this page. Retry shortly, or send the same request without residential to use our standard network. | The request asked for a residential exit and none could take it. There is no fallback here on purpose — answering from a datacentre address is the one thing the request ruled out. Nothing is spent; retry in a moment, or drop the parameter. |
| A selector in the request is not valid CSS. | A selector in page\_options or screenshot\_options could not be parsed as CSS. |
| No visible element on the page matched the selector. | A screenshot's selector matched nothing, or only an element with no size. Nothing is spent. |
| The element named by wait\_for\_selector did not appear on the page. | The page was given 10 seconds for the element and it never arrived. Nothing is spent. |
| The screenshot is too large to return. Lower full\_page\_max\_height, or use format jpeg or webp. | The image came out larger than 20 MB — usually a very long page at device\_scale\_factor 2 or 3, as a PNG. |
| The site's robots.txt disallows this page, and this project is set to follow robots.txt. | The project follows robots.txt and a rule in the site's file covers this page, so it was not fetched. Nothing is spent. See robots.txt for how the rules are read and where the setting lives. |

## Busy pages are waited out for you

Three of those messages — a timeout, a 5xx and a 429 — mean the page is struggling rather than broken, and they are the ones a retry actually fixes. URLpipe does that part itself: when a page starts stalling, requests to that host are spaced further and further apart and the analysis is tried again, for up to ten minutes, before any failure is reported. A crawl of a site that cannot keep up therefore takes longer and still finishes, instead of failing every request after the first few.

What you see is a request that takes longer, not one that fails. A [synchronous](https://urlpipe.dev/docs/async) request may outlive its wait and answer `processing_timeout` with a token — collect the result from [GET /result/:token](https://urlpipe.dev/docs/results) as you would for any long analysis. An async request simply delivers later. The `processing_time_ms` we report covers the whole of it, waiting included.

## Recommended handling

- Check the HTTP status first: `401` → fix credentials, `403` → confirm the account's email address, `429` → back off, `422` → inspect the message.
- Retry timeouts and connection errors with backoff; they're often transient — and by the time one reaches you we have already [waited the page out](https://urlpipe.dev/docs/errors#busy-targets) without success.
- Keep target pages under 10 MB of HTML to avoid `The page is too big to be processed.`.

Source: https://urlpipe.dev/docs/errors
