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

# Errors

> Nothing to catch for retries: one class per failure, and what the SDK retries for you.

**Nothing to catch for retries.** The SDK retries what is safe and throws the rest as the classes below;
catch only what you want to react to. Every one extends `AttractError`, so `instanceof AttractError`
catches anything the SDK throws.

```ts theme={"dark"}
import { NotFound } from '@fetchski/attract-cms-sdk';

const blog = await site.blogPosts().getBlog({ id }).catch((error: unknown) => {
  if (error instanceof NotFound) return null;
  throw error;
});
```

> [!TIP]
>
> For a missing document you don't need that at all: `findBlog()` (and every `find…()`) resolves `null`
> for a 404.

## The classes

All exported from `@fetchski/attract-cms-sdk`; check with `instanceof`.

| What happened | Class | What it carries |
| - | - | - |
| 400 422 | `ValidationFailed` | `errors`: field path → messages |
| 401 | `AuthenticationFailed` | A wrong or revoked key, or a missing, stale or reused signature |
| 403 | `Forbidden` | The scope or allowlist that refused it |
| 404 | `NotFound` | No such document or route, or one the key may not see |
| 409 | `Conflict` | `data` may name the existing record |
| 429 | `RateLimited` | `retryAfter`, after the client's own waits |
| 5xx | `SiteError` | The site failed; its logs say why |
| no answer | `SiteUnreachable` | Network or timeout; `neverConnected` |
| not sent | `RequestNotSent` | The client refused it first, saying what to change: also an empty id or a blank required value (`cause`: the engine's `RequiredError`) |
| drifted | `SiteRequestFailed` | A 2xx that doesn't match the SDK, a redirect, or not JSON: `reason` names the field |
| job | `JobFailed` · `JobTimedOut` | `job` (failed or canceled) · `jobId` (still running) |

> [card] siteMessage
>
> The site's own words: what it refused and how to fix it. Show it to people when it says more than
> the error's name.

> [card] reason
>
> The client's words when the site's are missing. It names hosts and settings: for logs and
> developers, never for people.

Every one carries `site`, `method`, `path` (no query), `status`, `errorName`, `data`, `response` and
`body`. Log its `message`, which leads with whichever of the two is set.

> [!NOTE]
>
> A few mistakes are thrown before any call: a bad client option (`SiteConfigurationError`), a missing
> or empty required argument (`RequestNotSent`, naming the route, `GET /api/v1/@apostrophecms/blog/{_id}`),
> a slug no site has (`UnknownSite`), all `AttractError`s, and a misused `request()` (a `TypeError`).

## What is retried

A call that failed in a way that is safe to repeat is sent again, so your app never has to.

```mermaid theme={"dark"}
flowchart TD
    A[A call fails] --> B{429?}
    B -- yes --> C[Wait Retry-After, then retry]
    B -- no --> D{A read?}
    D -- yes --> E{No answer, 502, 503 or 504?}
    E -- yes --> F[Retry after 0.5 s, then 1 s]
    E -- no --> G[Throw]
    D -- no --> H{Never reached the site?}
    H -- yes --> F
    H -- no --> G
```

> [card] Rate limits
>
> Any method. Waits `Retry-After` (1 s when unsaid); a wait over a minute is thrown for you to schedule.

> [card] Reads
>
> No answer, or a 502, 503 or 504: after 0.5 s, then 1 s.

> [card] Writes
>
> Only when they never reached the site. Never after a timeout or a 5xx.

> [!WARNING]
>
> A write that timed out may still have landed. Check before you send it again: a resend is a new write.

`retries` (2 by default) caps the extra attempts, and `0` turns them off. Each attempt is signed afresh.

> [card] [Next: paging](https://docs.fetchski-attract.com/typescript/paging.md)
>
> One loop over a whole list.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.