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

# Python SDK

> Typed Python client for the Aethis decision API — sync and async clients, stateful decision sessions, ergonomic Pydantic models.

The Python SDK wraps `api.aethis.ai` with sync and async clients, typed Pydantic response models, and a stateful `DecisionSession` adapter for wizard / chatbot intake flows. It also exposes a narrow authoring-recovery surface for inspecting and explicitly cancelling a generation job; use the CLI or MCP server to create, generate, refine, and publish rulesets.

Use this when you're shipping a Python service that calls Aethis from a server context — FastAPI, Django, a worker, a notebook. For ad-hoc evaluation from a terminal, prefer the [CLI](/interfaces/cli) or raw [curl](/interfaces/rest-api).

## Install

```bash theme={null}
uv add 'aethis-sdk>=0.7.0'
# or, for an isolated tool install:
uv pip install 'aethis-sdk>=0.7.0'
```

Python 3.11+. Pulls in `httpx` and `pydantic`. Source: [github.com/Aethis-ai/aethis-sdk-python](https://github.com/Aethis-ai/aethis-sdk-python).

<Note>
  **0.5.0 or later required for the rulebook surface.** `Aethis.decide_rulebook()` / `AsyncAethis.decide_rulebook()` and the `rulebook_id` field on `DecideResponse` ship in 0.5.0. The audit fields (`inputs_hash`, `decision_id`, `decision_time`, `engine_version`) on `DecideResponse` have been present since 0.3.2.
</Note>

<Note>
  **API key is optional for decision endpoints.** `Aethis()` without a key works for anonymous decides on public rulesets (`client.decide(ruleset_id="aethis/uk-fsm/child-eligibility", ...)`). A key is required for: composed rulebook decides via `decide_rulebook()`, private rulesets, and any authoring endpoint. [Request access →](https://aethis.ai/developer-access)
</Note>

## Quickstart — sync (anonymous on public rulesets)

```python theme={null}
from aethis_sdk import Aethis

with Aethis() as client:        # no api_key — public ruleset, anonymous OK
    response = client.decide(
        ruleset_id="aethis/uk-fsm/child-eligibility",
        field_values={
            "child.age": 10,
            "child.school_type": "state_funded",
        },
    )
    print(response.decision)        # "eligible" | "not_eligible" | "undetermined"
    print(response.inputs_hash)     # canonical SHA-256 fingerprint of the input set
    print(response.decision_id)     # per-call audit identifier
    print(response.decision_time)   # ISO-8601 timestamp
    print(response.engine_version)  # e.g. "aethis-core@0.41.1"
```

### Composed rulebook (requires API key)

To evaluate a multi-ruleset rulebook (e.g. `aethis/uk-fsm`'s `A AND (B OR C)`), use `decide_rulebook()` and supply an API key:

```python theme={null}
from aethis_sdk import Aethis

with Aethis(api_key="YOUR_KEY") as client:
    response = client.decide_rulebook(
        rulebook_id="aethis/uk-fsm",
        field_values={
            "child.age": 10, "child.year_group": "year_6",
            "child.school_type": "state_funded",
            "household.receives_universal_credit": True,
            "household.annual_net_earnings": 5000,
            # ... rest of household fields
        },
    )
    print(response.decision)      # eligible
    print(response.rulebook_id)   # rb_kzZ_td0tbKW_OLRB (resolved from slug)
```

Rulebook decide is always scope-gated — anonymous callers get HTTP 401, regardless of rulebook visibility.

`response` is a typed `DecideResponse` — see the [decision envelope reference](/concepts/decision-envelope) for the full shape. The four audit fields above are the determinism proof: same `inputs_hash` always produces the same outcome from the same `engine_version`, and the `decision_id` lets you pin a specific decision to a log line.

`Aethis` must be used as a context manager — the underlying `httpx` client is created in `__enter__` and closed in `__exit__`, so calling methods on a non-entered client raises `RuntimeError`.

## Quickstart — async

```python theme={null}
import asyncio
from aethis_sdk import AsyncAethis

async def main():
    async with AsyncAethis() as client:    # no key — public ruleset
        response = await client.decide(
            ruleset_id="aethis/uk-fsm/child-eligibility",
            field_values={"child.age": 10, "child.school_type": "state_funded"},
        )
        print(response.decision)

asyncio.run(main())
```

The async surface mirrors the sync one method-for-method. Use it inside FastAPI handlers, async workers, or any code path already on the asyncio event loop.

## With trace

```python theme={null}
response = client.decide(
    ruleset_id="aethis/spacecraft-crew-certification",
    field_values={"space.crew.species": "Vogon"},
    include_trace=True,
)
print(response.trace["failure_reasons"])
print(response.trace["group_statuses"])
```

## Discover fields

```python theme={null}
schema = client.get_schema("aethis/uk-fsm/child-eligibility")
for field in schema.fields:
    print(f"{field.field_id}  {field.field_type}  {field.description}")
```

## Stateful decision session

For interactive flows — chatbot, wizard, multi-step form — `SyncDecisionSession` accumulates answers locally and only hits `/decide` when the input set has actually changed. The session does not own the client; keep the `Aethis` context open for the session's lifetime.

```python theme={null}
from aethis_sdk import Aethis, SyncDecisionSession

with Aethis() as client:        # no key — public ruleset
    schema = client.get_schema("aethis/spacecraft-crew-certification")
    session = SyncDecisionSession(
        "aethis/spacecraft-crew-certification", client, schema,
    )
    session.answer("space.crew.species", "Human")

    while True:
        status = session.status()
        if status.blocked:
            # `next_question is None` is not a completion signal: invalid
            # input suppresses the next question and leaves a blocked session.
            raise ValueError(status.field_errors)
        if status.is_complete:
            print("Eligible:", status.decision == "eligible")
            break
        if status.next_question is None:
            raise RuntimeError("session is pending but supplied no next question")
        answer = input(f"{status.next_question.question} ")
        session.answer(status.next_question.field_id, answer)
```

The async equivalent is `DecisionSession` — same surface with `await` on the I/O methods.
In either surface, `status.blocked` means the API rejected one or more supplied
values, `status.is_complete` means an unblocked `eligible` or `not_eligible`
decision, and every other status is pending. Do not infer completion from an
absent next question.

## FastAPI integration

A typical server-side wiring: one client per process, an async dependency, decision endpoints that return the envelope verbatim.

```python theme={null}
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends, HTTPException
from aethis_sdk import AsyncAethis, AethisAPIError, AethisUnavailable

@asynccontextmanager
async def lifespan(app: FastAPI):
    # `async with` is required — it opens the underlying httpx client.
    async with AsyncAethis(api_key=os.environ["AETHIS_API_KEY"]) as client:
        app.state.aethis = client
        yield

app = FastAPI(lifespan=lifespan)

def get_aethis() -> AsyncAethis:
    return app.state.aethis

@app.post("/eligibility/free-school-meals")
async def free_school_meals(
    payload: dict, client: AsyncAethis = Depends(get_aethis),
):
    try:
        decision = await client.decide(
            ruleset_id="aethis/uk-fsm/child-eligibility",
            field_values=payload,
        )
    except AethisAPIError as e:
        raise HTTPException(status_code=e.status_code or 502, detail=str(e))
    except AethisUnavailable:
        raise HTTPException(status_code=503, detail="decision_engine_unavailable")
    return decision.model_dump()
```

## Django integration

The simplest pattern is per-request: instantiate inside the view's `with` block. New connections each call (no pooling across requests), but no lifecycle wiring required.

```python theme={null}
# views.py
import json
import os
from django.http import JsonResponse
from aethis_sdk import Aethis

API_KEY = os.environ["AETHIS_API_KEY"]

def free_school_meals(request):
    payload = json.loads(request.body)
    with Aethis(api_key=API_KEY) as client:
        decision = client.decide(
            ruleset_id="aethis/uk-fsm/child-eligibility",
            field_values=payload,
        )
    return JsonResponse(decision.model_dump())
```

For high-traffic services that want connection pooling across requests, lift the `with` block into a Django `AppConfig.ready()` (call `client.__enter__()` at startup, register `atexit(client.close)` for shutdown) and stash the client in `apps.get_app_config(...)`. For async views, swap to `AsyncAethis` with `async with` and `await` the call.

## Configuration

| Argument | Required | Default | Notes |
| - | - | - | - |
| `api_key` | no | `None` | Optional. Anonymous decides on public rulesets work without it; required for composed rulebook decides, private rulesets, and authoring. Provisioned via [aethis.ai/developer-access](https://aethis.ai/developer-access). |
| `base_url` | no | `https://api.aethis.ai` | HTTP only permitted for `localhost` / `127.0.0.1` or with a test transport. |
| `timeout` | no | `5.0` (seconds) | Per-request, applies to all HTTP methods. |
| `iam_token` | no | — | Cloud Run service-to-service bearer token. Most users won't need this. |

## Errors

The SDK exposes a small exception hierarchy. Catch the base class for blanket handling, or the specific subclasses for tailored retry / fallback logic.

```python theme={null}
from aethis_sdk import (
    AethisError,         # base — catch this for "anything went wrong"
    AethisAPIError,      # 4xx / 5xx with structured body
    AethisUnavailable,   # network failure, DNS, connection refused
    AethisTimeout,       # timeout exceeded
)

try:
    decision = client.decide(ruleset_id=..., field_values=...)
except AethisTimeout:
    # log + degrade — the call exceeded your client timeout (network, not engine time)
    ...
except AethisUnavailable:
    # network / engine down — circuit-break and fall back
    ...
except AethisAPIError as e:
    # 4xx (your input) or 5xx (their bug)
    if e.status_code == 422:
        # validation: missing required field, wrong type, etc.
        ...
```

See the [error reference](/reference/errors) for response shapes and codes.

## Public API surface

| Import | Purpose |
| - | - |
| `Aethis`, `AsyncAethis` | HTTP clients for `/decide`, `/rulesets` (list), `/rulesets/{id}/schema`, `/me`, `/explain`, `/source`, and the narrow authoring-recovery methods `get_generation_status()` / `cancel_generation()`. `decide()` / `decide_rulebook()` accept `include_explanation` and `list_rulesets()` pages the public catalogue (both 0.7.0+). |
| `SyncDecisionSession`, `DecisionSession` | Stateful adapters over the stateless `/decide` endpoint |
| `DecideResponse`, `SchemaResponse`, `SchemaField`, `NextQuestion`, `FieldNote`, `SectionResult`, `RulesetSummary`, `GenerationStatusResponse`, `GenerationCancellationResponse` | Pydantic response models |
| `Decision`, `SectionStatus`, `SessionStatus` | Enum-like result types |
| `AethisError`, `AethisAPIError`, `AethisUnavailable`, `AethisTimeout` | Exception hierarchy (`.detail` / `.body` carry the API's error payload) |

## Status

Pre-1.0. The decision surface (`/decide`, `/schema`, `/explain`, `/source`, `/me`) is stable. The SDK exposes generation status and explicit cancellation for recovery, but it does not expose the rest of the authoring workflow (project creation, source upload, generation submission, refinement, or publishing). Use the [CLI](/interfaces/cli) or [MCP server](/mcp-server/overview) for full authoring.

## See also

* [Which interface?](/interfaces/which-to-use) — pick between SDK, CLI, MCP, REST.
* [Decision envelope](/concepts/decision-envelope) — every field on `DecideResponse`.
* [Error reference](/reference/errors) — codes and recovery patterns.
* [aethis-sdk on PyPI](https://pypi.org/project/aethis-sdk/) · [source on GitHub](https://github.com/Aethis-ai/aethis-sdk-python)

<Note>
  **Help improve this page**

  If something here is unclear or missing an example, use the feedback button at the bottom of the page.

  Found a bug? [Open a GitHub issue](https://github.com/Aethis-ai/feedback/issues). Evaluating Aethis for a regulated workflow? [Contact us directly](https://aethis.ai/developer-access).
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.