# Unio · agent API — auth & capabilities

Unio is a document-vault / data-room workbench. This is its READ-ONLY agent API:
inspect a data room (an "arrangement"/set) — inventory, document text, full-text
search, exact diffs between sets, and deterministic as-of state — provenance intact.

## Authenticate
Auth is a **scoped, revocable bearer grant**.
1. A Unio user mints a grant in-app (Agent grants), choosing a scope:
   - `arrangement` — one set (data room)
   - `library` — all of that user's sets
   - `document` — one Unio writing document, editable through a bounded session
   - `cli` — your visible writing documents, addressable by slug/path
2. The user shares the grant token with the agent.
3. Send it on every request: `Authorization: Bearer <grant-token>`.

(Today a human mints the grant. Unknown / revoked / expired / out-of-scope
tokens all return 404, never confirming what exists; 401 only for a
missing/malformed header.)

### User-claimed ceremony
Agents can also start a self-service claim ceremony:
1. `POST /agent/v1/identity` with `{"type":"service_auth","login_hint"?:string,"requested_scope"?:{"kind":"arrangement"|"library"}}`.
2. Show the returned `user_code` and `verification_uri` to the human.
3. Poll `POST /agent/v1/token` with `{"grant_type":"urn:unio:agent-auth:grant-type:claim","claim_token":"..."}`.

`login_hint` is display context only. The signed-in Unio session at the claim
page chooses the owner, scope, and expiry. The raw grant token is minted only
at the first successful poll and is returned once.

## Capabilities (with `Authorization: Bearer <token>`)
- `GET /agent/v1/start` — your in-scope sets + usage notes (start here once authed)
- `GET /claim?code={user_code}` — human claim page from the ceremony `verification_uri`
- `GET /agent/v1/sets` — list sets in scope
- `GET /agent/v1/sets/{id}/inventory` — folders, files, dates, redaction counts (JSON)
- `GET /agent/v1/sets/{id}/state?attribute=&asOf=` — deterministic state_of: yes/no/indeterminate + basis (asOf = ms or ISO)
- `GET /agent/v1/docs/{id}/text?offset=&max=` — extracted document text
- `GET /agent/v1/search?q=&set={id}&limit=` — full-text search (chunks, page numbers)
- `GET /agent/v1/grep?pattern=&doc=&set=&regex=&case=&matches=&context=&cursor=` — exact-text scan (literal default, safe regex subset); offsets feed /docs/{id}/text; resume truncated calls via next_cursor
- `GET /agent/v1/find?name=&set=&limit=` — locate documents by display name
- `GET /agent/v1/compare?a=&b=` — exact diff between two sets (library scope only)
- `GET /agent/v1/docs/{id}/markdown` — document-scope anchored markdown
- `POST /agent/v1/docs/{id}/session` — document-scope live edit session
- `GET /agent/v1/whoami` — account + scope for this token
- `GET /agent/v1/writing-docs` — list visible owner writing docs
- `PUT /agent/v1/session/{id}/content` — replace the working version markdown
- `PATCH /agent/v1/session/{id}/ops` — surgical anchored block ops against the
  working version (replace/insert/delete/append top-level blocks)
- `POST /agent/v1/session/{id}/propose|apply` — finish as a review draft or
  atomically apply as a new version; legacy aliases are `end` and `adopt`
- `apply` requires the live grant's `edit` capability, respects every
  narrowing Ask policy, and always refuses base drift
- `POST /agent/v1/chat/completions` — OpenAI-compatible cited answer grounded in
  your scope. Standard Chat Completions body + `return_citations` (default true)
  and `reasoning_effort`; library-scoped grants may pass `scope: [setId, …]`.
  Citations carry canonical document ids + verified page numbers. Spends the
  grant owner's credits; non-streaming in v1.

### Artifacts (work plane)
Scratch storage for YOUR working output — a running synthesis, a draft memo,
an analysis, a log. Explicitly non-canonical: artifacts never enter the vault,
sets, search, or exports; the lifecycle is draft → proposed → promoted, and
only review-gated promotion can ever make one canonical. Writes here are
draft-only (proposed content is frozen for review; promoted rows are permanent
provenance stubs), and you only ever see artifacts your own principal created.
Available to `arrangement` and `library` grants; an arrangement grant works
only inside its one set.
- `POST /agent/v1/artifacts` — create a draft (`kind`: synthesis|draft|analysis|log · `format`: md|txt|json · `title` · `content` · optional `source_document_ids`; library grants pass `arrangement_id`)
- `GET /agent/v1/artifacts?set=&status=&limit=` — list your artifacts in a set
- `GET /agent/v1/artifacts/{id}` — one artifact with content + source stamps
- `PUT /agent/v1/artifacts/{id}` — overwrite a draft in place (`content` and/or `title` and/or append `source_document_ids`)
- `POST /agent/v1/artifacts/{id}/propose` — submit a draft for review; content freezes and the review judges an immutable snapshot
- `POST /agent/v1/artifacts/{id}/withdraw` — pull a proposed artifact back to draft
- `POST /agent/v1/artifacts/{id}/discard` — soft-delete a draft
- `GET /agent/v1/artifacts/events?since=&limit=` — poll YOUR artifact events (created/updated/proposed/withdrawn/accepted/rejected/source_superseded/discarded), newest-first; pass the returned `next_cursor` back as `since` to page

Propose freezes the artifact and lands it in the room's review inbox. You
CANNOT accept or promote your own work — acceptance is the owner's (or the
autonomy-dial's) decision and happens in Unio, never through this API; a
rejection returns the artifact to draft with a note so you can revise and
re-propose.

You are implicitly SUBSCRIBED to events on artifacts you created (or cited): no
webhook, no subscription — poll `/artifacts/events` and page with `next_cursor`.
A `source_superseded` event means a vault doc your artifact cited was replaced,
so your synthesis may be stale; an `accepted`/`rejected` event is your proposal's
verdict.

md/txt artifacts carry a code-written provenance footer (agent, owner, date,
sources, artifact id), refreshed on every write; json artifacts keep their
payload untouched — provenance lives in the row.

## Rules
- Arrangement/library grants are read-only. Document/CLI writing grants stage
  isolated proposed versions. Only a live `edit` capability may promote the
  calling principal's own session; document policy may narrow but never upgrade
  that authority, and agents can never force base drift.
- Cite canonical document ids (they survive renames/moves), not paths.
- Never cite a page number returned as null (unverified page mapping).
- No file blobs are ever returned.

## MCP
Unio also ships a local stdio MCP server exposing the same capabilities — the most
direct integration for MCP-native tools (Claude Code, Codex, etc.).

## Machine spec
`GET /agent/v1/openapi.json` — OpenAPI 3.1 description of this API (no token required).
