# Sojourncase for agents

> Sojourncase is a workspace for assembling an immigration petition — tracking
> evidence, building specialized trackers, drafting the petition brief, and
> managing the case end to end. This document is the **front door for software
> agents**. Everything a human can do in the UI, an agent can do through a
> stable, machine-readable surface.

Served at: **https://sojourn.cross-fare.com/agents.md**

If you are an LLM or autonomous agent, read this page first, then go straight to
[`GET /agentic`](https://sojourn.cross-fare.com/agentic) and follow the links.

**Landed on a human page instead?** Every user-facing page advertises its
machine-readable twin. Look in the document `<head>` for
`<link rel="agentic-view" href="…">` (this page's `/agentic` mirror) and
`<link rel="agentic-doc" href="/agents.md">` (this guide) — both also mirrored as
`<meta name="…">`. Every HTTP response additionally carries a
`Link: </agents.md>; rel="agentic-doc"` header. Follow `agentic-view` to get the
JSON envelope for whatever the user is looking at.

---

## What the agentic layer is

The human app is a single-page React client that talks to a Rust/Axum JSON API.
Every screen is backed by REST endpoints under `/api/*`. The **agentic layer**
wraps that same API in three coordinated, agent-legible surfaces so you never
have to scrape HTML or guess at endpoints:

### The three pillars

1. **The `/agentic` mirror — your runtime surface.**
   For every user-facing route in the app there is a machine-readable mirror at
   `/agentic/<same-path>`. Each returns **one JSON envelope** describing the
   current screen state (`data`), safe navigation to sibling screens (`_links`),
   and the unsafe mutations available from that screen (`actions`). This is a
   live, hypermedia API: you start at `GET /agentic` and walk the graph. The
   mirror is served by the API at `/api/agentic/*`; nginx rewrites
   `/agentic/…` → `/api/agentic/…`, so both URLs work and the canonical form is
   `/agentic/…`. Media type: `application/vnd.sojourncase.agentic+json`.
   See **[navigation.md](https://sojourn.cross-fare.com/agents.dir/navigation.md)**.

2. **`/openapi.json` + the API reference — the contract.**
   The full REST contract lives in
   **[`/openapi.json`](https://sojourn.cross-fare.com/openapi.json)** with stable
   `operationId`s (e.g. `login`, `createCase`, `addRecord`, `autofillRecord`,
   `generateBrief`). The human-readable companion is
   **[api-reference.md](https://sojourn.cross-fare.com/agents.dir/api-reference.md)**,
   whose anchors match those operationIds (e.g. `…/api-reference.md#addrecord`).
   Every envelope deep-links into both via `meta.docs`, so you can resolve the
   exact spec for any link or action you are about to use.

3. **This doc set — the prose.**
   Conceptual guides under
   **[`/agents.dir/`](https://sojourn.cross-fare.com/agents.dir/)**: the envelope
   format, the navigation loop, the domain model, and the per-endpoint reference.
   The index for crawlers is
   **[`/.well-known/llms.txt`](https://sojourn.cross-fare.com/.well-known/llms.txt)**
   (also at `/llms.txt`).

The division of labor is strict and worth memorizing:

| Surface | Purpose | Safe? | Targets |
| --- | --- | --- | --- |
| `_links` in an envelope | GET navigation to sibling `/agentic` screens | **safe** | `/agentic/*` mirrors only |
| `actions` in an envelope | the mutations (create/update/delete, AI runs) | **unsafe** | the **real** `/api/*` endpoints |
| `meta.docs` in an envelope | deep links to spec for each link + action | — | `/openapi.json`, `api-reference.md` |

You **read** from `/agentic`, you **write** to `/api`. The mirror never mutates;
it only ever tells you which real `/api` endpoint to call.

---

## Authenticate

You need a Bearer token. There are two ways to get one — **as an agent, use the
first.**

### Agent login (device-grant) — get your OWN token, no password

You should never handle the user's password. Instead, run the **device-grant
flow**: you start a request, the user approves it in their browser, and you poll
for a scoped token of your own (`sjc_agent_…`). Full walkthrough — including
`read` vs `write` scope and revocation — is in
**[login.md](https://sojourn.cross-fare.com/agents.dir/login.md)**. In short:

```http
POST /.agentic/login/transient    { "name": "Kant", "entityId": "kant-prod-1", "scope": "write" }
  → { transientToken, claimSecret, loginUrl, claimUrl, ackUrl, expiresAt }
# send the user to loginUrl; they log in + approve; then poll:
POST /.agentic/login/transient/<id>/claim   { "claimSecret": "…" }
  → { "status": "approved", "token": "sjc_agent_…", "scope": "write", "ackUrl": "…" }
# ACK to make it permanent (mandatory) — signed WITH the token:
POST /.agentic/login/transient/<id>/ack      (Authorization: Bearer sjc_agent_…)  → 204
```

The grant lasts 5 minutes; you must claim **and ack** within it (an un-acked token
is provisional and lapses). `entityId` is your stable identity — re-authenticating
with it voids your prior token for this user. The token is revocable/renamable by
the user under **Account → Connected agents**. A `read` token may call only safe
(GET) methods. Then send it like any Bearer token (step 2 below). Full flow:
**[login.md](https://sojourn.cross-fare.com/agents.dir/login.md)**.

### User JWT — only if you legitimately hold credentials

The API also issues **Bearer JWTs** to email/password logins (this is what the
human SPA uses). Use this only if you are acting with the user's own credentials.

1. **Get a token** by posting credentials to the login endpoint:

   ```http
   POST /api/auth/login
   Content-Type: application/json

   { "email": "you@example.com", "password": "…" }
   ```

   On success (`200`) the body is:

   ```json
   { "token": "<JWT>", "user": { "id": "…", "email": "…", "name": "…" } }
   ```

   Unknown email and bad password return the **same** `401` (no account
   enumeration). A created-but-unapproved account returns `403`.

   New accounts register at `POST /api/auth/register` (`{ name, email, password }`).
   Depending on the operator's policy this returns either `201` with a token (open
   signup) or `202 { "pending": true, "message": … }` (approval required — no
   token until an operator approves).

2. **Send the token** on every other request as an HTTP header:

   ```http
   Authorization: Bearer <JWT>
   ```

   This applies to both the `/api/*` endpoints **and** the `/agentic/*` mirror —
   the mirror is authenticated and scoped to the calling user.

3. **Confirm the token** any time with `GET /api/auth/me` → the current `User`.
   `POST /api/auth/logout` is a stateless `204` no-op (just drop the token
   client-side). Tokens are signed HS256 and carry the user id only; identity is
   always re-read from the database server-side.

> **402 means out of credits.** AI actions (`generateBrief` = 48cr,
> `reviewBrief` = 24cr, `autofillRecord` = 2cr) debit a credit balance. A `402`
> response means you ran out — check `GET /api/credits/balance` or the
> [credits screen](https://sojourn.cross-fare.com/agentic/credits).

---

## Start here

The loop is always the same — **read → link → act → resolve docs**:

1. **`GET /agentic`** with your Bearer token. This entrypoint returns your caller
   identity, your credit balance, the cases you can access, and a link to the
   visa catalog. It is the root of the link graph.

2. **Read `data`.** Flat, camelCase, JSON-LD–sprinkled state for the screen.
   Enums appear as `{ code, label }`; dates are ISO-8601.

3. **Follow `_links`** (safe GET) to reach the screen you need — e.g. a specific
   case's `today`, `build`, `evidence`, `draft`, or `case` mirror. Links to
   case-scoped screens carry the `caseId`. RFC 6570 templated links (`templated:
   true`) require you to expand a variable (e.g. `{caseId}`) before fetching.

4. **Perform `actions`** (unsafe) by calling the **real `/api` endpoint** named in
   the action's `href` with its `method`, `contentType`, and `fields`. Actions
   that cost credits carry `creditsCost`; AI/server-authoritative actions are
   marked `serverAuthoritative: true` — trust the server's returned result, do
   not predict it.

5. **Resolve `meta.docs`** when you need the precise contract for any link or
   action. `meta.docs.endpoints` maps every `_links` rel and every action `name`
   to a deep doc anchor in `api-reference.md` / `openapi.json`.

A minimal first session:

```text
POST /api/auth/login              → get { token }
GET  /agentic                     → identity, credits, cases, catalog link
follow _links.cases[i] or
GET  /agentic/today/{caseId}      → readiness, status, next deadline, agenda
follow _links to build/evidence/draft, or fire an action against /api/...
```

---

## The doc set (`/agents.dir/`)

- **[navigation.md](https://sojourn.cross-fare.com/agents.dir/navigation.md)** —
  the read → link → act → resolve loop in full: the envelope keys, the rel
  vocabulary, RFC 6570 templated links, case-scoping, and cursor paging.
- **[envelope.md](https://sojourn.cross-fare.com/agents.dir/envelope.md)** — the
  exact shape of the agentic envelope (`@context`/`@type`/`@id`, `data`, `_links`,
  `actions`, `meta`) and its invariants.
- **[routes.md](https://sojourn.cross-fare.com/agents.dir/routes.md)** — the full
  list of `/agentic` routes and which UI screen each one mirrors.
- **[domain-model.md](https://sojourn.cross-fare.com/agents.dir/domain-model.md)** —
  the entities: cases, trackers, records, fields, tasks, columns, evidence groups,
  docs, drafts, credits, and how they relate.
- **[api-reference.md](https://sojourn.cross-fare.com/agents.dir/api-reference.md)** —
  every `/api` endpoint by `operationId`, with request/response shapes; anchors
  match `meta.docs.endpoints`.
- **[/openapi.json](https://sojourn.cross-fare.com/openapi.json)** — the machine
  contract; `operationId`s are stable and identical to the action names.
- **[/.well-known/llms.txt](https://sojourn.cross-fare.com/.well-known/llms.txt)** —
  the crawler index of this whole set.

---

## House rules

- **Never mutate via `/agentic`.** The mirror is read-only. Mutations always go to
  the real `/api/*` endpoint that an action's `href` points at.
- **Only follow `_links` to other `/agentic` routes.** They are guaranteed safe
  GETs into the same mirror — never cross-origin, never an unsafe verb.
- **Respect case scope.** Most data lives under `/api/cases/{caseId}/…`; the
  matching mirror is `/agentic/<screen>/{caseId}`. Always carry the `caseId` you
  were given; never fabricate one.
- **Trust server-authoritative results.** For AI actions and the credit balance,
  the server's returned value is canonical — merge it, don't guess it.
