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

# Error handling

> How Portal reports errors: the envelope, error types and codes, and retries.

Every error response from Portal uses one JSON envelope, on any endpoint and
for any data source:

```json theme={"system"}
{
  "error": {
    "type": "rate_limit_error",
    "code": "overloaded",
    "message": "Service is overloaded, please try again later",
    "param": "buffer_size",
    "request_id": "0198c3f1-..."
  }
}
```

Two fields carry the meaning, and they answer different questions:

* `type` is the coarse category. It is a closed set of four values. Branch on
  it.
* `code` is the specific cause. Match on it when you handle one case.

`message` is prose for humans. It is not stable and is not part of the
contract, so do not parse it or match on it. `param` appears when the error
concerns one request parameter. `request_id` appears in the body on 5xx
responses; the same id is sent on every response as the `x-request-id` header,
so quote it when reporting a problem.

<Note>
  A `204 No Content` response is not an error. It is the correct answer when
  the requested range has no blocks yet, and it carries no body, no `type`,
  and no `code`.
</Note>

## Error types

| `type`                  | Cause                                    | Retry the same request?                                                |
| ----------------------- | ---------------------------------------- | ---------------------------------------------------------------------- |
| `invalid_request_error` | The request itself                       | No. The same request reproduces the same error. Fix the request.       |
| `rate_limit_error`      | Capacity                                 | Yes, after the interval in `Retry-After`.                              |
| `availability_error`    | A transient Portal or upstream condition | Yes. Honor `Retry-After` when present, otherwise use your own backoff. |
| `api_error`             | A bug in Portal                          | No. Retrying cannot succeed. Report the error with its `request_id`.   |

The split between the last two matters: `availability_error` means a later
attempt can still work, while `api_error` means an invariant broke and a retry
loop cannot help.

## Error codes

| `code`                 | `type`                  | Status                                  | Meaning                                                                                                                                                                                                                              |
| ---------------------- | ----------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `malformed_request`    | `invalid_request_error` | 400                                     | The request or query does not parse or does not validate. `param` names the field when one is at fault.                                                                                                                              |
| `method_not_allowed`   | `invalid_request_error` | 405                                     | Right path, wrong HTTP method. The `Allow` header lists the accepted methods.                                                                                                                                                        |
| `unknown_dataset`      | `invalid_request_error` | 404                                     | The requested dataset does not exist on this Portal.                                                                                                                                                                                 |
| `not_found`            | `invalid_request_error` | 404                                     | The route or resource does not exist.                                                                                                                                                                                                |
| `base_block_mismatch`  | `invalid_request_error` | 409                                     | `parentBlockHash` does not match the canonical parent of the first requested block. The body carries a top-level `previousBlocks` list; see [Blockchain forks](/en/portal/introduction/blockchain-forks) for the recovery procedure. |
| `overloaded`           | `rate_limit_error`      | 529, or 429/529 when proxied            | Portal is at capacity, or a data source refused for capacity. Always carries `Retry-After`.                                                                                                                                          |
| `no_workers`           | `availability_error`    | 503                                     | No worker currently holds the requested data.                                                                                                                                                                                        |
| `retries_exhausted`    | `availability_error`    | 503                                     | Workers were reachable, but every attempt failed transiently until retries ran out.                                                                                                                                                  |
| `upstream_unavailable` | `availability_error`    | 502, or the upstream's 5xx when proxied | A data source that Portal depends on is down.                                                                                                                                                                                        |
| `not_ready`            | `availability_error`    | 503                                     | Portal is starting up or draining. Only `/ready` returns this code.                                                                                                                                                                  |
| `worker_failure`       | `api_error`             | 500                                     | A worker returned something that cannot be right.                                                                                                                                                                                    |
| `internal_error`       | `api_error`             | 500                                     | An invariant that Portal owns was violated.                                                                                                                                                                                          |
| `unclassified`         | `api_error`             | 5xx                                     | An error that escaped classification. Always a bug; report it.                                                                                                                                                                       |

Codes are added over time. Treat an unknown `code` according to its `type`.
That is why the two axes exist: a client that branches on `type` keeps working
when a new code appears.

## Backing off

`Retry-After` is mandatory on every `overloaded` response and is always at
least one second. Honor it:

```http theme={"system"}
HTTP/1.1 529
Retry-After: 10

{"error":{"type":"rate_limit_error","code":"overloaded","message":"..."}}
```

For `overloaded`, the value is in seconds, never an HTTP date. When Portal
proxies a capacity refusal from a data source, it forwards that source's
interval when usable and substitutes its own minimum otherwise.

An `upstream_unavailable` response can also carry a `Retry-After` supplied by
the data source. Honor it when present. Portal does not add or normalize this
value itself.

## Browser clients

Portal exposes `Retry-After` and `x-request-id` through CORS, together with
the stream metadata headers `x-sqd-data-source`, `x-sqd-head-number`,
`x-sqd-finalized-head-number`, and `x-sqd-finalized-head-hash`. The default
Fetch response-header safelist contains none of them, so without this a
browser client would see a status code and nothing else. JavaScript code can
read all of these headers from a `fetch` response.

For the `409 Conflict` recovery procedure, continue with [Blockchain
forks](/en/portal/introduction/blockchain-forks). For the retry loop of a
streaming client, see [EVM stream
continuation](/en/portal/evm/api#stream-continuation) or [Solana stream
continuation](/en/portal/solana/api#stream-continuation).


## Related topics

- [EVM Portal API Reference](/en/portal/evm/api.md)
- [Solana Portal API Reference](/en/portal/solana/api.md)
- [solanaInstructionDecoder](/en/sdk/pipes-sdk/solana/reference/utility-components/instruction-decoder.md)
- [Portal batch processor](/en/sdk/squid-sdk/evm/reference/batch-processor.md)
- [Blockchain forks and recovery](/en/portal/introduction/blockchain-forks.md)
