# kindgi.client

> Kindgi and AsyncKindgi: the API client, its errors and paging.

The Kindgi API client.

```plaintext
from kindgi.client import Kindgi

client = Kindgi()  # KINDGI_API_URL, KINDGI_API_TOKEN; in development, the running kindgi dev
run = client.runs.start(flow="acme.ledger.record-flow", input={"vendor": "Acme"})
print(run.status, run.output)
```

Every operation of the API (`@kindgi/api/openapi.json`) is a method: the operation id `approvals.reviewers.list` is `client.approvals.reviewers.list()`. Request bodies are a model from `kindgi.client.models`, a mapping, or their fields as keywords; answers are models. Errors are typed (`NotFoundError`, `GuardrailViolationError`, …). `AsyncKindgi` is the same with asyncio.

Without arguments, a client takes `KINDGI_API_URL` and `KINDGI_API_TOKEN` from the environment. In development, when they're unset, it uses the running `kindgi dev` (the nearest `.kindgirc.json`), and warns once (`KindgiConfigWarning`) to put them in your env file (`.env` / `.env.local`). Production (`KINDGI_ENV` or `NODE_ENV` set to `production`) never reads `.kindgirc.json`.

## `AsyncKindgi`

```python signature
class AsyncKindgi(
    base_url: str | None = None,
    *,
    token: str | None = None,
    timeout: float = 60.0,
    max_retries: int = 2,
    default_headers: Mapping[str, str] | None = None,
    http_client: httpx.AsyncClient | None = None,
)
```

The Kindgi API, with asyncio.

async with AsyncKindgi() as client: run = await client.runs.start(flow="acme.ledger.record-flow", input={...}) async for event in client.runs.follow(run.id): # to the run's end ...

Like `Kindgi`, it finds its URL and token on first use, not when it's created.

## `AuthError`

```python signature
class AuthError(
    message: str,
    *,
    reason: Literal['unauthenticated', 'forbidden', 'token-expired'],
    **kw: Any,
)
```

## `ConflictError`

```python signature
class ConflictError(
    message: str,
    *,
    status: int | None = None,
    server_code: str | None = None,
    details: Mapping[str, Any] | None = None,
    request_id: str | None = None,
)
```

## `GuardrailViolationError`

```python signature
class GuardrailViolationError(
    message: str,
    *,
    violations: list[Mapping[str, Any]],
    evaluation_errors: list[Mapping[str, Any]],
    **kw: Any,
)
```

## `InvalidRequestError`

```python signature
class InvalidRequestError(message: str, *, issues: list[Mapping[str, Any]], **kw: Any)
```

## `Kindgi`

```python signature
class Kindgi(
    base_url: str | None = None,
    *,
    token: str | None = None,
    timeout: float = 60.0,
    max_retries: int = 2,
    default_headers: Mapping[str, str] | None = None,
    http_client: httpx.Client | None = None,
)
```

The Kindgi API, synchronously.

```plaintext excerpt
client = Kindgi("http://127.0.0.1:4000", token="kgi_bt_…")
client = Kindgi()  # KINDGI_API_URL, KINDGI_API_TOKEN; in development, kindgi dev
run = client.runs.start(agent="acme.bookkeeper", input={"userMessage": "hi"})
```

Resources follow the API's operation ids (`client.approvals.reviewers.list()`). Use it as a context manager, or `close()` it, to release connections.

The URL and token are found when the client is first used (its first request, or `base_url` / `token`), not when it's created: a module-scope `Kindgi()` loads in a build step that runs without them. If they're still missing then, that use raises `ValueError`, naming what to set.

## `KindgiApiError`

```python signature
class KindgiApiError(
    message: str,
    *,
    status: int | None = None,
    server_code: str | None = None,
    details: Mapping[str, Any] | None = None,
    request_id: str | None = None,
)
```

A Kindgi API call failed.

## `KindgiConfigWarning`

```python signature
class KindgiConfigWarning(*args, **kwargs)
```

How the client found its runtime needs your attention (a fallback, a stale token).

## `NetworkError`

```python signature
class NetworkError(
    message: str,
    *,
    timeout: float | None = None,
    status: int | None = None,
)
```

The request didn't get an answer (connection, timeout, a body that isn't JSON).

## `NotFoundError`

```python signature
class NotFoundError(message: str, *, kind: str, id: str, **kw: Any)
```

## `RateLimitedError`

```python signature
class RateLimitedError(
    message: str,
    *,
    retry_after_seconds: float | None = None,
    **kw: Any,
)
```

## `ServerError`

```python signature
class ServerError(
    message: str,
    *,
    status: int | None = None,
    server_code: str | None = None,
    details: Mapping[str, Any] | None = None,
    request_id: str | None = None,
)
```

## `apaginate`

```python signature
apaginate(
    list_method: Callable[..., Awaitable[_Page[T]]],
    /,
    *args: Any,
    **kwargs: Any,
) -> AsyncIterator[T]
```

`paginate` for `AsyncKindgi`: `async for run in apaginate(client.runs.list): …`.

## `comparison_of`

```python signature
comparison_of(run: EvalRun) -> JudgedComparisonResult | None
```

A comparison's result, typed: its `summary` and each case (`per_case`).

`run` is a `judged` eval run (a test set compared with a version). `None` for another kind of eval run, a dry run, or one that hasn't finished. The same reading as `comparisonOf` in the TypeScript client.

## `paginate`

```python signature
paginate(
    list_method: Callable[..., _Page[T]],
    /,
    *args: Any,
    **kwargs: Any,
) -> Iterator[T]
```

Every item of a list operation, page after page: `paginate(client.runs.list, limit=50)`.

## `pins_digest`

```python signature
pins_digest(pins: Mapping[str, Any] | BaseModel) -> str
```

`sha256:<hex>` of an agent version's pins, the string in its `pins_digest`.

`pins` is an agent version's `pins` (a model or a mapping with `tools`, `prompts` and `settings`). Two agent versions with the same digest run the same blocks. The same string as `pinsDigest` in `@kindgi/agents`.
