> ## 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: what Laravel does with each failure, and when to react yourself.

**Nothing to catch.** The SDK retries what is safe, and Laravel's exception handler logs and renders the
rest. Your code calls the method and moves on.

```php theme={"dark"}
public function update(Request $request, string $id)
{
    Attract::testimonials()->updateTestimonial(id: $id, quote: $request->input('quote'));

    return back();
}
```

If the site refuses the quote, the user lands back on the form with the error under `quote`, exactly
like Laravel's own validation.

## What Laravel does with each

> [tab] Logged
>
> Every failure carries its context to the log: the site, method, path, status, error name and the
> request id (never a query, body or header).
>
> | Failure | Reported as |
> | - | - |
> | A 5xx, or no answer | an error, as Laravel reports any exception |
> | A refusal (4xx) | a warning |

> [tab] Rendered
>
> | Exception | Becomes |
> | - | - |
> | `ValidationFailed` | Laravel's validation response: 422 JSON, or a redirect back with the errors, keyed by field path (a refusal with no field under `site`) |
> | `NotFound` | a 404 |
> | `RateLimited` | a 503, "The site acme is busy: try again in 30 seconds.", with `Retry-After` when known |
> | anything else | a 502, "The site acme could not complete the request." |

## Reacting to one case

Catch only the class you want to react to. `AttractException` catches everything the SDK throws,
config errors and refused arguments included.

```php theme={"dark"}
use Fetchski\AttractCmsLaravel\Exceptions\RateLimited;

try {
    Attract::blogPosts()->importBlogPost(markdown: $markdown);
} catch (RateLimited $e) {
    ImportPost::dispatch($markdown)->delay($e->retryAfter() ?? 60);
}
```

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

## The classes

All in `Fetchski\AttractCmsLaravel\Exceptions`.

| What happened | Class | What it carries |
| - | - | - |
| 400 422 | `ValidationFailed` | `errors()`: field path → messages; `toValidationException()` |
| 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, TLS or timeout; `neverConnected()` |
| not sent | `RequestNotSent` | The client refused it first, saying what to change: also an empty id, a blank required filter or an upload named without an extension |
| 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) |
| setup | `SiteConfigurationError` · `UnknownSite` | A bad config value · a slug no site has |

> [card] siteMessage()
>
> The site's own words: what it refused and how to fix it. Show it to people when it says more than
> `errorName()`.

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

> [!NOTE]
>
> A refused argument (an empty id, a blank required filter, an upload named without an extension) is a
> `RequestNotSent` too: status 0, `errorName()` `notSent`, the message naming the route
> (`GET /api/v1/@apostrophecms/blog/{_id}`), the engine's own error as `getPrevious()`. Nothing was sent.

## 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/laravel/paging.md)
>
> One call for a whole list.


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