# Python

Synchronous and asynchronous clients with typed errors.

Requires Python 3.12 or newer. Install the local `packages/sdk-python` project as described in the SDK overview. Store the key on your server.

```python
import os
from secnd import Secnd

client = Secnd(api_key=os.environ["SECND_API_KEY"])
result = client.decide({
    "model": "ren-1.1.0",
    "type": "noul",
    "situation": "The customer asks to cancel before renewal.",
    "question": "Is cancellation explicitly requested?",
    "threshold": 0.9,
})
print(result["choice"], result["probability"], result["action"])
```

`Secnd(api_key, base_url="https://api.secndai.com", max_retries=2, timeout=30.0)` accepts a request dictionary. Timeout is in seconds per attempt. Set `max_retries` from 0 through 5. HTTPS is required except on localhost.

## Async

```python
import asyncio
import os
from secnd import AsyncSecnd

async def main():
    client = AsyncSecnd(api_key=os.environ["SECND_API_KEY"])
    result = await client.decide({
        "model": "ren-1.1.0",
        "type": "noul",
        "situation": "Please cancel my plan.",
        "question": "Is cancellation explicitly requested?",
    })
    print(result["action"])

asyncio.run(main())
```

The async client uses a thread-backed transport. It keeps the event loop free while a request waits; cancelling the coroutine does not necessarily stop the underlying HTTP request. Bound concurrency and rely on request timeouts.

## Errors and retries

Catch `AuthenticationError`, `ValidationError`, `RateLimitError`, or `ServerError`; each extends `SecndError`. Error instances expose `status`, `code`, `retry_after`, and `request_id` (the `X-Request-Id` header) plus the message through `str(error)`. Network or invalid-response failures can raise the base `SecndError`.

Retries cover `429` and server errors only, with exponential backoff (up to 30 seconds) or the full server delay up to 60 seconds, preferring `retry-after-ms` over `Retry-After`; a longer `Retry-After` raises `RateLimitError` with `retry_after` instead of retrying. Transport failures are surfaced without automatic retry. A 30-second per-attempt timeout is not a 30-second total deadline.
