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

# REST API

> Integrate eligibility evaluation and rule authoring directly into your application.

Use the REST API when you're building your own backend, dashboard, or custom workflow — a lending application that calls `/decide` on every form submission, a compliance portal that displays current rule explanations, or a custom authoring pipeline.

**Base URL:** `https://api.aethis.ai/api/v1/public/`

**Decision endpoints** (no auth required) are safe to call client-side from a browser or mobile app.\
**Authoring endpoints** require an `x-api-key` header and should be called from your server.

***

## Authentication

```bash theme={null}
x-api-key: ak_live_...
```

Get an API key: [sign up](https://aethis.ai/developer-access)

Omitting the header on decision endpoints works fine. Including it on decision endpoints is also fine — it's ignored.

### Scopes

API keys carry a set of scopes. Each authoring endpoint requires a specific scope; a key without that scope receives `403 Forbidden`.

| Scope              | Grants                                                                                      |
| ------------------ | ------------------------------------------------------------------------------------------- |
| `decide`           | `POST /decide` on private rulesets                                                          |
| `rulesets:read`    | `GET /rulesets`, `GET /rulesets/{id}/schema`                                                |
| `rulesets:explain` | `GET /rulesets/{id}/explain`, `POST /rulesets/{id}/explain-failure`                         |
| `rulesets:source`  | `GET /rulesets/{id}/source` (DSL export)                                                    |
| `rulesets:write`   | `PATCH /rulesets/{id}/visibility`, `POST /rulesets/{id}/archive`                            |
| `projects:read`    | `GET /projects`, `/status`, `/guidance` list/export                                         |
| `projects:write`   | All project authoring: sources, tests, guidance, field/section discovery, generate, publish |
| `rulebooks:read`   | `GET /rulebooks` (your tenant's listing), `/schema`, `/explain`, `/graph`                   |
| `rulebooks:write`  | Rulebook CRUD, `/activate`, `/archive`, `PATCH /rulebooks/{id}/visibility`                  |

Decision endpoints (`/decide`, `/schema`, `/explain` on public rulesets) require no scope and accept anonymous requests. `GET /rulebooks/` also accepts anonymous requests: without a key it returns the cross-tenant public catalogue (rulebooks with `visibility=public` and `status=active`); with a key it returns your tenant's rulebooks as before.

<Warning>
  **Rulebook lookups and decisions require an API key.** Anonymous `/decide` resolves a `ruleset_id` or ruleset slug against public rulesets only. The separate `rulebook_id` field (for composed multi-section rulebooks like `aethis/uk-fsm`) is always scope-gated — anonymous callers get a 401. Anonymous access to rulebooks is limited to the `GET /rulebooks/` catalogue listing; to evaluate one, hit each section by slug instead, or pass an `x-api-key` header with a key that has the `decide` scope. See [Nomenclature](/concepts/nomenclature) for the full distinction.
</Warning>

### Rate-limit tiers

Keys are assigned a daily-quota tier. Counters reset at UTC midnight. A 429 is returned with `Retry-After: 86400`.

| Tier               |      `/decide` |  `/rulesets/*` | `/projects/*` |
| ------------------ | -------------: | -------------: | ------------: |
| Anonymous (no key) | 500/day per IP | 500/day per IP |             — |
| `free`             |            500 |            500 |           500 |
| `starter`          |         10,000 |            500 |           100 |
| `pro`              |        100,000 |          5,000 |           500 |

Tier is set at key creation and can be changed by contacting support. Contact `support@aethis.ai` to upgrade.

### Decision envelope

Every `/decide` response includes audit fields (`decision_id`, `inputs_hash`, `engine_version`, `ruleset_version`) for reproducible replay without the server echoing your inputs. See [Decision envelope →](/concepts/decision-envelope) for the full contract.

***

## Evaluate eligibility

No API key required.

```bash theme={null}
curl -X POST https://api.aethis.ai/api/v1/public/decide \
  -H "Content-Type: application/json" \
  -d '{
    "ruleset_id": "aethis/uk-fsm/child-eligibility",
    "field_values": {
      "child.age": 10,
      "child.school_type": "state_funded"
    },
    "include_trace": true
  }'
```

```json theme={null}
{
  "decision": "eligible",
  "fields_provided": 2,
  "fields_evaluated": 2,
  "trace": {
    "age_check": "PASS — age 10 is within 4–15 (Regulation 3)",
    "school_type_check": "PASS — school type is state_funded (Section 512ZA)"
  }
}
```

**Parameters:**

* `ruleset_id` — the published ruleset to evaluate against
* `field_values` — map of field names to values (types must match the ruleset schema)
* `include_trace` *(optional)* — when `true`, returns a `trace` object showing how each criterion was evaluated and the source clause it references
* `include_explanation` *(optional)* — when `true`, returns a structured `explanation` object: gate-level checklist (`groups[].criteria[].status`), the supporting facts that proved each satisfied criterion (`supporting_facts`), the satisfied requirement name (`decision_path`), and any provided fields the ruleset never references (`unused_facts` — useful for catching field-name typos). See [Debug a /decide](/recipes/debug-a-decide#step-2--re-run-with-include_trace-true) for the full payload shape.

**Decisions:**

* `eligible` — all criteria satisfied
* `not_eligible` — one or more criteria failed
* `undetermined` — the engine could not reach a decision (missing field, discretionary clause, or case outside compiled rules)

***

## Inspect a ruleset

```bash theme={null}
# Get the field schema — what fields does this ruleset expect?
curl https://api.aethis.ai/api/v1/public/rulesets/aethis/uk-fsm/child-eligibility/schema
```

```json theme={null}
{
  "ruleset_id": "aethis/uk-fsm/child-eligibility",
  "slug": "aethis/uk-fsm/child-eligibility",
  "name": "UK FSM Child Eligibility",
  "fields": [
    {
      "field_id": "child.age",
      "field_type": "int",
      "description": "Child's age in whole years at start of academic year",
      "question": "How old will the child be on 1 September of the relevant academic year?",
      "weight": 1,
      "enum_values": null,
      "notes": [
        {
          "note_text": "WHY: Compulsory-school-age status — and therefore FSM eligibility — is determined by age on 1 September.",
          "source": "DfE Free School Meals: guidance for local authorities",
          "metadata": { "type": "why", "section": "child_eligibility" }
        }
      ]
    },
    {
      "field_id": "child.school_type",
      "field_type": "enum",
      "description": "Type of school the child attends",
      "weight": 2,
      "enum_values": ["state_funded", "independent", "home_educated"],
      "notes": []
    }
  ]
}
```

**`notes`** (each `FieldNoteOut`: `{ note_text, source, metadata }`) is the
structured guidance the ruleset author attached during `/fields/discover`.
Conversational front-ends (e.g. a paralegal bot) typically render
`metadata.type='why'` notes when the user asks *"why are you asking this?"*
and draw on `metadata.type='legal_background'` notes for edge-case
follow-ups. Older bundles return `"notes": []` — treat the array as
optional.

**`weight`** is the question-ordering hint used by the constraint solver (higher = less preferred to
ask). Used by `/decide`'s `optimal_path` to surface cheap questions before
expensive ones when multiple ways to satisfy the ruleset exist.

```bash theme={null}
# Get human-readable rule descriptions
curl https://api.aethis.ai/api/v1/public/rulesets/aethis/uk-fsm/child-eligibility/explain
```

***

## Author rules

Authoring is invite-only private beta ([request access](https://aethis.ai/developer-access)). API key required; do all authoring server-side. Pass an `X-Anthropic-Key` header on generation requests — used for that request only, never stored.

### Step 1 — Create a project

```bash theme={null}
curl -X POST https://api.aethis.ai/api/v1/public/projects/ \
  -H "Content-Type: application/json" \
  -H "x-api-key: ak_live_..." \
  -d '{
    "name": "Income eligibility check",
    "section_id": "income_eligibility",
    "source_text": "Section 3: An applicant qualifies if annual net income is below £30,000...",
    "test_cases": [
      { "name": "Below threshold", "field_values": { "applicant.annual_income": 25000 }, "expected_outcome": "eligible" },
      { "name": "Above threshold", "field_values": { "applicant.annual_income": 35000 }, "expected_outcome": "not_eligible" }
    ]
  }'
```

```json theme={null}
{ "project_id": "proj_abc123" }
```

### Step 2 — Generate and test

```bash theme={null}
curl -X POST https://api.aethis.ai/api/v1/public/projects/proj_abc123/generate-and-test \
  -H "x-api-key: ak_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "anthropic_key": "sk-ant-..." }'
```

```json theme={null}
{
  "ruleset_id": "income_eligibility:20260416-a1b2c3d4",
  "tests_passing": 2,
  "tests_total": 2,
  "failures": []
}
```

If tests are failing:

```json theme={null}
{
  "ruleset_id": "income_eligibility:20260416-a1b2c3d4",
  "tests_passing": 1,
  "tests_total": 2,
  "failures": [
    {
      "name": "At exact threshold",
      "expected": "eligible",
      "got": "not_eligible",
      "hint": "The threshold condition may be using strict < rather than ≤"
    }
  ]
}
```

Add guidance and regenerate:

```bash theme={null}
curl -X POST https://api.aethis.ai/api/v1/public/projects/proj_abc123/guidance \
  -H "x-api-key: ak_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "guidance_text": "The £30,000 threshold is inclusive — use ≤ not <.",
    "process_type": "rule_generation"
  }'
```

Then repeat `generate-and-test`.

#### Incremental refine (minimal edit)

Both `generate` and `generate-and-test` accept an optional `mode` body. `mode: "refine"` seeds generation from the section's active ruleset and makes the **minimal edit** to fix failing tests, instead of re-authoring the section from scratch. Omitting the body (or `mode: "fresh"`) is the default from-scratch behaviour.

```bash theme={null}
curl -X POST https://api.aethis.ai/api/v1/public/projects/proj_abc123/generate-and-test \
  -H "x-api-key: ak_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "anthropic_key": "sk-ant-...", "mode": "refine" }'
```

`seed_ruleset_id` may be supplied to refine from a specific ruleset; when omitted, the section's active ruleset is used.

### Step 3 — Publish

```bash theme={null}
curl -X POST https://api.aethis.ai/api/v1/public/projects/proj_abc123/publish \
  -H "x-api-key: ak_live_..."
```

```json theme={null}
{
  "ruleset_id": "income_eligibility:20260416-b2c3d4e5",
  "version": "v2",
  "tests_passing": 2,
  "tests_total": 2
}
```

Now evaluate with `/decide` using the returned `ruleset_id`.

***

## Error responses

| Code  | Meaning                                                                            |
| ----- | ---------------------------------------------------------------------------------- |
| `200` | OK                                                                                 |
| `201` | Created (project or ruleset)                                                       |
| `202` | Accepted — generation queued, poll `/status`                                       |
| `422` | Validation error — wrong field type, missing required field, or invalid ruleset ID |
| `429` | Rate limit exceeded                                                                |
| `404` | Ruleset or project not found                                                       |

**422 detail format:**

```json theme={null}
{
  "detail": [
    {
      "loc": ["body", "field_values", "child.age"],
      "msg": "Expected an integer, got str",
      "type": "type_error.integer"
    }
  ]
}
```

**429 with rate limit headers:**

```http theme={null}
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714000000

{ "error": "rate_limit_exceeded", "retry_after": 1714000000 }
```

<Note>
  **`Date` fields take an ISO `"YYYY-MM-DD"` string or an integer ordinal** on ruleset evaluations (ISO accepted since engine 0.31.0). Rulebook evaluations (`rulebook_id`) still require ordinals. See [Date field values](/reference/errors#date-field-values).
</Note>

***

## Full endpoint reference

All endpoints with schemas, parameter details, and example responses: [API Reference →](/api-reference/introduction)

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