Skip to main content
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

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.
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.
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 →

Quickstart — sync (anonymous on public rulesets)

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.36.0"

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:
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 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() 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

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() 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 (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_keynoNoneOptional. Anonymous decides on public rulesets work without it; required for composed rulebook decides, private rulesets, and authoring. Provisioned 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 (list), /rulesets/{id}/schema, /me, /explain, /source. decide() / decide_rulebook() accept include_explanation and list_rulesets() pages the public catalogue (both 0.7.0+).
SyncDecisionSession, DecisionSessionStateful adapters over the stateless /decide endpoint
DecideResponse, SchemaResponse, SchemaField, NextQuestion, FieldNote, SectionResult, RulesetSummaryPydantic response models
Decision, SectionStatus, SessionStatusEnum-like result types
AethisError, AethisAPIError, AethisUnavailable, AethisTimeoutException hierarchy (.detail / .body carry the API’s error payload)

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

Help improve this pageIf 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. Evaluating Aethis for a regulated workflow? Contact us directly.