api.aethis.ai serves engine 0.49.1, which is the contract these pages
describe; every response carries engine_version so you can confirm which
build answered you.
What the deployed engine serves./decide call returned something unexpected. Work through this page in order; each step is safe.
Step 1 — Read the envelope, not just the decision
Every/decide response carries an audit envelope:
Values above are illustrative —
content_digest is a placeholder, and the
ruleset_id / ruleset_version you get back depend on what is published
when you call. The field names and their meanings are exact.field_errors— read this beforedecision. If it is non-null, at least one input could not be applied, and the decision is forced toundeterminedno matter what the rest of the envelope says. A key here is an unknown field name or a value that failed the field’s type. Fix those first; nothing below matters until they are clear.decision—eligible/not_eligible/undetermined. The only field to treat as the outcome. Never re-derive it by aggregating group statuses.missing_fields— which fields the engine wanted but didn’t getfields_providedvsfields_evaluated— if provided > evaluated, the extra fields are either unknown to this ruleset (checkfield_errors, it is probably a typo) or not relevant to the active pathslugresolved — if you sent a slug, this confirms what the resolver picked. Different from what you expected? Wrong slug, wrong tenant.ruleset_id/ruleset_version/content_digest— the resolved immutable identity of what was actually decided.ruleset_versionis a mutable authoring label;content_digestis the immutable content identity. Pin replay to the digest. For a published leaf ruleset,ruleset_versionis never"unknown"— if you see that value, you are either on a composed rulebook or on an engine older than this contract.engine_version— same as your local expectation? Mismatched engine versions can change outcomes; pin the version if replay matters
Step 2 — Re-run with include_trace: true
Trace shows per-group status for every criterion group in the ruleset:
- CLI
- Python SDK
- curl
- MCP
/explain output for that group tells you which clause of the source legislation it compiled from.
include_explanation: true adds a structured explanation object to the response — the gate-level checklist plus the supporting facts that proved each satisfied criterion. Shape:
groups[].statusandcriteria[].statusaresatisfied/not_satisfied/pending— the gate-level checklist.supporting_factslists the answers that proved each satisfied criterion. For anOrbranch, only the satisfied disjunct’s facts appear (no over-reporting alternatives).unused_factslists field names you provided that no criterion in the ruleset references — the typo signal. If you sentchild.school_kindinstead ofchild.school_type, it shows up here.
Step 2b — Visualize with include_graph_overlay
include_trace and include_explanation are both text. If you want a map of the ruleset with this decision’s status stamped onto every node — to render a UI, or just to see at a glance which branch failed — add include_graph_overlay: true:
graph_overlay field: the same {nodes, edges, sections, stats} structure GET /rulesets/{id}/graph returns, but with each criterion’s overlay stamped {"status": "satisfied" | "not_satisfied" | "pending", "supporting_facts": [...]} for this decision instead of null. Walk the not_satisfied nodes to find the failing branch without parsing trace.group_statuses by hand — useful when you’re rendering rather than reading raw JSON. Full shape and the structure-only (no decision) form: Ruleset & rulebook graphs.
Step 3 — If decision = not_eligible, use /explain-failure
- CLI
- Python SDK
- curl
- MCP
--explain runs /decide first, then calls /explain-failure when the outcome is not_eligible and renders both in one pass.Step 4 — If you got a 4xx, check the error reason_code
Every error response has areason_code under detail:
Step 5 — Still confused? Minimal reproduction
Print everything — trace + explanation + timing + cache bypass — and attach the output to whatever support channel you’re using. Thedecision_id is the handle; the server logs every decision keyed by that id.
- CLI
- Python SDK
- curl
- MCP
include_timing or no_cache flags — for a cache-bypassed timing repro, use the curl tab.Common gotchas
ruleset_id: "aethis/uk-fsm"— that’s a rulebook slug. Move the value torulebook_idand addx-api-key. See Nomenclature.- Anonymous
/decideon a private ruleset returns 404 — the engine doesn’t leak existence of private rulesets. If you own the ruleset, add your API key. - Slug resolves to an unexpected version — slug pointers transfer on re-publish. Check
ruleset_idin the response envelope to see which ruleset you actually hit. missing_fieldsis empty but decision isundetermined— discretionary clause (see system-wide discretion principle) or a case outside the compiled rules. The ruleset may need regeneration or an explicit rule for your case.
Next steps
- Errors reference — every
reason_codethe API emits - Decision envelope — the audit/replay contract
- Evaluate a case — the happy-path workflow