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

# Errors

> Every failure is a PoolError case you can switch over.

| Case | When | What to do |
| - | - | - |
| `invalidAppKey` | The key is malformed, revoked, or not allowed for this bundle ID. | Check the key and the bundle IDs in the dashboard. |
| `permissionDenied` | The person declined photo access. | Explain the benefit and link to Settings. |
| `grantRevoked` | The grant was revoked. | Stop, or ask again when the person returns to the feature. |
| `grantExpired` | The grant's expiry passed. | Request a new grant. |
| `grantNotFound` | No such grant on this device. | Request a grant. |
| `operationNotGranted` | The grant does not include this operation. | Request a grant with it. |
| `invalidRequest` | An access request names nothing, or sources the configuration leaves out. | Fix the request. The message says which part. |
| `unsupportedQuery` | The query asks for something version 1 cannot do. | The message says what. |
| `invalidCursor` | The cursor belongs to an index that was rebuilt. | Start from the first page. |
| `notFound` | The evidence or thing does not exist or is outside the grant. | Treat as gone. |
| `offline` | A service call could not connect. | Nothing. The on-device engine keeps working. |
| `rateLimited` | The service asked the SDK to wait, or the app used its allowance. | Nothing for indexing: the SDK waits by itself. `ask` and `profile` throw it while the wait lasts, with the seconds left in `retryAfter`. |
| `serviceUnavailable` | Pool's service cannot do this right now. | Retry after `retryAfter` seconds. The on-device engine keeps working. |
| `service` | The service answered with an unexpected status. A 401 means [App Attest](https://docs.pool.day/app-attest.md) is enforced and this build could not attest, for example a development build with a live key. | Retry later. For a 401, check the App Attest capability and use a test key in debug builds. |
| `storage` | The index could not be read or written. | `rebuildIndex()` recovers it. |
| `modelsUnavailable` | The engine's models could not be listed, downloaded or verified. | Nothing. PoolKit works without them and tries again later. |
| `cloudAssistNotAllowed` | `ask` or `profile` needs cloud assist, and the configuration or the grant does not allow it. | Ask for a grant with `allowsCloudAssist`, or hide the feature. |
| `invalidSchema` | A profile schema is outside the supported subset or over a limit. | Fix the schema. The message says which part. |

## Retry or not

Every `PoolError` also has three properties, so you can handle a whole class of failures without listing cases:

```swift theme={null}
do {
    let answer = try await pool.context.ask(question, using: grant)
    show(answer)
} catch let error as PoolError {
    if error.retryable {
        retry(after: .seconds(error.retryAfter ?? 30))
    } else {
        showError(error)
    }
    log(error.serverCode)  // "rate_limited", or nil when the failure happened on the device
}
```

| Property | Meaning |
| - | - |
| `retryable` | True for `offline`, `rateLimited`, `serviceUnavailable`, `modelsUnavailable`, and `service` when the status is a 5xx or 408. The same call can work later. |
| `retryAfter` | Seconds to wait, set only for `rateLimited` and `serviceUnavailable`. |
| `serverCode` | The service's error code when the service refused the call: `invalid_key` (`invalidAppKey`), `invalid_request` (`invalidRequest`), `invalid_schema` (`invalidSchema`), `rate_limited` (`rateLimited`), `service_unavailable` (`serviceUnavailable`) or `service` (`service`). `nil` for failures that can only happen on the device. |

An app that used its month's cloud assist units or connected installs gets `rateLimited`, with a `retryAfter` that runs to when the allowance renews. There is no separate case for `quota_exceeded` or `plan_limit`; the service's [error body](https://docs.pool.day/service.md) still names them, and the dashboard shows usage.

From React Native, a rejected promise has `code` (the case name) and `message` (the case with its values, such as `rateLimited(retryAfter: 60)`), and `PoolKit.errorDetails(error)` returns `{ code, message, retryable, retryAfter, serverCode }`. Stream `onError` handlers get the same fields. See [React Native and Expo](https://docs.pool.day/expo.md#errors).

Being offline never breaks the engine. Only a key that the service has rejected stops the client, and it stays stopped until the app opens with a good key.


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