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

# errors and configuration

> the exception tree, retries, timeouts, headers, and debug logging.

## error handling

every error the SDK raises inherits from `RumikError`. handle the specific cases you can
recover from, like a bad key or a rate limit, and keep `RumikError` as a backstop so
nothing slips through unhandled.

```python theme={null}
from rumikai import (
    Rumik,
    RateLimitError,
    AuthenticationError,
    UnprocessableEntityError,
    RumikError,
)

client = Rumik()
try:
    audio = client.speech.create(text="Hello")
except AuthenticationError:
    ...  # bad/missing key
except RateLimitError as e:
    wait(e.retry_after)
except UnprocessableEntityError as e:
    print(e.code, e)          # e.g. "text: String should have at most 2000 characters"
except RumikError as e:
    print(f"error {getattr(e, 'request_id', None)}: {e}")
```

### the exception tree

```
RumikError
├─ APIConnectionError
│  └─ APITimeoutError
├─ APIStatusError
│  ├─ BadRequestError            400
│  ├─ AuthenticationError        401
│  ├─ PermissionDeniedError      403
│  ├─ NotFoundError              404
│  ├─ UnprocessableEntityError   422
│  ├─ RateLimitError             429   (.retry_after)
│  ├─ InternalServerError        500
│  └─ ServiceUnavailableError    503
└─ StreamError
```

every `APIStatusError` (the 4xx/5xx group) carries `.status_code`, `.code`,
`.request_id`, and `.response`. `RateLimitError` adds `.retry_after` (seconds to wait).
passing empty `text` raises a plain `ValueError` before any request is sent.

<Note>always log `request_id` so support can trace a failure.</Note>

## configuration

set these once when you build the client. a few can be overridden per request where it
makes sense.

```python theme={null}
client = Rumik(
    api_key="rk_live_...",              # or RUMIK_API_KEY
    base_url="https://silk-api.rumik.ai",
    timeout=60.0,
    max_retries=2,
    default_headers={"X-App": "my-app"},
)
audio = client.speech.create(text="Hi", extra_headers={"X-Trace-Id": "abc"})
print(audio.request_id)                  # server x-request-id
```

| option            | default                     | description                            |
| ----------------- | --------------------------- | -------------------------------------- |
| `api_key`         | `RUMIK_API_KEY`             | your key.                              |
| `base_url`        | `https://silk-api.rumik.ai` | API base URL (or `RUMIK_BASE_URL`).    |
| `timeout`         | `60`                        | request timeout in seconds.            |
| `max_retries`     | `2`                         | automatic retries on transient errors. |
| `default_headers` | `None`                      | headers added to every request.        |
| `http_client`     | `None`                      | supply your own HTTP client.           |

**retries** happen automatically on 408, 429, and 5xx responses and on network errors,
using jittered exponential backoff and honoring `Retry-After`. **`default_headers`**
apply to every request; **`extra_headers`** apply to a single call. every response
carries a `.request_id` from `x-request-id`.

### debug logging

set `RUMIK_LOG=debug` to log every request, response, and retry:

```bash theme={null}
export RUMIK_LOG=debug
```

<Note>debug logging never includes your API key.</Note>
