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

# Errors, retries & idempotency

> Every @goliath-data/sdk error class, the built-in retry policy, and how idempotent sends work

## Errors

Everything throws — there are no error-shaped return values:

| Class                                         | When                                                                                                        |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `GoliathAuthenticationError` (401)            | Missing/invalid/revoked key                                                                                 |
| `GoliathPermissionError` (403)                | Missing scope, or a resource outside your organization                                                      |
| `GoliathBadRequestError` (400)                | Bad body, unknown operation, raw GraphQL, misplaced Idempotency-Key                                         |
| `GoliathIdempotencyConflictError` (409)       | Same Idempotency-Key still in flight — retry with the same key                                              |
| `GoliathRateLimitError` (429)                 | Analytics bucket exhausted; carries `retryAfterSeconds`                                                     |
| `GoliathConcurrencyLimitError` (429)          | Too many in-flight requests on this key                                                                     |
| `GoliathServerError` (5xx)                    | Gateway-side failure                                                                                        |
| `GoliathOperationError`                       | The operation executed and returned `errors` (HTTP 200); carries every shaped error plus any partial `data` |
| `GoliathNetworkError` / `GoliathTimeoutError` | No HTTP response at all                                                                                     |

`GoliathOperationError` is the one to handle in normal application flow — it
means the gateway accepted and ran your call, but the operation itself
reported errors.

## Retries (built in)

* Both `429`s are **pre-execution rejections** — retried for any operation,
  honoring `Retry-After` exactly.
* Network errors, timeouts, and `5xx`s are retried **only when a re-send
  cannot double-fire a side effect**: queries, or mutations sent with an
  `idempotencyKey`.
* A mutation without an idempotency key is **never** re-sent.
* Backoff is exponential with full jitter: 500ms base, 8s cap.

## Idempotency

**Every mutation** accepts `options.idempotencyKey`:

```ts theme={null}
await goliath.contacts.createContact(
  { input: { firstName: 'Jane', lastName: 'Doe', phoneNumbers: ['+13235550100'] } },
  { idempotencyKey: crypto.randomUUID() }
)
```

Always pass one on writes — the client never re-sends an unkeyed mutation, so
without a key a timed-out request is simply lost to you, and retrying it by hand
can double-fire. Passing `idempotencyKey` to a **read** is a compile error,
matching the `400` the gateway returns at runtime.

The key is any string of 1-255 characters (a UUID is the obvious choice). The
gateway reserves it before executing and stores the response when the request
completes; keys expire after 24 hours.

<Note>
  **Detecting replays**

  When a retried write was already executed, the gateway replays the stored
  response instead of re-running it. Replays surface through the `onMeta`
  callback (`meta.replayed === true`) — including replays whose stored body
  carries errors.
</Note>

<Note>
  **When a key is handed back**

  A failure that provably happened *before* the write took effect — bad
  variables, an invalid phone number, a Do-Not-Contact block — releases the key,
  so you can correct the request and retry with the same one. A key whose
  original request is still in flight returns
  `GoliathIdempotencyConflictError` (409): retry shortly with the **same** key to
  pick up its stored result.
</Note>
