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

# Errors

> HTTP status codes returned by ScrapeUnblocker and what they mean.

ScrapeUnblocker uses standard HTTP status codes. Status codes in the `2xx` range indicate success. Codes in the `4xx` range indicate an issue with the request (bad parameters, blocked by the target site, etc.). Codes in the `5xx` range indicate either an upstream issue at the target site or, rarely, a problem on our side.

## Status codes

| Code  | Meaning                                                                                                | Where to look                                                                                       |
| ----- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| `200` | Success                                                                                                | Response body contains the requested content                                                        |
| `400` | Invalid URL or unsupported scheme, or the `x-scrapeunblocker-key` header is missing entirely           | Check your `url` parameter is well-formed and uses `http`/`https`, and that you send the key header |
| `401` | Authentication problem - the key is not recognised, or the account behind it has no valid subscription | See [401 unauthorized](#401-unauthorized) below and [Authentication](/authentication)               |
| `402` | Billing problem - quota exceeded, credit limit exceeded, or repeated payment failures                  | See [402 payment required](#402-payment-required) below                                             |
| `403` | Blocked by target site's bot protection on every available bypass path                                 | Try a different `proxy_country`, or see [handling failures](/guides/handling-failures)              |
| `404` | No image element found (only on `/getImage`)                                                           | The page loaded but contained no `<img>` tag                                                        |
| `408` | Browser run timed out (only on `/getImage`)                                                            | Retry, or increase `method_timeout` if applicable                                                   |
| `422` | Validation error - missing required field or wrong type                                                | The response body contains a `detail` array pinpointing the problem field                           |
| `503` | Upstream origin returned a server-side outage page                                                     | The target site is down. Not a bot block. Retry later.                                              |
| `504` | SERP fetch timed out (only on `/serpApi`)                                                              | Retry. If persistent, lower `pages_to_check` or pick a different `proxy_country`                    |

## 401 unauthorized

A `401` means the request was rejected at our edge, before it ever reached the scraping engine. Because nothing was scraped, a `401` does not count against your quota and is never billed.

Unlike validation errors, `401` responses have a plain-text body (`Content-Type: text/plain`), not JSON. There are two distinct messages, and they mean different things:

| Response body           | Meaning                                                                     |
| ----------------------- | --------------------------------------------------------------------------- |
| `Unauthorized`          | The key you sent is not recognised                                          |
| `No valid subscription` | The key is recognised, but the account behind it has no active subscription |

### `Unauthorized`

```
HTTP/1.1 401 Unauthorized
Content-Type: text/plain

Unauthorized
```

The value of your `x-scrapeunblocker-key` header does not match any known key. Common causes:

* **A typo, or a truncated copy-paste.** Keys are long; make sure the whole value was copied.
* **Trailing whitespace or a newline** in the header value - especially when the key is read from a file rather than an environment variable.
* **An empty header value.** Sending `x-scrapeunblocker-key` with nothing after it counts as an unknown key, not as a missing header.
* **A rotated or revoked key.** After you generate a new key in the [dashboard](https://app.scrapeunblocker.com), the old one stops working once its short grace period ends.
* **Environment mismatch.** Production keys only work against `api.scrapeunblocker.com`. A key issued for one environment sent to another is an unknown key there.

### `No valid subscription`

```
HTTP/1.1 401 Unauthorized
Content-Type: text/plain

No valid subscription
```

The key itself is valid, but the account it belongs to currently has no subscription period covering today - for example the free trial has ended and no plan was chosen, or a plan lapsed and was not renewed. Pick a plan in the [dashboard](https://app.scrapeunblocker.com) and access resumes within about a minute; no key change is needed.

<Note>
  A billing problem on an **active** subscription returns `402`, not `401` - quota exceeded, credit limit exceeded, or a card that failed repeatedly. `401` is strictly about who you are, `402` about what you owe.
</Note>

### Missing header returns `400`, not `401`

If you omit the `x-scrapeunblocker-key` header entirely, the response is `400 Bad Request` with the body `Missing x-scrapeunblocker-key`. This is deliberate: it separates "you forgot to authenticate" from "you authenticated, and it was rejected", so client code can tell a wiring bug from a credential problem.

```
HTTP/1.1 400 Bad Request
Content-Type: text/plain

Missing x-scrapeunblocker-key
```

## 402 payment required

A `402` means your key and account are recognised and in good standing as credentials - the request was stopped for a **billing** reason. Like `401`, it is refused at our edge before anything is scraped, so a `402` consumes no quota and is never billed.

The body is plain text (`Content-Type: text/plain`), not JSON. There are three messages:

| Response body                            | Meaning                                                            | Fix                                                |
| ---------------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------- |
| `Quota exceeded`                         | You have used every request your plan allows this billing period   | Upgrade your plan, or wait for the period to reset |
| `Credit limit exceeded`                  | Your unpaid balance has grown past your account's credit limit     | Pay the outstanding invoice                        |
| `Payment failed - update payment method` | A card payment for an open invoice has failed three times in a row | Update your card, then pay the invoice             |

If more than one applies, the most serious wins: payment failure outranks credit limit, which outranks quota.

<Note>
  All three clear themselves. Our load balancer refreshes key statuses about once a minute, so once you upgrade or the invoice is paid, access comes back within roughly a minute - no key change, no support ticket, no redeploy.
</Note>

### `Quota exceeded`

```
HTTP/1.1 402 Payment Required
Content-Type: text/plain

Quota exceeded
```

Your usage for the current billing period has passed your plan's quota. If your plan allows overages, this only fires once you are past quota **plus** the overage allowance - inside that band requests still succeed and the extra usage is invoiced. Any active coupon credit is spent before plan quota, so a key with remaining credit is never quota-blocked.

The counter resets at the start of your next billing period, which starts on your subscription's anniversary day, not on the first of the month. To get moving sooner, upgrade in the [dashboard](https://app.scrapeunblocker.com) - the new quota applies on the next status refresh. Current usage against quota is visible in the dashboard, so this is the one `402` you can see coming.

### `Credit limit exceeded`

```
HTTP/1.1 402 Payment Required
Content-Type: text/plain

Credit limit exceeded
```

This applies to accounts that accrue usage-based charges. We add up what you currently owe - the amount remaining on your open invoices, plus metered usage already consumed on active subscriptions but not yet invoiced - and compare it against your account's credit limit. Past the limit, the key is paused.

When this triggers we also finalise and attempt payment on the outstanding invoices automatically, so in the common case where your card is good it settles itself and access returns within about a minute. If payment does not go through, pay the invoice from the [dashboard](https://app.scrapeunblocker.com). A higher credit limit can be arranged through [support](https://scrapeunblocker.com/contact).

### `Payment failed - update payment method`

```
HTTP/1.1 402 Payment Required
Content-Type: text/plain

Payment failed - update payment method
```

An invoice on your account is open and its payment has been attempted and declined three times. Those attempts are our payment provider's automatic retries spread over several days, so reaching this state means a card has been failing for a while - typically expired, cancelled, or short of funds.

Update your payment method in the [dashboard](https://app.scrapeunblocker.com) and settle the open invoice. As soon as the invoice is paid the block lifts on the next status refresh, within about a minute.

<Warning>
  Subscribing to a new plan does **not** clear this on its own. The old unpaid invoice stays open, so the block stays in place until that specific invoice is paid, even if the new subscription is active and paid for.
</Warning>

### `402` versus its neighbours

| Code  | It means                                                                                                                                |
| ----- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `401` | We do not accept your credentials - unknown key, or no subscription at all. See [401 unauthorized](#401-unauthorized)                   |
| `402` | We accept your credentials; the account owes money or is out of quota                                                                   |
| `429` | Nothing is wrong with the account - you are simply sending requests faster than your plan's rate limit. See [rate limits](/rate-limits) |

## 422 validation error shape

When you send an invalid request body, `/getPageSource`, `/serpApi`, and `/getImage` all return a structured validation error:

```json theme={null}
{
  "detail": [
    {
      "loc": ["query", "url"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}
```

`loc` is the path to the problem field. `msg` is human-readable. `type` is a stable machine-readable identifier.

## 403 - blocked vs. invalid key

A `403` from ScrapeUnblocker **never** means your API key is wrong. Invalid keys return `401`. A `403` always means: the target site blocked us on every bypass route we tried.

When you see `403`:

1. **Try a different `proxy_country`.** Some sites geo-fence or geo-rotate their bot protection. A US site may be unreachable from EU IPs and vice versa.
2. **Wait and retry.** Rate-based blocks expire after a few minutes.
3. **Contact support** if the same URL repeatedly fails - we may need to add a custom plugin for that domain.

More detail in the [handling failures guide](/guides/handling-failures).

## Retries and idempotency

All three endpoints are safe to retry. Requests are idempotent in the sense that retrying with the same parameters does not double-charge or create duplicate state on your account. We recommend exponential backoff for transient `5xx` errors:

```python theme={null}
import time
import requests

def fetch_with_retry(url, max_attempts=3):
    for attempt in range(max_attempts):
        r = requests.post(
            "https://api.scrapeunblocker.com/getPageSource",
            params={"url": url},
            headers={"x-scrapeunblocker-key": "YOUR_API_KEY"},
            timeout=120,
        )
        if r.status_code == 200:
            return r
        if r.status_code in (503, 504) and attempt < max_attempts - 1:
            time.sleep(2 ** attempt)
            continue
        r.raise_for_status()
    return r
```
