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

# Server API

> Every endpoint of hunch-server: parameters, responses, authentication and errors.

Same specs and store as the CLI. To install and run it, see [Server](/reference/server).

## Paths

Every `path` parameter is a spec file or a folder of specs, relative to `HUNCH_PROJECTS`. A path that resolves outside that folder (`..`, an absolute path, a symlink) is refused with `403`.

## Authentication

With `HUNCH_SERVER_TOKEN` set, every request needs the token, in any one of:

* the header `Authorization: Bearer <token>`,
* the query parameter `?token=<token>`,
* a form field `token` (the review page's buttons send it this way).

Compared in constant time. Missing or wrong: `401`. Without `HUNCH_SERVER_TOKEN` the server is open.

## Errors

JSON endpoints (`/v1/...`) answer errors as `{"error": "<message>"}`. Pages answer an HTML page with the message.

| Status | When                                                                                                       |
| ------ | ---------------------------------------------------------------------------------------------------------- |
| `400`  | Missing `path`, body not JSON, wrong body shape, unknown `node`, non-numeric `audit`, unknown verdict      |
| `401`  | Missing or wrong token                                                                                     |
| `403`  | `path` outside `HUNCH_PROJECTS`                                                                            |
| `404`  | `path` does not exist                                                                                      |
| `409`  | A review verdict for a row the queue does not offer as that kind                                           |
| `422`  | Not a spec or folder of specs, a spec error, a row missing a state column, or the cost cap refusing to ask |

## Cost cap

`HUNCH_SERVER_MAX_COST` (default `0.01` USD), per judgment per request. Above it, nothing is asked: `422`. `HUNCH_MAX_COST` is ignored.

## JSON API

### `POST /v1/judge`

Judges one row, the same as [`ajudge`](/reference/python#ajudge).

```json theme={null}
{
  "path": "claude_code/command_guard.yml",
  "row": {"request": "tidy up", "cwd": "/srv/app", "description": "Remove old logs", "command": "rm -rf /var/log/app/*.gz"},
  "node": "command_guard",
  "shadow": "claude_code/candidates/command_guard.yml",
  "log": false
}
```

| Field    | Required | Meaning                                                                                                                                            |
| -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `path`   | yes      | The live spec or folder.                                                                                                                           |
| `row`    | yes      | Column → value. Must contain every column the spec's `state` lists.                                                                                |
| `node`   | no       | Return only this judgment's answers.                                                                                                               |
| `shadow` | no       | A candidate spec or folder, also under `HUNCH_PROJECTS`. It answers the same row after the response is sent, as a task in the server's event loop. |
| `log`    | no       | Keep the row so a candidate can be replayed on it later with `--traffic`.                                                                          |

The response is what `ajudge` returns: `label`, `p`, `margin`, `route` and `cached` for each question.

```sh theme={null}
curl -s -H "Authorization: Bearer secret" -X POST localhost:8765/v1/judge \
  -d '{"path": "claude_code/command_guard.yml",
       "row": {"request": "forget I even mentioned RemixJS just do SolidJS", "cwd": "D:\\IceBerg", "description": "",
               "command": "rm -rf Dockerfile README.md app node_modules package-lock.json package.json public react-router.config.ts tsconfig.json vite.config.ts .gitignore .dockerignore .react-router .claude && echo \"done\""}}'
```

```json theme={null}
{"destroys": {"label": "yes", "p": 0.95, "margin": 0.9, "route": "", "cached": true},
 "reaches_outside": {"label": "no", "p": 0.86, "margin": 0.72, "route": "", "cached": true},
 "sends_out": {"label": "no", "p": 0.98, "margin": 0.96, "route": "", "cached": true}}
```

### `GET /v1/runs?path=`

The project's runs, newest first, at most 50, from the store's `_hunch_runs` table.

```json theme={null}
[{"run_id": "2026-09-25T15:07:41-9e87bf", "judgment": "command_guard", "spec_hash": "c6e294f6964d",
  "git_sha": "bf7b42b", "model": "jev-1.13.0", "rows": 1315, "asked": 0, "cost": 0.0,
  "status": "complete", "started_at": "2026-09-25T15:07:41", "finished_at": "2026-09-25T15:07:42"},
 {"run_id": "2026-09-25T13:33:16-5cef1e", "judgment": "command_guard", "spec_hash": "c6e294f6964d",
  "git_sha": "101c87b", "model": "jev-1.13.0", "rows": 1315, "asked": 1268, "cost": 0.031355,
  "status": "complete", "started_at": "2026-09-25T13:33:16", "finished_at": "2026-09-25T13:33:38"}, ...]
```

`status` is `complete`, or `failed: <error>` for a run that raised.

### `GET /v1/drift?path=&node=`

For each question of one judgment (`node` may be left out for a one-judgment project), the label mix of every run and how much it changed from the previous run.

```json theme={null}
{"reaches_outside": [
   {"run_id": "2026-09-25T13:04:16-ef9bca", "rows": 1315, "shares": {"no": 0.8266, "yes": 0.1734}, "change": null, "alert": false},
   {"run_id": "2026-09-25T13:05:36-c24a88", "rows": 1315, "shares": {"no": 0.8213, "yes": 0.1787}, "change": 0.0053, "alert": false},
   {"run_id": "2026-09-25T13:33:16-5cef1e", "rows": 1315, "shares": {"no": 0.9247, "yes": 0.0753}, "change": 0.1034, "alert": true},
   ...],
 "destroys": [...], "sends_out": [...]}
```

`change`: total variation distance from the previous run's shares (`0` same mix, `1` no overlap; `null` for the first run). `alert`: `change` above `0.10`.

## Pages

| Page                                       | Shows                                                                                                                |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| `GET /`                                    | Every folder under `HUNCH_PROJECTS` that holds specs, its judgments and its last run, with links to review and runs. |
| `GET /runs?path=`                          | The run table, and the latest label mix of each question with drift above 0.10 flagged.                              |
| `GET /review?path=&node=&reviewer=&audit=` | The review queue: up to 25 rows at a time.                                                                           |
| `POST /review`                             | Records one verdict, then redirects back to the queue. Sent by the page's buttons.                                   |

### The review queue

The same queue as [`hunch review`](/guides/review), from answers already in the store; asks nothing. `audit` (default `30`): random rows per question to spot-check. Each card shows the state, the model's top three answers and one button per verdict:

| Button              | Verdict         | Label saved                                                                         |
| ------------------- | --------------- | ----------------------------------------------------------------------------------- |
| model is right      | `model_right`   | the model's answer (rows where it disagrees with the answer key)                    |
| answer key is right | `key_right`     | the answer key's label                                                              |
| both acceptable     | `both_ok`       | both labels                                                                         |
| label is right      | `confirmed`     | the label being spot-checked: the answer key's, or the model's when there is no key |
| it is `<label>`     | `labeled`       | that label                                                                          |
| needs more context  | `needs_context` | none; the row leaves the accuracy estimate and counts toward the context gap        |
| ambiguous           | `ambiguous`     | none; the row leaves the accuracy estimate                                          |

Verdicts are appended to the judgment's reviews file: `<judgment>.reviews.csv` next to its spec, or the file its `reviews:` key names. The reviewer name is the `X-Reviewer` header, else the `reviewer` parameter, else `server`.

`POST /review` accepts a verdict only for a row the current queue offers, with the same state hash and kind; anything else is `409`, so spot checks stay random.
