Skip to main content

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.

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. 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 or raw curl.

Install

pip install 'aethis-sdk>=0.3.2'
Python 3.11+. Pulls in httpx and pydantic. Source: github.com/Aethis-ai/aethis-sdk-python.
0.3.2 or later required for the snippets below. The inputs_hash, decision_id, decision_time, and engine_version fields on DecideResponse ship in 0.3.2 — earlier releases silently dropped them. Pin >=0.3.2 if your code reads any of those fields directly off the typed response.
The SDK requires an API key for every call. Even decision endpoints that are anonymous over raw HTTP require a key when called via the SDK — the client is intended for authenticated, server-side use. For unauthenticated decision evaluation, use curl or the CLI. Request access →

Quickstart — sync

from aethis_sdk import Aethis

with Aethis(api_key="YOUR_KEY") as client:
    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.10.0"
response is a typed DecideResponse — see the decision envelope reference 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

import asyncio
from aethis_sdk import AsyncAethis

async def main():
    async with AsyncAethis(api_key="YOUR_KEY") as client:
        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

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

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.
from aethis_sdk import Aethis, SyncDecisionSession

with Aethis(api_key="YOUR_KEY") as client:
    schema = client.get_schema("aethis/spacecraft-crew-certification")
    session = SyncDecisionSession(
        "aethis/spacecraft-crew-certification", client, schema,
    )
    session.answer("space.crew.species", "Human")

    while (nq := session.next_question()) is not None:
        answer = input(f"{nq.question} ")
        session.answer(nq.field_id, answer)

    print("Eligible:", session.is_eligible())
The async equivalent is DecisionSession — same surface with await on the I/O methods.

FastAPI integration

A typical server-side wiring: one client per process, an async dependency, decision endpoints that return the envelope verbatim.
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.
# 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

ArgumentRequiredDefaultNotes
api_keyyesProvisioned via aethis.ai/developer-access.
base_urlnohttps://api.aethis.aiHTTP only permitted for localhost / 127.0.0.1 or with a test transport.
timeoutno5.0 (seconds)Per-request, applies to all HTTP methods.
iam_tokennoCloud 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.
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 — engine missed its 5ms budget under back-pressure
    ...
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 for response shapes and codes.

Public API surface

ImportPurpose
Aethis, AsyncAethisHTTP clients for /decide, /rulesets/{id}/schema, /me, /explain, /source
SyncDecisionSession, DecisionSessionStateful adapters over the stateless /decide endpoint
DecideResponse, SchemaResponse, SchemaField, NextQuestion, SectionResultPydantic response models
Decision, SectionStatus, SessionStatusEnum-like result types
AethisError, AethisAPIError, AethisUnavailable, AethisTimeoutException hierarchy

Status

Pre-1.0. The decision surface (/decide, /schema, /explain, /source, /me) is stable. Authoring endpoints (projects, rulesets, publishing) are not yet exposed in the SDK — use the CLI or MCP server for those.

See also