# Errors

> Every error has the same JSON shape, an HTTP status and a stable code.

When a request fails, the API answers with an HTTP error status and this body:

```json
{
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "Not enough credits for this run."
  }
}
```

Use `code` in your program; it stays stable. `message` is meant for people and can change.

## Error codes [#error-codes]

| Status | Code | What happened | What to do |
| --- | --- | --- | --- |
| 400 | `INVALID_INPUT` | The body is not valid JSON, or it does not match the tool's input fields. The message names the field. | Fix the input. Check the fields in the [tool reference](/docs/tools). Nothing was charged. |
| 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is unknown or revoked. | Send a valid key. See [Authentication](/docs/authentication). |
| 402 | `INSUFFICIENT_CREDITS` | Your balance cannot cover the credits this run reserves. | Lower `maxResults` so the reservation fits your balance. See [Credits](/docs/credits). |
| 404 | `NOT_FOUND` | The tool ID does not exist, or the run does not exist in your organization. | Check the ID. `GET /api/v1/tools` lists all tool IDs. |
| 429 | `RATE_LIMITED` | This key used up its burst of 120 requests and its 60 per minute. | Wait the seconds in the `Retry-After` header (also `retryAfterSeconds` in the body), then retry. See [Authentication](/docs/authentication#rate-limit). |
| 500 | `INTERNAL_ERROR` | Something broke on our side. If a run had already started, the body also contains `runId`. | Retry later. With a `runId`, fetch the partial results that were charged: `GET /api/v1/runs/{runId}/results`. |
| 503 | `SERVICE_UNAVAILABLE` | A part of the service is temporarily not available. | Retry later. |
| 503 | `TOOL_UNAVAILABLE` | Our latest automatic checks of this tool failed, so runs are paused. Nothing was reserved or charged. | Retry later. The [status page](/status) shows when the tool works again. |

## Runs that stop early [#runs-that-stop-early]

A run that stops early is not an HTTP error. A sync run that hits its deadline still answers `200`: the results it collected are in the body, and `run.error` explains why it stopped, for example:

```json
{ "status": "succeeded", "resultsCount": 7, "error": "Stopped at the 25 s deadline. Partial results were charged and saved." }
```

If the target website blocks a run, `run.error` names the reason code. When nothing could be fetched at all, the run is `failed` (not an empty success) and nothing is charged:

```json
{ "status": "failed", "resultsCount": 0, "error": "No results: 3 items could not be fetched (BLOCKED). The target site blocked our requests, so this is not an empty result. Nothing was charged." }
```

When only some items were blocked, the run is `succeeded`, the results you got are charged, and `run.error` says how many items could not be fetched. See [Runs](/docs/runs) for all statuses.
