> This page is also available as Markdown: append .md to its URL. The full index of Sikaru docs is at https://docs.sikaru.ai/llms.txt. Code examples use only operations from the public API reference at https://docs.sikaru.ai/api-reference.

# SDK configuration and async usage

Facts on this page:

1. Python async client: `AsyncSikaruApi(api_key=..., base_url=..., httpx_client=httpx.AsyncClient(...), timeout=..., max_retries=...)`. Sync client: `SikaruApi` with `httpx.Client`. Python timeouts are seconds. Per-request `request_options` (`timeout`, `max_retries`) override client defaults.
2. TypeScript: `new SikaruApi({ apiKey, baseUrl, timeoutInSeconds, maxRetries })`; per-request `{ timeoutInSeconds }`.
3. Raw responses: `client.runs.with_raw_response.get(project, run_id)` exposes `status_code`, `data`, `headers`. Errors: `sikaru_api.core.api_error.ApiError` with `status_code`, `body.code`, `headers["retry-after"]`.
4. Error body: `{"detail": ..., "code": ...}`; 422 lists invalid fields in `detail`. 429 or 503 with a `code` includes `Retry-After` in seconds. A failed run is not an error response: read `error.code` and `error.recoverable` on the run.
5. Only GET and HEAD retry automatically. Mutations are never replayed automatically.
6. Pagination: session and objective listings use `after`; evaluation job and result listings use `cursor`. Run events: `runs.events` (JSON page, `after` in, `next_after` out); SSE iterator `runs.stream_events` (Python) or `runs.streamEvents` (TypeScript).
7. Local compute: controllers use `compute_attachments.create`; workers `compute_workers.poll`; executors `compute_operations.poll(project_id, attachment_id, wait_seconds=0)`. Use `sikaru compute serve` unless implementing the full executor contract. Never pass a controller credential to the sandbox.

Use the authenticated client setup from [Quickstart](/get-started/quickstart). The examples below read an existing run: set `SIKARU_PROJECT_ID` and `SIKARU_RUN_ID` to its identifiers.

| Setting              | Python                             | TypeScript                  |
| -------------------- | ---------------------------------- | --------------------------- |
| Client               | `SikaruApi`, `AsyncSikaruApi`      | `SikaruApi`                 |
| Client timeout       | `timeout` (seconds)                | `timeoutInSeconds`          |
| Retries              | `max_retries`                      | `maxRetries`                |
| Per-request override | `request_options={"timeout": ...}` | `{ timeoutInSeconds: ... }` |
| Automatic retries    | GET and HEAD only                  | GET and HEAD only           |

## Make async requests in Python

Use `AsyncSikaruApi` in an async application. Supply an async HTTP client when you need explicit connection cleanup or transport configuration.

```python
import asyncio
import os
import httpx
from sikaru_api import AsyncSikaruApi

async def main():
    async with httpx.AsyncClient(timeout=20) as transport:
        client = AsyncSikaruApi(
            api_key=os.environ["SIKARU_API_KEY"],
            base_url=os.environ["SIKARU_BASE_URL"],
            httpx_client=transport,
            timeout=20,
            max_retries=0,
        )
        run = await client.runs.get(
            os.environ["SIKARU_PROJECT_ID"], os.environ["SIKARU_RUN_ID"],
            request_options={"timeout": 10},
        )
        print(run.status)

asyncio.run(main())
```

The synchronous `SikaruApi` accepts the same timeout and retry options, with `httpx.Client` for its transport. Python timeouts are in seconds. Per-request `timeout` and `max_retries` override client defaults.

> **Note**
>
> A request timeout limits one HTTP call. It does not set an overall observation deadline, and it does not cancel a hosted run.

## Handle errors and raw responses

```python
from sikaru_api.core.api_error import ApiError

try:
    response = client.runs.with_raw_response.get(project, run_id)
    print(response.status_code)
    print(response.data.status)
except ApiError as error:
    print("Sikaru request failed:", error.status_code)
```

Here `client` is a synchronous client and `run_id` is an existing run ID. Raw responses also expose `headers`. Handle network and timeout errors from the HTTP transport separately. Log only the response fields you need for diagnosis: bodies and headers can contain customer data.

| Response   | Application action                                                                                               |
| ---------- | ---------------------------------------------------------------------------------------------------------------- |
| 401 or 403 | Check the key, project scope, and permission required by the operation.                                          |
| 404        | Check the identifier and its project ownership.                                                                  |
| 402        | Billing refused the request (`insufficient_credits`). Check the plan, credits and the agent's spending cap.      |
| 409        | Branch on the body's `code`, such as `idempotency_conflict`. Read the retained state or receipt before retrying. |
| 422        | Correct the request using the endpoint schema and error details.                                                 |
| 429 or 5xx | Apply bounded retry handling for reads; reconcile mutations before submitting them again.                        |

Error responses have the body `{"detail": ..., "code": ...}`. `422` validation errors list invalid fields in `detail` instead.

* `code` is specific where one applies, such as `idempotency_conflict`. Otherwise it is a status default such as `not_found`.
* In Python, read `error.body.code` and `error.headers["retry-after"]`.
* A `429` or `503` refusal with a `code` includes `Retry-After` in seconds.
* A failed run is not an error response. Read `error.code` and `error.recoverable` on the run itself. See [Runs and tool requests](/run/runs-and-tool-requests#handle-failures).

> **Warning**
>
> Only GET and HEAD requests retry automatically. Setting retry options does not enable automatic mutation replay. Keep idempotency keys stable for identical submissions when the endpoint supports them.

## Set TypeScript request options

```typescript
import { SikaruApi } from "@sikaru/sdk";

const client = new SikaruApi({
  apiKey: process.env.SIKARU_API_KEY!,
  baseUrl: process.env.SIKARU_BASE_URL!,
  timeoutInSeconds: 20,
  maxRetries: 0,
});
const run = await client.runs.get(
  process.env.SIKARU_PROJECT_ID!, process.env.SIKARU_RUN_ID!,
  { timeoutInSeconds: 10 },
);
console.log(run.status);
```

Keep the key in server-side code. Use your language client's reference for error types and request-option names. They are not identical across languages.

## Page through results and events

Pagination is endpoint-specific:

| Listing                             | Cursor parameter             |
| ----------------------------------- | ---------------------------- |
| Sessions, objectives                | `after`                      |
| Evaluation jobs, evaluation results | `cursor`                     |
| Run events (`runs.events`, Python)  | `after` in, `next_after` out |

Read each response envelope and pass its continuation cursor to the same endpoint, keeping the same filters. Do not assume these clients automatically read every page.

For run events, persist the cursor and pass it as `after` on the next JSON page. Deduplicate by event identity if a page arrives again. `runs.events` returns a JSON page. For the dedicated SSE iterator, use `runs.stream_events` in Python or `runs.streamEvents` in TypeScript. See [Runs and tool requests](/run/runs-and-tool-requests).

## Poll self-hosted compute with JSON

Each role uses ordinary generated HTTP methods, without requiring SSE support:

| Role       | What it does                                                              | Method                       |
| ---------- | ------------------------------------------------------------------------- | ---------------------------- |
| Controller | Binds an execution session, claims it, and issues a restricted credential | `compute_attachments.create` |
| Worker     | Polls for one environment                                                 | `compute_workers.poll`       |
| Executor   | Polls for its own attachment                                              | `compute_operations.poll`    |

An executor can inspect its scoped work page:

```python
from sikaru_api import SikaruApi

executor = SikaruApi(base_url=api_url, api_key=attachment_token)
page = executor.compute_operations.poll(project_id, attachment_id, wait_seconds=0)
# Empty operations are not completion; inspect the execution summary separately.
print(page.execution_phase)
```

The native executor owns readiness, monotonic lease deadlines, process supervision, fsynced intents, and immutable receipt submission. Use `sikaru compute serve` in your sandbox unless you implement that entire executor contract. **Never pass a controller credential to the sandbox.**

See [Local compute](/run/local-compute) for bootstrap fields, approvals, file boundaries, and recovery.

## Next steps

#### [Runs and tool requests](/run/runs-and-tool-requests)

Stream events and handle failures.

#### [Local compute](/run/local-compute)

Attach an executor you control.

#### [Quickstart](/get-started/quickstart)

Set up an authenticated client.