# Troubleshooting

> What you see, why it happens and what to change. 401, 402, 403, 404, 409, 422, 429, 503, empty pages, schema extraction and MCP setup.

**TL;DR**: find what you see in the list below. Each entry says why it
happens and what to change. An error is never charged. Every answer carries
`X-Request-Id`: quote it if you write to support. The full list of codes is in
[Error codes](/reference/errors).

Print the status, our headers and the body of any call with `-sS -D -`:

```sh
curl -sS -D - -o page.out "https://get.megalania.io/v1?url=https://docs.megalania.io/start/quickstart" \
  -H "Authorization: Bearer $MEG_API_KEY"
```

## The key

### `401 bad_token`

The message says which of five it is:

- "No API key was sent": no `Authorization` header reached us.
- "The Authorization header has no Bearer": write `Authorization: Bearer <key>`, not just the key.
- "The key was sent in an X-Api-Key header": we read only `Authorization`.
- "A key in the URL is refused": remove `api_key` from the URL.
- "The API key is wrong, expired or deleted": the header is right, the key is not.

- Run `echo ${MEG_API_KEY:0:7}`. It must print `meg_sk_`. Empty means the
  shell that runs the call has no key: `export` it in that shell.
- Send `Authorization: Bearer $MEG_API_KEY`. A key in the URL
  (`?api_key=`) is refused, even next to the header.
- Copy-paste can add a space or a line break. Paste the key between single
  quotes, as in the [quickstart](/start/quickstart).
- Still 401? Make a new key on the **API keys** page. We store only a hash,
  so a lost key cannot be shown again.

### `403 wrong_scope`

The key has the wrong scope. A key that starts with `meg_ak_` is an account
key: it manages the account and cannot scrape. Use a scrape key, `meg_sk_`.
The other way round, a scrape key cannot manage the account; only
`GET /v1/account` takes both.

### `403 notice_required`

You have not accepted the current beta notice. Sign in to the dashboard, read
the notice and press **I agree**. Then make the key, or connect the agent,
again.

## The address

### `404` with an empty body on `/v1/batches`

Batches live on the API host, `https://api.megalania.io`. The scrape host,
`https://get.megalania.io`, serves only `/v1`, `/v1/map`, `/v1/tier`,
`/v1/stats/domain` and `/v1/geos`. Change the host, keep the path.

### `400 bad_parameter`

Read `error.field`: that parameter is missing, misspelled, or has a value we
do not accept. The message lists the values that work. The four we see most:

- **An unknown parameter.** We never ignore one. `render=true` from an old
  script is a 400 naming `render`. Send `engine=browser`.
- **A number with a fraction or a sign.** `timeout`, `max_credits` and
  `max_age` take a whole JSON number or a string: `"timeout": 30` and
  `"timeout": "30"` both work; `30.5` or `-1` is a 400 naming the key.
- **A query string we cannot read.** On `GET /v1`, put `url` last and paste
  it as it is, or percent-encode it.
- **`Idempotency-Key` too long or not ASCII.** Send 1 to 128 printable ASCII
  characters. A UUID works.

### `415 unsupported_media_type`

Every `POST` takes JSON. Add `-H "Content-Type: application/json"`, even
with an empty `{}` body.

## The page

### `200`, but the page is empty or is only a script shell

Look at `X-Hint`. `needs_js` means the page builds its content with
JavaScript. Two fixes, cheapest first:

1. Ask for `"response_types": "page_data"`. Many pages carry their data as
   JSON in the HTML, at the HTTP price.
2. Send `"engine": "browser"`. We render the page in Chrome. It costs more:
   see [Credits and pricing](/guides/credits).

No `X-Hint` and still too little text? A short answer can still be a shell.
Try the same two steps.

### `422 blocked`

The site blocked every route we tried, and there is nothing to show. It is
free: `X-Billing: blocked` says so. A block is always this 422, never a
`200`. Read the headers:

- `X-Hops` lists each try, like `h:dc:chrome=3826,h:res:chrome=4989`.
- `X-Untried` names the routes we did not try, such as `c:res`. The climb
  stops early when the next step costs more than it is likely to win. To try
  one yourself, ask for it by name:

  | route | send |
  | -- | -- |
  | `h:res` | `"engine": "http", "proxy_tier": "residential"` |
  | `c:dc` | `"engine": "browser"` |
  | `c:res` | `"engine": "browser", "proxy_tier": "residential"` |

  A residential or browser page costs more: see
  [Credits and pricing](/guides/credits).
- `X-Untried-Reason: budget` means residential routes are unavailable right
  now, so the climb skipped every residential route and tried the next open
  one. A skipped route is never charged.
- No `X-Untried` means every route was tried. Retrying will not help today.
- `X-Target-Status` is what the site answered, often `403`.

### `X-Target-Status: 404` (or any status under 500)

The site itself answered with that status, and that is the result: you pay
for it like any page. Check the URL in a browser.

### `422 convert_cap` or `422 upstream_cap`

The page is too large to convert, or pulled more data than one request may
use. It is refunded. Ask for `html` instead of `markdown`, or fewer
`response_types`, or fetch a smaller page.

### `422 document_unreadable`

The `url` answered with a PDF, Word, PowerPoint or Excel file we cannot read.
It holds only scanned images, it needs a password, it is damaged, or reading
it ran past our memory or time limits. It is free. We do not run OCR yet, so
a scanned PDF stays unreadable. A file over 64 MiB unpacked, or over 500
slides, is a `422 convert_cap` instead.

### A host name that does not exist

Today you get `503 fetch_error`, free, after we tried the other routes.
The `422 unknown_host` code is in the error list, but the DNS check behind it
is switched off, so it is not sent yet. See [errors](/reference/errors).

- Check the spelling of the host, and whether you left out a `www.`.
- A new domain can take a few minutes to appear in DNS. Retry later.

### `X-Truncated` on a `200`

The page reached the most bytes this request may pull, and we kept the part
that fit. `max_credits`: raise it, or remove it, for the whole page.
`byte_cap`: the route's or your plan's byte cap, or your balance, stopped it.

## Money and limits

### `402 insufficient_credits`

Your balance is lower than the cheapest route for this page. In the beta,
write to support@megalania.io and we add credits.
`proxy_tier=datacenter` makes a request cheaper.

### `400 max_credits_too_low`

Every route for this page costs more than your `max_credits`. The message
names the lowest price on that site. Raise `max_credits` to at least that.
Run [check a site first](/guides/credits#check-a-site-first) before a big run:
it is free.

### `429` codes

| code | what to do |
| -- | -- |
| `over_concurrency` | Too many requests at once. Wait the seconds in `Retry-After`. |
| `rate_limited` | Batches, monitors and the account API take 2 calls a second. Wait the seconds in `Retry-After`. |
| `target_rate_limited` | The site is rate limiting. We hold your requests to it until `Retry-After` ends, so you do not pay for more 429s. |
| `daily_cost_cap` | Today's usage limit is used up. It resets at 00:00 UTC. |
| `period_cost_cap` | Your grant's usage limit is used up. In the beta, write to support@megalania.io. |

### `409 replayed`

You already used this `Idempotency-Key` in the last 24 hours, and the first
call went through. Do not retry. Use a new key for a new request.

### `503 llm_unavailable` with `schema`

Schema extraction could not run: it is not set up right now, or the model
did not answer in time. The extraction is always free. What about the page:

- **The body carries `markdown`.** We fetched the page, and you paid for the
  page only: `X-Credits-Charged` and `credits` in the body say how much,
  and `llm_credits` is 0. The page is there, so you can read it yourself.
- **No `markdown` in the body.** We stopped before the fetch. Nothing was
  charged.

Retry later. `extract` with CSS rules needs no model and works now:
[Fetch, extract and lists](/guides/fetch) shows how.

### `503 busy`, `timeout` or `fetch_error`

These are refunded. Wait the seconds in `Retry-After` if there is one, and
send the same request with the same `Idempotency-Key`. You are charged once.
`timeout`: raise `timeout`, up to `"90"`.

## MCP

### The client shows no tools, or every call is `401`

- Claude Code: type `/mcp`, pick `megalania` and finish the sign-in in the
  browser. Press **Approve** on the Megalania page.
- Cursor and VS Code: the key goes in `headers`, as
  `"Authorization": "Bearer meg_sk_..."`. Restart the client after you edit
  `mcp.json`.
- The server address is `https://mcp.megalania.io` with nothing after it. A
  `?` in the address is a 400.

Check the server from your shell. A 200 with the tools in `result.tools`
means your key and the server are fine, and the problem is the client's
config:

```sh
curl -sS -X POST https://mcp.megalania.io/ \
  -H "Authorization: Bearer $MEG_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: tools/list" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"curl","version":"8"},"io.modelcontextprotocol/clientCapabilities":{}}}}'
```

### A tool result says `ok: false`

Do what `next_step` says. It names the argument to change, or tells the
agent to call again later. Blocked pages and errors are free.

## Still stuck

Report it at `https://app.megalania.io/report`, or write to
support@megalania.io. Send the
`X-Request-Id`, what you sent (without the key) and what you expected. We
never need your key.
