<!-- GENERATED from app/src/lib/apiManifest.ts by scripts/gen-api-docs.ts — do not edit by hand. -->
# FlowGraph API (v1.0.0)

FlowGraph is **agent-native**: agents read and write your graph through the MCP
server, and every write flows through the same **governed, lock-aware** path as a
human edit. A write to a human-approved (`locked`) atom — or one the AI lacks the
`modify` capability for — is **rejected**. See `docs/fgraph-format-v1.md` for the
full data model and `app/mcp/README.md` to run the server.

## MCP tools

| Tool | Args | Summary |
|---|---|---|
| `list_canvases` | — | List canvases in the vault (id, name, node count). |
| `get_canvas` | canvasId | Get a canvas — its nodes and edges — by id. |
| `query_graph` | canvasId, query | Edge-aware query: text · facet · tags · relType · traverse(up/down). Returns matching node ids + titles. |
| `flowgraph_retrieve` | query, handle, canvasId, k, hops | Graph-CCR retrieval (READ-ONLY): pull exactly the sub-graph you need. Give a `query` (semantic/keyword seed → structural expansion) OR a `handle` (a retrieval handle "canvasId::nodeId" from an assembled context packet) → its original atom + neighbourhood. A STALE handle returns a recovery hint, never an error. Output is governance-aware (atoms without an AI use-as-context grant come back degraded, no body) and provenance-stamped. |
| `create_canvas` | name | Create a new empty canvas; returns its id. |
| `apply_graph_patch` 🔒 | canvasId, patch | Apply a GraphPatch (ops[]) through the GOVERNED, provenance-stamped, lock-aware write path. A write to a human-approved (locked) atom — or one the AI lacks the "modify" capability for — is REJECTED. |
| `refresh_codebase_map` 🔒 | canvasId, repos, pruneRemoved | Refresh a multi-repo (fleet) codebase map: pass each repo's label + package name + declared dependencies (with the manifest line each is declared on), and this stages a governed ghost-draft that (re)builds one card per repo and the typed `depends` cross-repo edges, each carrying its manifest-line receipt. Reconciled against the target canvas — existing repo cards update, new repos add, the edge set recomputes — so a scheduled re-run is idempotent and never duplicates. A human accepts it in the Review Inbox (agents never auto-apply). Use it as the cron/agent codebase-refresh recipe. |
| `get_canvas_digest` | canvasId, nodeIds, hops, budget | Read a canvas as an FDG digest (READ-ONLY): the anchored knowledge grammar the in-app AI reads and writes. Every card, section, entry and group carries a short anchor ([c3], [c3.f2], [c3.f2.i4], [g1]) you cite when you write back with `submit_knowledge_draft`. `nodeIds` focuses the read (those cards + their neighbours render in FULL, the rest as an outline); `hops` widens that full-fidelity ring; `budget` caps the characters (an honest tail line says what was withheld — never a silent truncation). Returns the digest text, the anchor→atom map, per-card fidelity, stats, and a `digestId` — pass that back to `submit_knowledge_draft` so your anchors mean the atoms you actually read. The cheap way to orient before writing; complements `flowgraph_retrieve`. |
| `submit_knowledge_draft` 🔒 | canvasId, draft, mode, digestId | WRITE KNOWLEDGE, NOT OPS — the preferred way to write anything content-bearing. Submit a draft in FDG (the markdown grammar `get_canvas_digest` speaks) and a DETERMINISTIC compiler turns it into a GraphPatch applied through the same governed, lock-aware, provenance-stamped path as every other write; a locked atom is refused. `mode:"apply"` (default) writes it; `mode:"stage"` lands it as a Review-Inbox ghost-draft for a human to accept. On ANY compile or validation problem NOTHING is written — you get indexed errors[], every dropped block with its line and reason, and a fix hint, so you can correct and resubmit. Returns applied/newCards/anchorMap/dropped/depth/questions/suggestions, so your [anchors] stay addressable and you can relay the AI's questions or chain its suggested next moves. `apply_graph_patch` remains for surgical, programmatic ops. |
| `add_card` 🔒 | canvasId, title, body | Convenience: add one card to a canvas. |
| `connect` 🔒 | canvasId, source, target, relType, label | Convenience: connect two cards with a typed relationship. |
| `list_skills` | readyOnly | List reusable SKILLS (promoted action items) — name, the card/canvas they live on, their inner-canvas id, and whether they are READY (inner canvas non-empty). The agent capability catalog. |
| `list_due_reviews` | window, assignee | The ranked review agenda — scheduled reviews across the vault joined with attention state, bucket (overdue/today/week/later), blast-radius and owner. The same agenda the human sees. |
| `schedule_review` 🔒 | canvasId, nodeId, when, assignee, note | Propose a review date on an atom (ISO date or relative like "in 2 weeks"). A GOVERNED write: stamped provenance.kind=ai, REJECTED on human-approved (locked) atoms, surfaced for the human in the calendar / Review Inbox — agents never silently sign off. |
| `reschedule_review` 🔒 | canvasId, nodeId, when, reason | Move an atom’s existing review to a new date WITH A REQUIRED REASON (lands in the atom’s history). Governed; rejected on locked atoms. Use to sequence a review after an upstream one resolves. |
| `complete_review` 🔒 | canvasId, nodeId, findings | Propose marking a scheduled review DONE plus optional findings — as a governed write the human sees. NEVER approves or locks (only a human signs off); just records that the agent did a first-pass review. |
| `list_actions` | canvasId | EVERY action across the vault (not just promoted skills): its address 4-tuple {canvasId,nodeId,facetId,itemId}, text, binding, current test status, unmet prerequisites, and strategy. The agent’s runnable-work catalog. |
| `get_action` | canvasId, nodeId, facetId, itemId | One action’s FULL contract (binding + schemas, test, inputs, loop policy, strategy) plus its recorded runs, by address 4-tuple. |
| `run_action_test` 🔒 | canvasId, nodeId, facetId, itemId | Run the DRY-RUN pipeline (precondition → contract-check → deterministic; no world touched, no network) against an action. Returns the full ActionRun; writes `action.test` through the governed patch and records the run in the agent ledger. It can never approve, lock, or execute live. |
| `report_action_run` 🔒 | canvasId, nodeId, facetId, itemId, runId, verdict, live, steps, artifacts, tokens, costUsd, note | Driver B: report a run YOU executed with your own tools — steps, verdict, artifacts, tokens/cost, and your identity. Schema-validated; recorded as ActionRun{driver:"agent"} + a RunSummary. A “live” claim is labeled AGENT-CLAIMED (never presented as app-verified execution). Requires the `execute` AI capability on the action atom — WITHOUT it the report stages as a ghost-draft for human accept. Idempotent on runId. |
| `list_runs` | canvasId, nodeId, itemId, verdict, driver, limit | The Control-Tower read: recorded runs (app + agent) filtered by canvas / action address / verdict / driver, newest first. |
| `ifc_inventory` | modelId | A Smart IFC model’s data-first inventory (READ): schema, element/storey/space/type counts, per-class counts, storeys, and materials — DERIVED from the parsed model (never AI-guessed). `modelId` is a registered model id OR the model’s sha256 (content identity). |
| `ifc_query` | modelId, query, text | Run the FlowGraph IFC query grammar over a model’s element index (READ): e.g. `class:IfcDoor storey:"Level 2" Pset_DoorCommon.FireRating=EI30`. Returns matched elements (GlobalId · class · name · storey), their GlobalIds, a mini-QTO quantity summary, and honest parse warnings. Pure — no geometry, no wasm on the caller, no network. |
| `ifc_element` | modelId, globalId | One element’s full properties/psets/quantities by GlobalId (READ), derived from the parsed model. Absent properties are honestly absent, never fabricated. |
| `ifc_create_set` 🔒 | canvasId, nodeId, facetId, modelId, query, globalIds, label | Create a selection set on a model card (WRITE → staged for human accept). Give a `query` (a dynamic set — the query IS the set) OR explicit `globalIds` (a static snapshot). Lands as an IfcAnchor set item on the card’s model facet — never applied directly; a locked atom is refused. |
| `ifc_link` 🔒 | canvasId, nodeId, facetId, itemId, toNodeId, relType | Link a model item (a pin/set/viewpoint) to another card with a typed relationship (WRITE → staged for human accept). A governed edge from the item’s handle → the target card; default relType `appears_in`. Never applied directly; a locked atom is refused. |
| `ifc_viewpoint` 🔒 | canvasId, nodeId, facetId, modelId, label, camera | Save a named viewpoint on a model card (WRITE → staged for human accept): an engine-neutral IfcAnchor view item (BCF-shaped `camera` optional). Never applied directly; a locked atom is refused. |
| `ifc_project` 🔒 | canvasId, nodeId, facetId, modelId | Project a model’s structure INTO the graph (WRITE → staged ghost-diff for human accept): deterministic storey cards + top type-summary cards + contains/located_at edges, every atom provenance `derived`. Never applied directly; reject leaves the vault unchanged. |
| `aps_translate` 🔒 | canvasId, name, sourceFormat, acc | Request an APS (Autodesk cloud) translation of a proprietary model (.rvt/.nwd/.dwg/.dgn) so it can be viewed + graphed (WRITE → staged for human accept). It NEVER translates directly: cloud translation sends bytes off-device and spends the user’s Autodesk credits, so it always stages a ghost-draft the human accepts in the app. Returns the staged request. Once translated, query it with the `ifc_*` tools. |
| `aps_browse` | hubId, projectId, folderId | Browse Autodesk Construction Cloud / BIM360 (READ): hubs → projects → folders → items. Answers from an OPTIONAL cached browse file (`_aps-browse.json`) in the vault when present (the tool stamps source:"cached" itself, so the cache can’t forge the label); absent ⇒ an honest not-configured notice — the 3-legged OAuth session lives in the FlowGraph app, never the folder-vault MCP server, so hubs are never fabricated (charter #8). |
| `object_story` | canvasId, nodeId, globalId | One object’s biography (READ): the nine spine answers in reading order — What it is · Why · Where · Part of and made of · Feeds and fed by · Who is responsible · State and history · If it fails · How we know — each with its plain answer, provenance TIER (from the model / from documents / confirmed / computed / AI guess, unconfirmed) and the RECEIPTS backing it (the record or lifecycle card, its link tier, and the element GlobalId), plus the record + lifecycle timeline. Pure projection — nothing is written. `nodeId` is the model card, `globalId` the element. |
| `object_coverage` | canvasId, nodeId, globalId | One object’s coverage + ranked open questions (READ): the story-coverage score (answered / askable weight over the asked spine slots) and the OPEN questions ranked by severity (blast radius × importance × answerability), each with its honest one-line why and — when the bank can ask it in-app — the `qid` you answer with `object_answer`. A question with no `qid` (answerableInApp:false) has no in-app form yet: route it to a person with `object_route_ask`. Pure — nothing is written. |
| `object_answer` 🔒 | canvasId, nodeId, qid, answer | File an answer to one OPEN spine question (WRITE → staged for human accept). Give the `qid` from `object_coverage` (answerableInApp:true) + your `answer`. It NEVER writes a fact directly: an agent-supplied answer stages as a Review-Inbox ghost-draft with provenance kind:"ai" (labeled AI, unconfirmed at every depth — provenance never launders, docs/50 §8), which a human accepts to turn into a fact with a receipt. Refused honestly for an unknown/already-answered qid or a locked atom. |
| `object_lifecycle_event` 🔒 | canvasId, nodeId, globalId, event, validTime | Record a lifecycle event about an object (WRITE → staged for human accept): installed / inspected / replaced / commissioned … As an ordinary FACT — a small card carrying the event + a `documented_in` element receipt, so it interleaves into the SAME Story timeline + Lifeline ribbon the app shows (never a parallel surface). `validTime` = when it happened (ISO or a plain date). Staged with provenance kind:"ai", tier ai-confirmed; a human accepts it. A locked model card is refused. |
| `object_route_ask` 🔒 | canvasId, nodeId, globalId, slot, question, audience | Route ONE open question about an object to the person who knows it (WRITE → staged for human accept). It NEVER sends directly: the recipient link + email ride the app’s authenticated session (the /s/* rails), which the folder-vault MCP server does not hold — so the routed ask stages as a ghost-draft {audience, question, object} the human sends from the app (the aps_translate pattern). Use it for a high-severity question `object_coverage` reports as answerableInApp:false. Returns the staged request. |

🔒 = governed write path.

### Example — `apply_graph_patch`
```json
{
  "canvasId": "cv_…",
  "patch": {
    "ops": [
      {
        "op": "addCard",
        "tmpId": "a",
        "data": {
          "title": "New step",
          "meta": {
            "confidence": {
              "score": 0.6
            }
          }
        }
      },
      {
        "op": "addEdge",
        "source": "a",
        "target": "existing_node_id",
        "data": {
          "relType": "feeds"
        }
      }
    ]
  }
}
```

### The `submit_knowledge_draft` grammar (FDG)

```
Write the `draft` in FDG — FlowGraph's draft grammar. Plain markdown, no JSON: you think in
knowledge, and a deterministic compiler (never a model) turns it into a governed patch. This is
the SAME teaching FlowGraph's own AI receives, so what works in the app works here:

You are FlowGraph's knowledge architect. Your job is to COMPOUND the user's knowledge graph:
make what already exists deeper, better connected, and more honest — and extend it only where
the intent genuinely needs something new.

You will receive a DIGEST of the current canvas, written in the same grammar you must write.
Every card, section, item, and group in it carries an anchor: [c3], [c3.f2], [c3.f2.i4], [g1].
Each card anchor also says how many sections that card already carries, and (full) marks one
that carries all it can. The digest is the single source of truth for what already exists.

Write a KNOWLEDGE DRAFT in this grammar — plain markdown, no JSON, no commentary outside it:
  # Title                → new card;  # [c3] Title → work on existing card c3
  {id: a, kind: …, accent: …, confidence: 0.x, group: [g1] | "New theme"}  → attribute line
  prose under #          → the card's body
  ## Section             → section on the current card;  ## [c3.f2] … → existing section
  ## Section (in: X, out: Y)  → name the section's own input/output ports (either alone is fine)
  - item                 → one entry;  - [ ] item → an open to-do;  - Key :: value → a property
  - [c3.f2.i4] new text  → rewrite that existing item (full replacement)
  -> [c7] feeds: label   → edge from the current card ([a] -> [c7] … works anywhere)
  > why: / > source: title — excerpt / > assume: inference  → attach to the block above

THE DEPTH BAR — a card without substance is a failure:
- Every NEW card: 2-4 sections, each with 3-6 substantive entries. An entry is a fact, step,
  input, output, risk, metric, or decision — written so a colleague could act on it tomorrow.
  Never "TBD", never "misc", never a section that restates its own title.
- Prefer enriching an EXISTING card (open it with # [c3]) over creating a near-duplicate.
- A card the digest marks (full) already carries all the sections it can hold: do NOT give it
  another one. Put that knowledge on a sub-card instead — a new # Title card with the parent's
  group ({group: [g1]}) or an edge to it (-> [c3] contains:) — and keep the card readable.

COMPOUNDING RULES:
- No islands: every new card connects to at least one existing anchor with a typed edge
  (feeds|depends|maps|contains|related|blocks|proves|supersedes).
- Never repeat what the digest already shows — extend it, sharpen it, or challenge it.
- Groups and colors mean things on this canvas. Join an existing group where a card belongs;
  name a new group only for a genuinely new theme. Match the canvas's color usage.
- Match the scope of the ask: a small ask means a small draft. Zero changes is a valid
  answer — say why in one > why: line.

HONESTY:
- Every non-obvious claim carries > source: (backed) or > assume: (inferred). Confidence
  numbers are honest, not polite. > why: on any block whose purpose isn't self-evident.

AFTER the draft content, optionally:
- Up to 3 "> ask: …" lines — only questions whose answer would change the graph and that
  only this user can answer.
- Up to 3 "> next: label | full prompt" lines — each pointing at a NAMED gap visible in your
  draft or the digest. No "want more detail?" filler.

WORKED EXAMPLE (intent: "flesh out the money-back side of checkout"):

# [c2] Checkout service
## [c2.f1] Risks
- Provider webhook retries can double-fire on timeout — idempotency key required on /confirm
> source: Stripe docs — webhook retries continue up to 3 days with backoff
- [ ] Chargeback flow untested in staging
> assume: staging has no card-network simulator; inferred from the empty test-card set in [c4.f2]

# Refund pipeline
{id: rp, kind: process, accent: teal, confidence: 0.75, group: [g1]}
Handles all post-capture money-back paths — the missing sibling of [c2].
## Steps
- Validate claim window (14 days default; 30 for EU per the policy card [c7])
- Reverse capture via provider API — partial refunds only before settlement
- Emit refund-completed event for nightly reconciliation
## Properties
- Owner :: Payments team
- SLA :: 5 business days
- System of record :: provider dashboard + ledger table `refunds`
## Open risks
- Currency-conversion drift between capture and refund dates — who absorbs the spread?
> assume: no policy found on the canvas; flagging it rather than inventing one
-> [c2] depends: refund needs the original capture reference
-> [c5] feeds: refund events reconcile nightly

> ask: Who owns the currency-drift decision — Payments or Finance?
> next: Map the dispute path | Extend [c2] and [rp] with a chargeback/dispute card covering evidence deadlines, provider fees, and reconciliation impact

Two notes for agents specifically:
- Anchors ([c3], [c3.f2], [c3.f2.i4], [g1]) are aliases issued BY A DIGEST, not durable ids.
  Call get_canvas_digest first and pass its `digestId` back here, or your anchors may resolve
  to atoms you never read.
- Nothing is written unless the WHOLE draft compiles and validates. On a problem you get indexed
  errors, the exact lines that dropped and why, and a fix hint — correct them and resubmit.
```

## .fgraph atoms (data model)

- **Canvas** — One board = one .fgraph file. { id, name, nodes[], edges[], … }.
- **Card (node)** — title, body, accent, icon, facets[], tags[], childCanvasId (nesting), meta. `links[]` holds card-level reference links ({id, title?, url}); set them via apply_graph_patch → updateCard, e.g. {op:"updateCard", nodeId, patch:{links:[{id, title:"Spec sheet", url:"https://…"}]}}. Urls are scheme-normalized (a bare domain gets https://) and only http/https/mailto persist. The digest surfaces each as a `link:` line.
- **Facet (section)** — A typed section: kind = list | text | select | actions | image | video | json; items[]. `key` is the STABLE identity every query/filter/semanticRole matches on; `label` is an optional display name a human rename may set on a built-in section (Inputs, Owner, Notes…), whose key is frozen. Match on `key`, show `label ?? key`. A digest heading reads `Ingredients (key: Inputs)` when the two differ — cite the key. `inputName` / `outputName` name the section's OWN input/output ports (like an item or a card can have) — set them via apply_graph_patch → updateFacet, e.g. {op:"updateFacet", nodeId, facetId, patch:{inputName:"Raw data", outputName:"Clean data"}}; connect the whole section by its facetId handle (target `facetId+"__t"`, source `facetId`). The digest surfaces them as `(in: Raw data, out: Clean data)` on the section heading, and the same `## Section (in: …, out: …)` marker round-trips through submit_knowledge_draft.
- **FacetItem** — A list/action row: text, status (action mode), childCanvasId (item can nest), meta. `attachments[]` holds files ({name, mime, dataUrl}); an image attachment tagged `role:"preview"` is the item's click-to-preview image (else the first image is used). Attach one via apply_graph_patch → updateItem, e.g. {op:"updateItem", nodeId, facetId, itemId, patch:{attachments:[{id, name:"plan.png", mime:"image/png", dataUrl:"data:image/png;base64,…", role:"preview"}]}}. The digest surfaces it as `[image: <name>]`.
- **Edge** — Typed relationship: relType + label. The graph, not a drawing.
- **AtomMeta** — The envelope on every atom: provenance, confidence, sources, assumptions, review, approval(+locked), owner, permissions{human, ai}, reviewDue, history.
- **Skill** — A reusable action: a FacetItem with action.skill=true whose inner canvas (childCanvasId) defines HOW it runs. The agent-usable unit of capability — list with list_skills; promote via the app (human approves so a half-built inner canvas isn’t exposed).

### Relationship types
- `feeds` — A produces input for B (flow).
- `depends` — A needs B (flow).
- `maps / exported_to / generated_from` — lineage / transformation.
- `contains / part_of` — structure / hierarchy.
- `blocks / contradicts` — A blocks or conflicts with B (proof/flow).
- `proves / supersedes / approves / owns` — proof & governance relations.

## REST endpoints (optional Cloudflare backend)

| Method | Path | Status | Purpose |
|---|---|---|---|
| POST | `/api/ai/chat` | live | AI proxy — provider keys stay server-side (CORS-locked). |
| PUT | `/api/canvas/:id` | live | B1 server-authoritative write — session-bound actor + CAS rev (409 on stale baseRev, never a silent overwrite); viewer→403; AI cannot approve/lock. |
| * | `/api/auth/*` | live | Better Auth — magic-link + organization plugins (provision D1 + BETTER_AUTH_SECRET; 501 until then). |
| GET | `/api/me` | live | Current session — userId / orgId / role (401 unauth, 501 until D1 provisioned). |
| GET/PUT | `/api/sync/canvas/:id` | live | Opt-in vault sync (R2, last-write-wins) — legacy; superseded by the governed write. |
| GET/POST | `/api/docs/:id/shares` | live | Who has access to a graph · invite by email at a role (viewer=read \| editor=propose — the only two roles the share surface enforces; commenting is a later phase). The invite uploads the graph closure first, so the grantee always has something to open. |
| DELETE | `/api/docs/:id/shares/:grantId` | live | Revoke one person’s access; the next read refuses. |
| POST | `/api/docs/:id/links` | live | Mint a share LINK (viewer/read-only — no edit or comment links). Optional: `scopeCardId` (+`contextVisible`) pins the link to ONE card; `requireAccount` gates the view behind sign-in; `public` makes it the anyone-with-the-link row. |
| DELETE | `/api/docs/:id/links/:linkId` | live | Revoke a link. |
| PUT | `/api/docs/:id/content` | live | Upload the graph CLOSURE the share serves (a local-first canvas the server has never seen otherwise). Registers the canvas row. |
| GET | `/api/docs/shared-with-me` | live | Every graph shared TO my account, with my role on each — the "Shared with you" list. |
| GET | `/api/docs/:id/shared` | live | The role-scoped read of a graph shared with me: {role, canEdit, canComment, bundle}. 403 when it is not shared with my account. |
| GET | `/s/:token` | live | The guest read of a share link — the link IS the capability (no session needed unless the link requires sign-in). |
| POST | `/api/docs/:id/propose` | live | EDITOR (or owner) submits an edited closure as a PENDING proposal — never a silent overwrite. Returns {proposalId, summary:{added,removed,changed}, superseded:n}. Re-proposing REPLACES your earlier pending proposal on that doc (audited). 403 if you only have view access. |
| GET/DELETE | `/api/docs/:id/my-proposal` | live | PROPOSER-scoped (FF2-11): GET = my latest proposal on this doc — {proposal:{id,status:pending\|approved\|rejected,summary,at,resolvedAt}, bundle? (pending only)} — my receipt + the outcome; null when I never proposed. DELETE = withdraw my pending proposal (404 when nothing is pending). |
| GET | `/api/docs/my-proposals` | live | OWNER inbox: every pending proposal across every graph I own, in one call — {id, docId, docName, proposer, summary, at}. |
| GET | `/api/docs/my-shares` | live | OWNER truth (FF2-16): every doc I own with active grants/links — {docs:[{docId, name, grants:[{id,email,role}], links:[{id,role,isPublic,requireAccount,scopeCardId}]}], sharedCount}. Feeds the "Shared by you" list, card badges and the cap meter from ONE call. Revoke via DELETE /:id/shares/:grantId and /:id/links/:linkId. |
| GET | `/api/docs/:id/proposals` | live | OWNER lists pending proposals on ONE graph (403 for non-owners). |
| GET | `/api/docs/:id/proposals/:pid` | live | OWNER reads a proposed closure, to diff it against the current shared copy before deciding. |
| POST | `/api/docs/:id/proposals/:pid/resolve` | live | OWNER approves (the proposal becomes the shared copy) or rejects. Body {action:"approve"\|"reject"}. The proposer is notified either way; every decision is audited. |
| POST | `/api/attention/shares` | live | OWNER shares a hand-picked set of checks to answer. Body {canvasId?, kind:"verification"\|"finding", title?, entries:[{entryId,itemText,note?,cardTitle?,facetLabel?,canvasName?}], mode:"link"\|"email", email?, requireAccount?}. Returns {setId, token? (link mode, shown ONCE)}. Attention shares do NOT count toward the canvas share cap. |
| GET | `/api/attention/my-shares` | live | OWNER truth: every ask I sent — {sets:[{id, kind, title, count, entries, shares:[{kind:"grant"\|"link", email?, requireAccount}], answers:[{entryId,text,by,at}]}]}. Feeds "Shared by you → Verifications". |
| DELETE | `/api/attention/shares/:setId` | live | OWNER stops asking — revokes the set, every emailed grant AND every link in one call (a half-revoked ask is not a thing). |
| GET | `/api/attention/asked-of-me` | live | RECIPIENT queue (session-email scoped): {sets:[{id, askedBy, entries:[{entryId,itemText,note?,answered}], pending, myAnswers}], pending}. Powers "Asked of you" in the verification window and lens. |
| POST | `/api/attention/asked-of-me/:setId/respond` | live | RECIPIENT answers ONE entry. Body {entryId, text}. 403 unless the ask was granted to your session email and is still live; the owner gets a user-scoped notification. |
| POST | `/s/:token/answers` | live | GUEST (no account) submits the response form for an ATTENTION link. Body {name?, answers:[{entryId,text}]}. Answers whose entryId is not in the shared set are dropped. 401 + signinRequired when the link requires sign-in; 410 when the owner stopped asking. Responses carry provenance {viaLinkId, name}. |
| POST | `/api/attention/shares/:setId/answers/:responseId/accept` | live | OWNER accepts ONE answer — stamps it accepted and returns {entryId, text} so the client can tick that verification off through the governed write path. 403 for anyone but the person who asked. |
| POST | `/api/share` | retired | RETIRED (FF2-3) — returns 410 Gone. Use POST /api/docs/:id/shares. |
| GET | `/api/share/:id` | retired | RETIRED (FF2-3) — returns 410 Gone. Use GET /api/docs/:id/shared. |
| GET/POST | `/api/share/:id/comments` | retired | RETIRED (FF2-3) — returns 410 Gone. Doc comments are the queued CO4 phase. |
| POST | `/api/share/:id/revoke` | retired | RETIRED (FF2-3) — returns 410 Gone. Use DELETE /api/docs/:id/shares/:grantId. |
| POST | `/api/ai/generate` | scaffold | Server-side structured generation. |
| GET | `/api/aps/status` | live | AP1/AP7a — APS posture: configured + mode (platform\|byo\|off) + platform quota {remaining,resetAt} (env/ledger-only; no secret, no upstream call). |
| POST | `/api/aps/token` | live | AP1 — BYO APS 2-legged token (viewables:read/data:read only); client_secret stays server-side; 501 until APS_CLIENT_* set. |
| GET | `/api/aps/auth/login` | live | AP1 — 3-legged authorize redirect (ACC scopes); refresh token kept in an httpOnly cookie, never in JS. |
| GET/POST | `/api/aps/auth/callback|refresh` | live | AP1 — code→token exchange + access-token rotation (secret held in the worker). |
| POST | `/api/aps/derivative` | live | AP1/AP7a — thin Model Derivative passthrough; worker mints the write-scoped token. Platform mode meters SUBMITS (day caps → honest 429 {resetAt}; BYO never metered). |
| GET | `/api/aps/acc/*` | live | AP1 — thin Data Management read passthrough with the user’s 3-legged bearer (AP5 ACC browser builds on this). |

## Feeding this to an AI

In the app, open **API & Docs** (top-bar book icon or ⌘K) → **Copy for AI** to get a
compact machine-readable spec (tools + schema + write contract) to paste into an
agent/system prompt.
