# Agentic Route Map

Every user-facing SPA route has a machine-readable mirror at `/.agentic/<same-path>`,
rendered by the API (`/api/agentic/*`; nginx rewrites `/.agentic/` → `/api/agentic/`).
Each mirror returns one [envelope](./envelope.md): `data` (state), `_links` (SAFE GET
to sibling `/.agentic` routes), `actions` (UNSAFE verbs to the **real** `/api`), `meta`.

This file maps **every** `/.agentic` route → the UI route it mirrors → the data
entities it returns → its key `_links` rels → its actions and the real `/api`
endpoint each maps to.

> The `/api` endpoints below were confirmed against `apps/api/src/router.rs`
> (the single source of routing truth) and `apps/api/src/dto.rs` (entity shapes).
> Path params: `{caseId}`, `{trackerId}`, `{recordId}`, `{fieldId}`, `{taskId}`,
> `{docId}`, `{colId}`, `{visaId}`, `{req}`, `{findingId}`. The `{req}` segment
> carries spaces (e.g. a criterion name) and is URL-encoded.

---

## Quick index

| `/.agentic` route | mirrors UI route | kind |
|---|---|---|
| `/.agentic` | `/` (app entry / `/today`) | `entrypoint` |
| `/.agentic/new` | `/new` | `onboarding` |
| `/.agentic/visa-types` | (catalog; UI surfaced in `/new`) | `visa-catalog` |
| `/.agentic/visa-types/{visaId}` | (catalog detail) | `visa-def` |
| `/.agentic/today/{caseId}` | `/today` | `today` |
| `/.agentic/build/{caseId}` | `/build` | `build` |
| `/.agentic/build/{caseId}/{trackerId}` | `/build/{trackerId}` | `tracker` |
| `/.agentic/build/{caseId}/{trackerId}/records` | `/build/{trackerId}` (records) | `records` |
| `/.agentic/build/{caseId}/{trackerId}/records/{recordId}` | record sheet | `record` |
| `/.agentic/build/{caseId}/tasks` | `/build` (tasks board) | `tasks` |
| `/.agentic/build/{caseId}/tasks/{taskId}` | task detail | `task` |
| `/.agentic/evidence/{caseId}` | `/evidence` | `evidence` |
| `/.agentic/evidence/{caseId}/docs/{docId}` | doc detail | `doc` |
| `/.agentic/draft/{caseId}` | `/draft` | `draft` |
| `/.agentic/case/{caseId}` | `/case` | `case` |
| `/.agentic/case/{caseId}/events` | `/case` (history feed) | `events` |
| `/.agentic/credits` | credits panel | `credits` |

---

## 1. `/.agentic` — entrypoint

- **Mirrors:** the app root / `/today` landing.
- **Entities:** caller identity (`User` = `{name,email}`), credit `balance`,
  accessible cases (summary list of `Case` → `{id,name,visa,status,stage}`),
  link to the visa catalog.
- **`_links`:** `self`, `describedby`; `visaTypes` → `/.agentic/visa-types`;
  `new` → `/.agentic/new`; `credits` → `/.agentic/credits`; `item`/`case` per
  accessible case → `/.agentic/today/{caseId}`.
- **Actions:**

| action | method | real `/api` endpoint | operationId |
|---|---|---|---|
| `createCase` | POST | `/api/cases` | `createCase` |

> Read sources for the list: `GET /api/cases`, `GET /api/auth/me`,
> `GET /api/credits/balance`.

---

## 2. `/.agentic/new` and `/.agentic/visa-types[/{visaId}]` — visa catalog → create case

- **Mirrors:** `/new` (Onboarding) and the visa-definition catalog.
- **Entities:** `VisaDef` (`{id,name,full,rule:{kind,n},finalMerits,offer?,reqs[]}`).
  `/.agentic/new` and `/.agentic/visa-types` return the catalog list;
  `/.agentic/visa-types/{visaId}` returns one `VisaDef`.
- **`_links`:** `self`, `describedby`, `up` → `/.agentic`; `item`/`visaDef` per
  visa → `/.agentic/visa-types/{visaId}`.
- **Actions:**

| action | method | real `/api` endpoint | operationId |
|---|---|---|---|
| `createCase` | POST | `/api/cases` (body `{visa, id?}`) | `createCase` |

> Read sources: `GET /api/visa-types`, `GET /api/visa-types/{visaId}`.

---

## 3. `/.agentic/today/{caseId}` — today

- **Mirrors:** `/today`.
- **Entities:** readiness summary, case `status` + `stage` (`{code,label}`),
  next `deadline` (from `Task` where `deadline=true`, soonest `date`), agenda
  (upcoming/undone `Task[]`).
- **`_links`:** `self`, `describedby`, `case` → `/.agentic/case/{caseId}`,
  `build` → `/.agentic/build/{caseId}`, `evidence` → `/.agentic/evidence/{caseId}`,
  `draft` → `/.agentic/draft/{caseId}`, `credits` → `/.agentic/credits`.
- **Actions:** none of its own (Today is a read/roll-up surface). Task actions are
  reached via `/.agentic/build/{caseId}/tasks`.

> Read source: `GET /api/cases/{caseId}` (the fully-assembled `Case`).

---

## 4. `/.agentic/build/{caseId}` — build board

- **Mirrors:** `/build`.
- **Entities:** `Column[]` (board columns), `Task[]` (cards), trackers summary
  (`Tracker[]` headers + record counts).
- **`_links`:** `self`, `describedby`, `up`/`today` → `/.agentic/today/{caseId}`,
  `tasks` → `/.agentic/build/{caseId}/tasks`, `tracker` (templated) →
  `/.agentic/build/{caseId}/{trackerId}`, `case`, `evidence`, `draft`.
- **Actions:**

| action | method | real `/api` endpoint | operationId |
|---|---|---|---|
| `addTask` | POST | `/api/cases/{caseId}/tasks` | `addTask` |
| `addTracker` | POST | `/api/cases/{caseId}/trackers` | `addTracker` |
| `addColumn` | POST | `/api/cases/{caseId}/columns` | `addColumn` |

---

## 5. `/.agentic/build/{caseId}/{trackerId}` — tracker

- **Mirrors:** `/build/{trackerId}`.
- **Entities:** `Tracker` meta (`name,color,preset`), `fields[]` (`TrackerField`),
  `statuses[]`, `countByStatus` (records per status).
- **`_links`:** `self`, `describedby`, `up` → `/.agentic/build/{caseId}`,
  `records` → `.../records`, `record` (templated) → `.../records/{recordId}`,
  `case`.
- **Actions:**

| action | method | real `/api` endpoint | operationId |
|---|---|---|---|
| `addRecord` | POST | `/api/cases/{caseId}/trackers/{trackerId}/records` | `addRecord` |
| `addTrackerField` | POST | `/api/cases/{caseId}/trackers/{trackerId}/fields` | `addTrackerField` |
| `configureTracker` | PUT | `/api/cases/{caseId}/trackers/{trackerId}/configure` | `configureTracker` |
| `updateTracker` (rename/recolor) | PATCH | `/api/cases/{caseId}/trackers/{trackerId}` | `updateTracker` |
| `deleteTracker` | DELETE | `/api/cases/{caseId}/trackers/{trackerId}` | `deleteTracker` |

> See the full worked envelope for this route in [envelope.md §9](./envelope.md).

---

## 6. `/.agentic/build/{caseId}/{trackerId}/records` — record collection

- **Mirrors:** the records table inside `/build/{trackerId}`.
- **Entities:** `TrackerRecord[]` (`{id,values,status,nextAction,req,notes?}`),
  plus the parent tracker's `fields[]`/`statuses[]` for column resolution.
- **`_links`:** `self`, `describedby`, `up` → `/.agentic/build/{caseId}/{trackerId}`,
  `item`/`record` (templated) → `.../records/{recordId}`.
- **Actions:**

| action | method | real `/api` endpoint | operationId |
|---|---|---|---|
| `addRecord` | POST | `/api/cases/{caseId}/trackers/{trackerId}/records` | `addRecord` |

---

## 7. `/.agentic/build/{caseId}/{trackerId}/records/{recordId}` — record sheet

- **Mirrors:** the RecordSheet detail.
- **Entities:** `TrackerRecord` — `values` (keyed by field id), `status`,
  `nextAction` (`substatus`/`nextAction`), `req`, `notes`, `privateNotes`,
  linked docs and linked tasks (resolved from `Doc.source` / `Task.linkedRecordId`).
- **`_links`:** `self`, `describedby`, `up` → `.../{trackerId}`,
  `tracker` → `/.agentic/build/{caseId}/{trackerId}`; per linked task →
  `/.agentic/build/{caseId}/tasks/{taskId}`; per linked doc →
  `/.agentic/evidence/{caseId}/docs/{docId}`.
- **Actions:**

| action | method | real `/api` endpoint | operationId | notes |
|---|---|---|---|---|
| `updateRecord` (patch) | PATCH | `/api/cases/{caseId}/trackers/{trackerId}/records/{recordId}` | `updateRecord` | partial: values/status/nextAction/req/notes |
| `setRecordValue` | PUT | `/api/cases/{caseId}/trackers/{trackerId}/records/{recordId}/values/{fieldId}` | `setRecordValue` | single cell |
| `autofillRecord` | POST | `/api/cases/{caseId}/trackers/{trackerId}/records/{recordId}/autofill` | `autofillRecord` | **2 credits**, `serverAuthoritative:true` |
| `spawnTask` | POST | `/api/cases/{caseId}/trackers/{trackerId}/records/{recordId}/spawn-task` | `spawnTask` | creates a `Task` linked to this record |
| `deleteRecord` | DELETE | `/api/cases/{caseId}/trackers/{trackerId}/records/{recordId}` | `deleteRecord` | |

---

## 8. `/.agentic/build/{caseId}/tasks[/{taskId}]` — tasks board / task

- **Mirrors:** the tasks board within `/build` and a task's detail.
- **Entities:** `Task` (`{id,title,colId,priority?,req,date,deadline,notes,steps[],
  docIds[],done,unreviewed?,linkedRecordId?}`). The collection returns `Task[]` +
  `Column[]`; `/{taskId}` returns one `Task`.
- **`_links`:** `self`, `describedby`, `up` → `/.agentic/build/{caseId}`;
  per task `item`/`task` → `.../tasks/{taskId}`; if `linkedRecordId`, `record` →
  `/.agentic/build/{caseId}/{trackerId}/records/{recordId}`.
- **Actions:**

| action | method | real `/api` endpoint | operationId | notes |
|---|---|---|---|---|
| `addTask` | POST | `/api/cases/{caseId}/tasks` | `addTask` | collection route |
| `updateTask` | PUT | `/api/cases/{caseId}/tasks/{taskId}` | `updateTask` | full replace of mutable fields |
| `markDone` | PUT | `/api/cases/{caseId}/tasks/{taskId}` (body `{done:true}`) | `updateTask` | convenience over `updateTask` |
| `deleteTask` | DELETE | `/api/cases/{caseId}/tasks/{taskId}` | `deleteTask` | |

> There is no dedicated mark-done endpoint; `markDone` is `updateTask` with
> `done:true`. The API exposes task update as **PUT** (full replace), not PATCH.

---

## 9. `/.agentic/evidence/{caseId}[/docs/{docId}]` — evidence

- **Mirrors:** `/evidence` and a doc's detail.
- **Entities:** `Group[]` (`{id,name,kind,req?,strength?}`), `Doc[]`
  (`{id,title,groupId,status,type,date,source?,file?}`), per-group `strength`
  (`{code,label}` band), gap notes. `/docs/{docId}` returns one `Doc` with its
  `DocFile` metadata if attached.
- **`_links`:** `self`, `describedby`, `up`/`case` → `/.agentic/case/{caseId}`;
  per doc `item`/`doc` → `.../docs/{docId}`; for a linked doc, `source` → the
  originating `/.agentic/build/.../records/{recordId}` or
  `/.agentic/build/{caseId}/tasks/{taskId}`.
- **Actions:**

| action | method | real `/api` endpoint | operationId | notes |
|---|---|---|---|---|
| `addGroup` | POST | `/api/cases/{caseId}/groups` | `addGroup` | |
| `addDoc` | POST | `/api/cases/{caseId}/docs` | `addDoc` | body `{groupId, id?}` |
| `addLinkedDoc` | POST | `/api/cases/{caseId}/docs/linked` | `addLinkedDoc` | doc tied to a Build effort |
| `setDocStatus` | PATCH | `/api/cases/{caseId}/docs/{docId}/status` | `setDocStatus` | enum `need\|draft\|final` |
| `setDocSource` | PUT | `/api/cases/{caseId}/docs/{docId}/source` | `setDocSource` | link/unlink source (`null` clears) |
| `uploadDocFile` | POST | `/api/cases/{caseId}/docs/{docId}/file` | `uploadDocFile` | 25 MB cap; `x-filename` header |
| `downloadDocFile` | GET | `/api/cases/{caseId}/docs/{docId}/file` | `downloadDocFile` | (bytes; not an envelope) |
| `deleteDocFile` | DELETE | `/api/cases/{caseId}/docs/{docId}/file` | `deleteDocFile` | |
| `deleteDoc` | DELETE | `/api/cases/{caseId}/docs/{docId}` | `deleteDoc` | |
| `toggleClaim` | PUT | `/api/cases/{caseId}/claims/{req}` | `toggleClaim` | toggles `claims[req]` (criterion) |

> "set-source" in the contract is the `PUT .../docs/{docId}/source` (`setDocSource`).
> "upload" is `POST .../docs/{docId}/file` (`uploadDocFile`). `toggle-claim` is the
> shared `PUT /api/cases/{caseId}/claims/{req}` route, surfaced here and on the case.

---

## 10. `/.agentic/draft/{caseId}` — draft

- **Mirrors:** `/draft`.
- **Entities:** `Draft` (`{generatedAt,sections[],review?,signedOff}`),
  `BriefSection[]` (`{id,title,req,body,sources[]}`), review `findings[]`
  (`{id,kind,req,title,detail,band,taskId?}`), `signedOff` flag.
- **`_links`:** `self`, `describedby`, `up`/`case` → `/.agentic/case/{caseId}`,
  `credits` → `/.agentic/credits`; per finding-with-task, `task` →
  `/.agentic/build/{caseId}/tasks/{taskId}`.
- **Actions:**

| action | method | real `/api` endpoint | operationId | notes |
|---|---|---|---|---|
| `generateBrief` | POST | `/api/cases/{caseId}/draft/generate` | `generateBrief` | **48 credits**, serverAuthoritative |
| `reviewBrief` | POST | `/api/cases/{caseId}/draft/review` | `reviewBrief` | **24 credits**, serverAuthoritative |
| `findingToTask` | POST | `/api/cases/{caseId}/draft/findings/{findingId}/task` | `findingToTask` | spawn a `Task` from a finding |
| `signoffBrief` | POST | `/api/cases/{caseId}/draft/signoff` | `signoffBrief` | sets `signedOff` |

> Read source: `GET /api/cases/{caseId}/draft`.

---

## 11. `/.agentic/case/{caseId}[/events]` — case surface

- **Mirrors:** `/case` and its history feed.
- **Entities:** `Case` header (`status,stage,target,receipt,attorney,visa,field`),
  `claims` (criterion → bool) with per-group `strength`, `people[]` (`Person`),
  `events[]` (`CaseEvent`). `/events` is the cursor-paged history feed (newest
  first).
- **`_links`:** `self`, `describedby`, `today`, `build`, `evidence`, `draft`,
  `events` → `/.agentic/case/{caseId}/events`, `visaDef` →
  `/.agentic/visa-types/{visaId}`.
- **Actions:**

| action | method | real `/api` endpoint | operationId | notes |
|---|---|---|---|---|
| `patchCase` | PATCH | `/api/cases/{caseId}` | `patchCase` | name/status/stage/target/receipt/field/attorney |
| `toggleClaim` | PUT | `/api/cases/{caseId}/claims/{req}` | `toggleClaim` | toggles `claims[req]` |
| `undoEvent` | POST | `/api/cases/{caseId}/events/{eventId}/undo` | `undoEvent` | reverses an event |
| `deleteCase` | DELETE | `/api/cases/{caseId}` | `deleteCase` | owner only |

> `/.agentic/case/{caseId}/events` (kind `events`) shares the parent's actions and
> adds `next` paging via `_links`. Read source: `GET /api/cases/{caseId}/events`.

---

## 12. `/.agentic/credits` — credits

- **Mirrors:** the credits panel.
- **Entities:** `CreditBalance` (`{balance}`) + ledger (`LedgerEntry[]` =
  `{id,delta,reason,at}`, `at` ISO-8601).
- **`_links`:** `self`, `describedby`, `up` → `/.agentic`.
- **Actions:** none (credits are spent implicitly by paid actions —
  `autofillRecord` 2, `reviewBrief` 24, `generateBrief` 48; there is no
  client-initiated purchase endpoint in `router.rs`). `caseId` in `meta` is `null`.

> Read sources: `GET /api/credits/balance`, `GET /api/credits/ledger`.
> operationId for the read is `getCredits`.

---

## Action → real `/api` endpoint master table

Every mutating action across all routes and the exact endpoint it posts to
(verified against `apps/api/src/router.rs`):

| operationId | method | real `/api` endpoint |
|---|---|---|
| `createCase` | POST | `/api/cases` |
| `getCase` (read) | GET | `/api/cases/{caseId}` |
| `patchCase` | PATCH | `/api/cases/{caseId}` |
| `deleteCase` | DELETE | `/api/cases/{caseId}` |
| `toggleClaim` | PUT | `/api/cases/{caseId}/claims/{req}` |
| `addColumn` | POST | `/api/cases/{caseId}/columns` |
| `updateColumn` | PATCH | `/api/cases/{caseId}/columns/{colId}` |
| `deleteColumn` | DELETE | `/api/cases/{caseId}/columns/{colId}` |
| `moveColumn` | POST | `/api/cases/{caseId}/columns/{colId}/move` |
| `addTask` | POST | `/api/cases/{caseId}/tasks` |
| `updateTask` | PUT | `/api/cases/{caseId}/tasks/{taskId}` |
| `deleteTask` | DELETE | `/api/cases/{caseId}/tasks/{taskId}` |
| `spawnTask` | POST | `/api/cases/{caseId}/trackers/{trackerId}/records/{recordId}/spawn-task` |
| `addGroup` | POST | `/api/cases/{caseId}/groups` |
| `addDoc` | POST | `/api/cases/{caseId}/docs` |
| `addLinkedDoc` | POST | `/api/cases/{caseId}/docs/linked` |
| `deleteDoc` | DELETE | `/api/cases/{caseId}/docs/{docId}` |
| `setDocStatus` | PATCH | `/api/cases/{caseId}/docs/{docId}/status` |
| `setDocSource` | PUT | `/api/cases/{caseId}/docs/{docId}/source` |
| `uploadDocFile` | POST | `/api/cases/{caseId}/docs/{docId}/file` |
| `downloadDocFile` | GET | `/api/cases/{caseId}/docs/{docId}/file` |
| `deleteDocFile` | DELETE | `/api/cases/{caseId}/docs/{docId}/file` |
| `addTracker` | POST | `/api/cases/{caseId}/trackers` |
| `updateTracker` | PATCH | `/api/cases/{caseId}/trackers/{trackerId}` |
| `deleteTracker` | DELETE | `/api/cases/{caseId}/trackers/{trackerId}` |
| `addTrackerField` | POST | `/api/cases/{caseId}/trackers/{trackerId}/fields` |
| `configureTracker` | PUT | `/api/cases/{caseId}/trackers/{trackerId}/configure` |
| `addRecord` | POST | `/api/cases/{caseId}/trackers/{trackerId}/records` |
| `updateRecord` | PATCH | `/api/cases/{caseId}/trackers/{trackerId}/records/{recordId}` |
| `deleteRecord` | DELETE | `/api/cases/{caseId}/trackers/{trackerId}/records/{recordId}` |
| `setRecordValue` | PUT | `/api/cases/{caseId}/trackers/{trackerId}/records/{recordId}/values/{fieldId}` |
| `autofillRecord` | POST | `/api/cases/{caseId}/trackers/{trackerId}/records/{recordId}/autofill` |
| `generateBrief` | POST | `/api/cases/{caseId}/draft/generate` |
| `reviewBrief` | POST | `/api/cases/{caseId}/draft/review` |
| `signoffBrief` | POST | `/api/cases/{caseId}/draft/signoff` |
| `findingToTask` | POST | `/api/cases/{caseId}/draft/findings/{findingId}/task` |
| `undoEvent` | POST | `/api/cases/{caseId}/events/{eventId}/undo` |
| `login` | POST | `/api/auth/login` |
| `register` | POST | `/api/auth/register` |
| `getCredits` (read) | GET | `/api/credits/balance`, `/api/credits/ledger` |

## Agent login (device-grant)

The bootstrap that lets an agent get its OWN scoped token — full walkthrough in
[login.md](login.md). Public routes are also exposed at `/.agentic/login/*`
(nginx rewrites → `/api/agentic/login/*`).

| operationId | method | path | auth |
| --- | --- | --- | --- |
| `agentLoginBootstrap` | POST | `/.agentic/login/transient` | public (needs `name` + `entityId`) |
| `agentLoginClaim` | POST | `/.agentic/login/transient/{id}/claim` | claim secret |
| `agentLoginAck` | POST | `/.agentic/login/transient/{id}/ack` | the token itself (Bearer) |
| `agentLoginStatus` | GET | `/api/agentic/login/transient/{id}` | user Bearer |
| `agentLoginApprove` | POST | `/api/agentic/login/transient/{id}/approve` | user Bearer |
| `agentLoginDeny` | POST | `/api/agentic/login/transient/{id}/deny` | user Bearer |
| `listAgentTokens` | GET | `/api/auth/agent-tokens` | user Bearer |
| `renameAgentToken` | PATCH | `/api/auth/agent-tokens/{tokenId}` | user Bearer |
| `revokeAgentToken` | DELETE | `/api/auth/agent-tokens/{tokenId}` | user Bearer |

All paid actions and their fixed credit costs: `autofillRecord` = 2,
`reviewBrief` = 24, `generateBrief` = 48 (confirmed in
`apps/api/src/routes/records.rs` and `apps/api/src/routes/draft.rs`).
