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

# Error Handling

> Error types, retry behavior, and recovery patterns.

## Error Hierarchy

The Python SDK has a detailed error hierarchy with specific exception types for common HTTP status codes:

```python theme={null}
from keystoneos.exceptions import (
    APIError,              # Base for all API errors
    AuthenticationError,   # 401
    ForbiddenError,        # 403
    NotFoundError,         # 404
    ConflictError,         # 409
    ValidationError,       # 422
    RateLimitError,        # 429
    ServerError,           # 5xx
    WebhookVerificationError,
)

try:
    settlement = await client.settlements.get("non-existent-id")
except NotFoundError:
    print("Settlement does not exist")
except ConflictError as e:
    print(f"State conflict: {e.message}")
except ValidationError as e:
    print(f"Invalid input: {e.detail}")
except RateLimitError as e:
    print(f"Rate limited. Retry after: {e.retry_after}s")
except ForbiddenError:
    print("Insufficient permissions")
except AuthenticationError:
    print("Check your M2M credentials")
except ServerError:
    print("KeyStone API issue")
except APIError as e:
    print(f"API error {e.status_code}: {e.message}")
```

| Exception                  | Status | Properties                                          | Description                         |
| -------------------------- | ------ | --------------------------------------------------- | ----------------------------------- |
| `APIError`                 | any    | `message`, `status_code`, `detail`, `response_body` | Base class for all API errors       |
| `AuthenticationError`      | 401    | `message`                                           | Token fetch failed or invalid       |
| `ForbiddenError`           | 403    | `message`, `detail`                                 | Missing scope or suspended platform |
| `NotFoundError`            | 404    | `message`                                           | Resource not found                  |
| `ConflictError`            | 409    | `message`, `detail`                                 | State conflict                      |
| `ValidationError`          | 422    | `message`, `detail`                                 | Request validation failed           |
| `RateLimitError`           | 429    | `retry_after`, `detail`, `response_body`            | Rate limit exceeded                 |
| `ServerError`              | 5xx    | `message`, `status_code`                            | Server-side error                   |
| `WebhookVerificationError` | -      | `message`                                           | Signature verification failed       |

***

## Retry Behavior

Automatic retries on 429 and 5xx with exponential backoff and jitter:

| Setting                  | Default                     | Description                      |
| ------------------------ | --------------------------- | -------------------------------- |
| `max_retries`            | `3`                         | Max retry attempts               |
| `base_delay`             | `0.5`                       | Base delay in seconds            |
| `max_delay`              | `30.0`                      | Maximum delay in seconds         |
| `retryable_status_codes` | `{429, 500, 502, 503, 504}` | Which status codes trigger retry |

**Strategy**: `base_delay * 2^attempt * random(0.5, 1.5)` (jitter prevents thundering herd)

**429 responses**: Respects the `Retry-After` header if present.

**Non-retryable**: 4xx errors (except 429) fail immediately.

***

## Recovery Patterns

### Catching specific errors

```python theme={null}
try:
    await client.settlements.submit_compliance_decision(
        settlement_id, {"decision": "approve"}
    )
except ConflictError:
    # Settlement is no longer in COMPLIANCE_CHECKING state
    settlement = await client.settlements.get(settlement_id)
    print(f"Current state: {settlement.state}")
except ForbiddenError:
    # Missing compliance:write scope
    print("This environment doesn't have compliance permissions")
```

### Rate limit handling

The SDK retries 429 automatically. For custom handling after exhaustion:

```python theme={null}
try:
    await client.settlements.list()
except RateLimitError as e:
    # All retries exhausted
    print(f"Rate limited. Retry after {e.retry_after} seconds")
    await asyncio.sleep(e.retry_after or 60)
    # Try again...
```

### Idempotent retries

```python theme={null}
key = client.generate_idempotency_key()

# Safe to call multiple times - same key returns existing resource
settlement = await client.settlements.create({
    "idempotency_key": key,
    # ...
})
```
