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

# Errors

> The response envelope, status codes, and how to handle each failure.

## The envelope

Every endpoint returns the same three-field wrapper, success and failure alike.
There is no "sometimes it is a bare object" case, so you can parse one shape.

<CodeGroup>
  ```json Success theme={null}
  {
    "success": true,
    "message": "ok",
    "result": { }
  }
  ```

  ```json Failure theme={null}
  {
    "success": false,
    "message": "You do not have enough credits for this request.",
    "result": { "code": "insufficient_credits" }
  }
  ```
</CodeGroup>

`message` is written for a person and is safe to surface in a UI. `result.code`
is the stable identifier to branch on. **Match on `code`, never on `message`**,
which may be reworded.

<Note>
  Health probes (`/health`, `/healthz`, `/readyz`) and the billing webhook are
  the only endpoints that are not enveloped.
</Note>

## Status codes

| Status | Meaning                                       | What to do                                                    |
| ------ | --------------------------------------------- | ------------------------------------------------------------- |
| `400`  | The request body or a parameter is invalid    | Fix the request; retrying will not help                       |
| `401`  | Missing, malformed, expired, or revoked token | Check the `Authorization` header                              |
| `402`  | Not enough credits                            | Top up, or wait for the monthly grant                         |
| `404`  | No such resource                              | Check the id                                                  |
| `429`  | Rate limit exceeded                           | Back off and retry, see [Rate limits](/reference/rate-limits) |
| `5xx`  | Something failed on our side                  | Retry with backoff                                            |

## Handling errors

```typescript theme={null}
const res = await fetch("https://api.truscan.co/api/search/query", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TRUSCAN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ query }),
});

const body = await res.json();

if (!body.success) {
  switch (body.result?.code) {
    case "insufficient_credits":
      return topUpAndRetry();
    case "rate_limited":
      return retryAfterBackoff();
    default:
      throw new Error(body.message);
  }
}

return body.result;
```

## Retrying safely

* `429` and `5xx` are worth retrying with exponential backoff and jitter.
* `400`, `401`, and `402` are **not**. They fail identically until you change
  something.
* Searches are idempotent in effect but **not** in billing: a retried search is
  a new charge unless it hits the cache. Prefer re-fetching a known result with
  `GET /api/search/searches/{id}`, which is free.

## Partial failures

`POST /api/search/contents` returns `200` even when some URLs fail. Check each
entry for an `error` field before reading its content. See
[Page content](/search/contents#partial-failures).
