> ## 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 and rate limits

> What each answer means, and what to do about it.

```json 400 invalid wrap theme={"dark"}
{
  "name": "invalid",
  "message": "title: required",
  "data": {
    "errors": [
      { "path": "title", "message": "required" }
    ]
  }
}
```

```json 429 tooManyRequests wrap theme={"dark"}
{
  "name": "tooManyRequests",
  "message": "This API key’s rate limit is used up for this minute.",
  "data": { "retryAfter": 12 }
}
```

Every error answer has the same shape. Its `message` is written for people: what is wrong, and how
to fix it.

**name** `string` *required*

The error's name: `invalid`, `required`, `unauthorized`, `forbidden`, `notfound`, `conflict`,
`tooManyRequests` or `error`.

**message** `string`

What is wrong, and how to fix it.

**data** `object`

The details, by error.

**properties**

**errors** `object[]`

One entry per field problem, each with its `path` and `message`.

**retryAfter** `integer`

On a `429`: the seconds to wait.

## Status codes

| Status | What happened | What to do |
| - | - | - |
| 400 422 | A value was refused. | Fix the fields `data.errors` names. |
| 401 | The key is wrong or revoked, or a signature is missing, stale or reused. | Check the key and its secret; [Signing requests](https://docs.fetchski-attract.com/signing.md) lists each message. |
| 403 | The key may not do this: its scopes or its allowlist. | Read the message: it names what is missing. |
| 404 | No such document or route, or one the key may not see. | Check the id and the key's scopes. |
| 409 | The write conflicts with the document's current state. | Read the current document, then try again. |
| 429 | The key's rate limit is used up. | Wait `Retry-After` seconds. |
| 5xx | The site failed. | Try again later. |

> [!TIP]
>
> Show `message` to people when it says more than the error's `name`. A bare `404` for an unknown id is
> just `notfound`: say "not found" in your own words there.

## What the SDKs retry

```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
```

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

## In the SDKs: nothing to catch

The SDKs retry what is safe, then throw what is left as one class per failure, carrying the site's own
words. You catch only what you want to react to.

> [tab] Laravel
>
> Laravel's exception handler takes care of the rest: every failure is logged with its site, route,
> status and request id, and rendered properly. A refused value becomes Laravel's own validation
> response, so a controller needs no `try`:
>
> ```php theme={"dark"}
> use Fetchski\AttractCmsLaravel\Facades\Attract;
>
> // 422 JSON, or a redirect back with the errors, keyed by field
> Attract::testimonials()->updateTestimonial(id: $id, quote: $request->input('quote'));
> ```
>
> `NotFound` renders a 404, `RateLimited` a 503 with `Retry-After`, anything else a 502.

> [tab] TypeScript
>
> Catch the one class you want to react to, and let the rest through:
>
> ```ts theme={"dark"}
> import { NotFound } from '@fetchski/attract-cms-sdk';
>
> const post = await site.blogPosts().getBlog({ id }).catch((error: unknown) => {
>   if (error instanceof NotFound) return null;
>   throw error;
> });
> ```
>
> Every list has a `find…()` that does exactly this for a 404.

> [card] [Every class, and what it carries](https://docs.fetchski-attract.com/laravel/errors.md)
>
> The Laravel and TypeScript tabs each list them, with what is retried.


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