Diesel2 prototype (proto1). This page lists every API route; how to use the pages is in Help.
Checking…
GET|HEAD /ping opsthe ops probe (^ping-1): 200 ok as text/plain, no-store, on every host, before everything else (not logged, not counted, never limited); 503 until the server listens. The admin agent answers GET /admin/ping the same wayGET /api/v2/health core{ok, version, topics, project, cdn: {host, ok}, mongo: {configured, connected, store}, heap, copy: false | {of, primary}}; openGET /api/v2/logs?date=&level=&kind=&project=&text=&tests=1&limit=&cursor= ops E_FORBIDDENthe detailed log, one day, newest first, a page at a time: {date, days, total, data, limit, capped?, next?, partial?, keptDays (how many days the box keeps), levels}; 100 unless limit= says, at most 100 (api.list.max; more is 100, capped: true); next, sent back as cursor= with the same query, the next page; the day's file is streamed and the read stops after 2 s with what it has, partial: true; kind= request, flow, audit, console, client or outbound (one line per web write a rule sent: project, topic, who, method, host, path, status, ms; ^http-14); the Architect or the ops tokenGET /api/v2/styles styles E_PAGE_MISSINGstylesheets: {total, default, current, data}; HTML twin /stylesPUT /api/v2/me/style styles{name}: the stylesheet every page uses, as /styles' Use this stylesheet sets it: the d2style cookie (a year) for this browser and the account's (look.st, every device) → {ok, name}; a name GET /api/v2/styles lists without errors, else E_ARGPOST /api/v2/logs/client ops E_BAD_JSON E_TOO_BIGa client's own log into the detailed log (kind client): {source, version, device, lines}; any logged-in accountPOST /api/v2/logs/probe?kind=internal|uncaught|rejection ops E_FORBIDDENthe box's ops token only, on any project: raise a failure on purpose, to check it is logged → 202 {ok, kind}, or the failure itselfGET /api/v2/logs/client?scope=project|all&days=1..14&device=&level=error&limit=&cursor= opsyour own client log lines (the Obsidian plugin's), newest first, a page at a time: {data: [{at, clientAt, project, device, source, version, level, msg, serverRid}], devices, last: {at, device}, total, limit, capped?, next?, partial?}; 100 unless limit= says, at most 100 (api.list.max; more is 100, capped: true); next, sent back as cursor= with the same query, the next page; the read stops after 2 s with what it has, partial: true; yours only, from a session or any of your tokens; HTML twin /obsidianDELETE /api/v2/logs/client?scope=project|all&before= ops E_FORBIDDENclears your own client log lines (older than before=, an ISO time, if given), never anyone else's: {removed}GET /api/v2/system ops E_FORBIDDENthis project's own system view: {project, level, view: full | small, total, data: [{id, name, spec, design, impl, tests, missing}], counts, domain}; the page is /components (P-226)GET /api/v2/system/components ops E_FORBIDDENd2's own project: the component list of its spec topic (the Specification tab of /components) as {topic, section, total, data: [{name, summary, uses, usedBy, realizedBy}] | null, coverage: {missing, unknown}}; data null when the topic has no components section; its membersGET /api/v2/system/index?impl=&stamp=1&since= ops{…the index}: the system view's index for this project (the small view below pro); stamp=1: {stamp}; since=GET /api/v2/system/implementation ops{…}: d2's own implementation, its components (d2spec only)GET /api/v2/implementations ops E_FORBIDDEN{source: settings | tags, data: [{name, title, kind, docs, …the row's other columns}]}: the project's implementations, from Settings:Implementations, else its d2-impl topicsGET /api/v2/system/deployment ops{…}: where and how this box runs (d2spec only)GET /api/v2/codes?feature=&q=&limit=&cursor=&project= design E_NOT_FOUND E_PRIVATE E_TOKEN_SCOPEbehaviour codes: each spec line's code against design, implementation, tests and the sources, and the broken references (d2spec); ?project= the other of base d2 and d2spec (each cites the other's codes): the same answer from that project's index, plus project, as the caller reads it there (a token as its scope reaches it, else its refusal; below that project's view 403 E_PRIVATE, signed out 401), ?q= required (E_ARG; only q and limit count), any other project 400 E_ARG; ?q= a part of the code, its feature or its spec line (case ignored), applied before the page is cut, so total counts the matches; next carries the q (sent back with no q it pages the same matches, with another q it is E_ARG); codes (and untested) a page at a time: 100 unless limit= says, at most 100 (api.list.max; more is 100, capped: true), next sent back as cursor= with the same query the next page; total (count for untested) all of them, with limit, only when the codes were cut or limit= or cursor= was sent; HTML twin /features (the Feature list). ?untested=1: only the codes nothing tests — no test line, no covers: comment — newest first, each with its line and section (^code-5); ?code=<c> one code looked up (P-2524): {code, codes, total, features}, a live code's own row, an old code's through its CodeAlias the code(s) it became, each with aliasOf, a retired one codes [] and retired {code, why} (200, never 404), only codes the caller's own index holds; a code that isn't one E_ARG; the index's broken and sourceBroken leave out references that name an alias, a covers: naming one counts for its new codes, and aliases {old: {new, why}} comes with the whole index; ?covers=bad: the sources' covers: naming a code neither live nor an alias, or a retired one, {bad: [{file, code, why: unknown|retired, retired?}], total, shown} (d2spec; the release check fails on any); ?feature=<f|first>&counts=1 (none named: first): the first screen of /features, {feature, features, counts: {feature: n}, known: {CODE: feature}, codes (that feature's; first: the first feature's), broken, sourceBroken, adds, pipeline}, ~50 KB (P-1947); the index is kept until a topic the viewer sees changesGET /api/v2/routes?feature=&q=&code=&entity=&action=&format=json declared E_NOT_FOUNDthe declared routes, one line a route: METHOD path {body} → reply · its codes · who · its action · what (text/markdown); no filter: the features with their route counts; feature= one feature, q= every word, code= the routes that answer it, entity= (topic) the routes of that entity's actions, action= (topic.publish) the routes of that action; format=json: {ok, total, common, data: rows}, each row with its action (GET /api/v2/routes/openapi.json declaredthe same routes as OpenAPI 3, for tools and outside developers (never part of a skill or a start)GET /api/v2/routes/deprecated declared{ok, total, routes: [{route, use, goes}], data: [{topic, route, line, use}]}: the deprecated routes and the Skill, Help, Page and App topics (this project's, then base d2's) that still name them, one row a topic and route, so what's left to change is seenGET /api/v2/errors?feature=&code=&format=json declared E_NOT_FOUNDevery error code once: its HTTP status, the routes that return it and its line from the feature skill's Errors (meaning → fix); no filter: the codes; format=json: {ok, total, data: [{code, status, common?, routes, features, skill: [{feature, text}]}]}GET /api/v2/cmd/<entity> declared E_NOT_FOUNDan entity's actions (DesignDomain#dd-inventory): {ok, entity, actions: [{action, method, route, key?, routes}]}, route the one /cmd runs (key: its path parameter, when it has one), routes every route naming the action; an unknown entity is 404 E_NOT_FOUND listing the entitiesPOST /api/v2/cmd/<Class>/<key>/<verb> {args} declared E_NOT_FOUND E_METHOD E_TOO_BIG E_NO_VERB E_NOT_BUILT E_PERM E_SITE E_ROBOTS E_BUSY E_HTTPthe door (DesignDomain#dd-gateway): a verb of the dictionary, the class matched without case and the key %-decoded (POST /api/v2/cmd/Topic/Landing/publish; a verb with no target is /cmd/<Class>/<verb>), run through the dictionary and the gate: its module's do, else its @route as that route with your own session or token; Web.fetch and Web.post (POST /api/v2/cmd/Web/<host>/fetch {url, raw?, max?, timeout?} and /post {url, method?, headers?, body?}, ^proxy-1..10) are code, an agent token's only (else 403 E_SCOPE) with its role's web.fetch row on (else 403 E_PERM {row}): → 200 {status, host, url, text (reader-mode markdown, or the HTML with raw), truncated, untrusted}; a private, reserved or d2's own address, also after a redirect (at most 5), 403 E_SITE {host, reason: private}; a post to a host not in apps.sites 403 E_SITE {host, line}, nothing sent; a path robots.txt disallows 403 E_ROBOTS; a 4th call in flight on the token 429 E_BUSY, over 30 a minute per token or 120 per project 429 E_RATE (Retry-After); a body over 256 KB 413 E_TOO_BIG; the site unreachable or slow 502/504 E_HTTP; a class the dictionary has with no such verb is 404 E_NO_VERB {class, verb, verbs}, a verb with neither a body nor a route 501 E_NOT_BUILT; /api/v2/d2/<Class>/<key>/<verb> is the same call. The older form /api/v2/cmd/<entity>/<action> {key?, markdown?} runs the action's primary route as that route, with your own session or token (^dd-3): key fills its one path parameter (topic.publish {key: "Landing"} is POST /api/v2/drafts/Landing/publish), the other fields are its body (a GET's query), markdown a text/markdown body; the answer is the route's, status and body, and every check it makes applies; an unknown entity or action is 404 E_NOT_FOUND listing what exists; a route with two path parameters, or a key missing or extra, E_ARG naming the route to call insteadPOST /razadmin/copy/now ops E_BAD_JSON E_COPY E_FORBIDDEN E_GATE_UNKNOWN E_NOT_FOUND E_PRIMARY{confirm?, hammer?}: the Architect's session (any token E_SCOPE) on a box with D2_COPY_FROM (else E_NOT_FOUND): the admin agent's POST /admin/import pulls the source box's database and files over this one → its answer {ok, from, at, dbMB, cdnMB, took}, or 202 {started} when it outlasts 3 s (the app restarts). On a primary, confirm: primary, and the import still needs a restore order (409 E_PRIMARY). When this box's app is too old or down to answer the gate the admin agent answers 409 E_GATE_UNKNOWN, and hammer (the phrase, checked by the admin agent against D2_HAMMER_SHA256, never kept) lets it through: wrong E_FORBIDDEN, three wrong in an hour E_RATEGET /api/v2/views/Dashboard pages E_NOT_FOUNDa page's data in one request: the queries `App:GET /api/v2/admin/cache ops E_METHODthe server's caches: {ok, total, data}, each with what it holds, what it is keyed by and its size; the Architect, or an AI as ops.cache allowsPOST /razadmin/copy/primary ops E_BAD_JSON E_FORBIDDEN E_PRIMARY{confirm: primary}: the Architect's session (any token E_SCOPE): a copy becomes primary (d2Meta.copyOf.primary): mail, crons, processing and payment webhooks follow their settings again, the banner goes → {ok, copyOf}; a box that is no copy, or primary already, E_PRIMARYPOST /api/v2/views {view: {: " arg=value …" | {query, …args}}} pages E_PARSEthe same for a view built on the fly; registry: spec.codes, spec.features, design.sections, tests.cases, tests.coverage, pipeline, history, agents, approvals, topics (pagesFrom: 1 adds the Page:/App: names the project borrows through pages-from, each {name, from}; an arg a query doesn't take E_ARG), thoughtsPOST /api/v2/admin/cache/clear ops E_METHOD{scope?: all | permissions | topics | files | system | project:<name>} → {ok, scope, dropped, cleared, ms}; changes no data; an AI whose person isn't the Architect clears only project:<this one>POST /razadmin/copy/allow ops E_BAD_JSON E_FORBIDDEN{box, why, hammer?}: a restore order: one import onto this primary within 30 minutes → 201 {allow: {box, why, by, via, at, until}}. The Architect's monitor's own token, the Architect's session with the hammer phrase (checked against D2_HAMMER_SHA256, never kept), or the box's own admin token (ops carrying out the Architect's Allow one copy, from anywhere: via ops; box may be left out; a box with no D2_COPY_FROM E_FORBIDDEN, ^deploy-7); anyone else, a wrong phrase or a box with no hash E_FORBIDDEN; three wrong in an hour lock it for an hour (E_RATE); box must name this box (D2_BOX or its domain)GET /api/v2/revs pages E_FORBIDDENwhat moved, in one small read: each store's change counter → {pipeline, history, agents, board, chats, topics: {POST /razadmin/copy/gate ops E_BAD_JSON E_PRIMARY{daily?, by?}: the box's admin agent only, with its admin token (else E_AUTH), before an import: {ok, copy: true} on a copy, {ok, allowed} using up a restore order on a primary, else 409 E_PRIMARY (always for the daily pull)GET /api/v2/revs/stream pages E_FORBIDDENthe same, pushed: text/event-stream, `event: revs` with the revs on connect and whenever one moves (at most every 250 ms), a `:` keep-alive every 25 s; up to 8 open streams per viewer and project, the oldest closed past that. ?scope=system (the Architect and admins, else 403 E_SCOPE): one combined {GET /api/v2/razadmin/diag/<report> ops E_NOT_FOUND E_STATEthe box's diagnostics, one report a call: {at, box, version, report, data, partial?}; report is memory (what is in d2's memory: {process, total, code, realms, server, caches}, P-2484), heap (the heap summary: a snapshot written to the box's tmp, summed up and deleted, never served: {summary: {nodes, edges, selfBytes, byType, byConstructor, retainers}, ms, snapshotMB}; at most once an hour and only with twice the heap free, else E_RATE with Retry-After; one that fails E_STATE), load ({loop: {m1, m15: {p50, p99, max, samples}}, cpu: {m1, m15, cores, loadavg}, requests: {hour, perRoute}, slowest: 20 routes of the hour, {route, count, p50, p99}}), handles ({sockets: {in, out, byHost}, servers, pipes, timers, intervals, immediates, children: [{cmd (its first word), pid, ageS}]}), store (mongo: {collections: [{name, count, size, storageSize, indexes, indexSize}], pool, calls, slowest}; file: {files, bytes, biggest}), errors (the last 200 answers of 5xx and refusals: {groups: [{code, route, status, count, last, paths?, hosts?}], kept, since, fivexx5m}; a 404 or 405 group, a code-less one too, names this hour's and the last hour's misses (P-2663): paths: the top 20 [{path, count, last, callers: {session, agent, token, anon}, roles?}], the query cut and ids folded to POST /api/v2/razadmin/inspect ops E_BUSY E_NOT_FOUND E_NOT_YOURS E_PERM E_STATEops.debug (P-2487): {item, box, minutes?, why} → 200 {grant, box, item, minutes, until, host, port: "127.0.0.1:9229"}: an ops agent's grant to open the Node inspector on a box of the deployment definition, for its own open item; pipe the grant to sudo d2-admin inspect on on the box, which opens it on 127.0.0.1 only and closes it after minutes (reach it through your ssh session, never through d2). Only an ops agent token of the Architect's (anyone else E_SCOPE); its role's ops.debug row off (person, every preset's default) 403 E_PERM {row}; the item not the token's E_NOT_YOURS, closed E_STATE; no inspect: <box> <minutes> line for this box E_SCOPE {need: "line"}, minutes over 60 E_ARG, the item neither askedBy the Architect, filed by him nor by the monitor for him E_SCOPE {need: "askedBy"}; minutes (default the line's) above the line's E_ARG; one already open on the box E_BUSY {until}; a box with no admin key or address E_STATE. {item, box, off: true} after d2-admin inspect off → {closed: true}. Each open and close is N_INSPECT to the Architect and A_INSPECT on record (box, item, agent, why, minutes); the grant is used within 5 minutes, onceGET /razadmin/page/deploy ops E_FORBIDDEN E_PAGE_MISSINGat /razadmin/page/deploy (^blocks-11; /razadmin/deploy, its old address, is 404): Raz Admin's 🚀 Deployment: base d2's signed Page:Deployment, the Architect's session only (a visitor logs in first); 503 E_PAGE_MISSING while it isn't publishedGET /api/v2/deploy ops E_FORBIDDENthe Architect's session only (any token E_FORBIDDEN): this project's deployment definition (CloudDeployment's newdeployment fence, P-1834; a project without one, its deployment fence) → {release: {version, commit, at}, topic, kind: newdeployment|deployment, fence, errors: [{line, message}], boxes: [{…its definition, live: {reachable, ok, version, deployedAt, disk, memory, heap, alerts, copy, daily}, drifts: [{what, fix?, kind}], envState: [{key, kind: set|secret, state: ok|differs|missing|unknown, settable?}]}]} (live.envPending {count, keys}: env changes on the box waiting for a restart, P-1833) (a secret drift, kind secret: the box's envHashes against your vault's; a setting drift, kind setting: a set line's value against the box's hash; kind heap: no D2_HEAP_MB on the box; names only, never a value); live from each box's admin agent (its token opened from your vault by name) and its health, 5 s each; no token or secret valuePUT /api/v2/mail/transport ops E_BAD_TRANSPORT E_METHOD E_ORIGIN{name: gmail | postmark | resend}: the Architect's session, or his agent on his intent or Allow once: writes mail.transport in base Settings:System and a history entry → {transport, from}; the box env D2_MAIL_TRANSPORT still wins (from: env); another name E_BAD_TRANSPORT 400; anyone else E_SCOPEGET /api/v2/deploy/<box>/actions ops E_FORBIDDEN E_NOT_FOUNDthe actions on one box, described by the server: {actions: [{id, icon, label, detail?, enabled, why?, lead?, needs: [{ok: true|false|"hammer", text}], force?, fields: [{name, label?, kind, options?, hint?, placeholder?}], steps, go}]}: deploy, copy, daily, restart, rollback, primary, secrets (🔐 Sync secrets: each env KEY vault:GET /api/v2/mail/unsubscribe ops E_METHOD E_NOT_FOUNDt: the signed token of a broadcast's unsubscribe link (one account's, never an email): turns that account's product news by email off and shows Unsubscribed; a token d2 didn't sign E_NOT_FOUNDPOST /api/v2/deploy/<box>/<action> ops E_FORBIDDEN E_HAMMER E_NOT_FOUND E_STATE{fields…} (apply, P-1833: {confirm?, files?: [env] | [caddy] | both, P-2140: none named is the env only, any other E_ARG, a Caddyfile-only apply with no change E_STATE}; its env run names the keys the agent refuses as for ops: <KEYS>, after giving the agent the definition's keys (POST /admin/definition); with caddy it writes the box's Caddyfile-<box> fence when the box's differs, through POST /admin/caddy, else for ops, P-1849/P-1851; each file left for ops is on the run's scripts: [{file: env | Caddyfile, path, script}], and the run's last line is ⏳ <box>: n scripts to run as root (…), never done, P-2139; setenv/unsetenv write the box's env without restarting it): every need re-checked (E_STATE with why), a confirm that isn't typed E_ARG, the hammer phrase checked here for a copy onto a box that can't show it's a copy (none E_HAMMER, wrong E_FORBIDDEN, three wrong E_RATE for an hour), a setenv/unsetenv field that won't do E_ARG naming the field (a key the box doesn't take, a secret-looking key as a setting, D2_ADMIN_TOKEN/D2_TOKEN/D2_MONGO_URL removed), then the box's admin agent is called → 202 {run}; audited A_DEPLOY without field valuesPOST /api/v2/mail/unsubscribe ops E_METHOD E_NOT_FOUNDthe one-click unsubscribe (RFC 8058, List-Unsubscribe-Post) of a broadcast's link: as the GET → {productNews: false}GET /api/v2/deploy/runs/<id> ops E_FORBIDDEN E_NOT_FOUNDa run's progress, readable while it runs: {lines, done, ok, error?, box, action, by, at}POST /api/v2/postmark/inbound ops E_COPY E_METHODPostmark's inbound JSON for d2mailer.aiputty.com, HTTP basic auth with the vault's postmark-inbound (else 401, audited): P-n@ → a note on P-n from a member, s-GET /api/v2/deploy/checks ops E_FORBIDDEN E_NOT_FOUND?runbook=&chore=<title>&box=: a runbook chore's checks, run by the server, each within 5 s → {checks: [{line, ok, detail}]}: dns resolves the name's A records (the ip among them), vault: your vault holds the names (names only, never a value), health: the box's admin status (an unreachable box ok:false), heap: the box's env carries D2_HEAP_MB; box only when two chores share the title (else E_ARG)POST /api/v2/postmark/events ops E_COPY E_METHODPostmark's webhooks, basic auth as inbound: a hard bounce or a spam complaint stops mail to the address (a notice to the Architect), an unsubscribe turns product news off → {kind, stopped?}POST /api/v2/deploy/chores ops E_FORBIDDEN E_NOT_FOUND{runbook, box, title, done}: a chore done (its thought ticked ✅ and archived) or open again → {thought: {name, project, done}}; GET /api/v2/deploy lists the chores ({chores: [{runbook, line, box, title, codes, steps, thought}], choreErrors: {<runbook>: [{line, message}]}}; a copy step naming {vault:<name>} carries refs and vault: [{name, kind: secret|key|waiting|none}], never a value: POST /api/v2/vault/resolve fills it in), each the Architect's thought tagged chore and #<box>, made once; an action a chore names (^<action>) is enabled:false while it is openPOST /api/v2/resend/inbound ops E_COPY E_METHOD E_TOO_BIG E_UPSTREAMResend's webhooks, Svix-signed with the vault's resend-webhook-secret (svix-id, svix-timestamp within 5 min, svix-signature; else 401, audited): email.received → the mail read through Resend's API and routed as Postmark's inbound → {routed, item?, project?, thread?}; any other event as /api/v2/resend/events; a replayed svix-id → {duplicate: true}; Resend unreachable E_UPSTREAM 502 (it retries)GET /api/v2/deploy/<box>/preview ops E_FORBIDDEN E_NOT_FOUNDread only (P-1834): what CloudDeployment's newdeployment fence would write on the box against its files read now from its admin agent's GET /admin/files (never kept) → {box, definition: {ok, errors}, readAt | readError, files: [{path: /etc/d2/env | /etc/caddy/Caddyfile, new, current | null, diff: [[" "|+|-, line, note?]] | null, script: text | null, tick, why?, error?}]}, definition.warnings: where the box's host, alias and redirect lines disagree with its Caddyfile (P-2140); tick: its diff has a change (P-2140); script (P-1833): the script ops runs as root on the box, {vault:<name>} for each secret (POST /api/v2/vault/resolve fills it on 📋 Copy), the env's changed lines through d2-setenv on stdin and each drop line's key deleted, the Caddyfile through tee and caddy validate, each after a dated backup and put back on failure; new is the box's ```caddyfile <box> fence as it is, its Caddyfile-<box> topic's, else CloudDeployment's (P-1851, P-2140), else null: never built from its lines; no fence, or the box's current one unreadable: script null, tick false, why; a secret is {vault:<name>} in new and {hash, vault?} in current, the same when the box's hash is the vault's; a missing vault entry is a note on its line, never a change; a key the definition doesn't name is kept unless a drop line names it; an unreachable box or an agent without /admin/files: readError, current null, no diff; nothing writtenPOST /api/v2/resend/events ops E_COPY E_METHOD E_TOO_BIG E_UPSTREAMResend's webhooks, signed as inbound: email.bounced (permanent) or email.complained stops mail to each address (a notice to the Architect), the rest only counted → {kind, stopped?}; email.received here is taken as inboundPOST /api/v2/deploy/definition ops E_FORBIDDEN E_NOT_FOUND E_STATEP-1833: {box?, key, value: text | null} → {was: text | null, changed, ver}: one set line of CloudDeployment's newdeployment fence (the box's when box is given, else the deployment's; value null removes it), written as the Architect; restarts nothing, writes no box; was is the old value, which the page's Discard writes back; a secret-looking key or value E_ARG (a secret line instead); audited A_DEPLOY with the key, never the value. P-1851: {box, caddyfile: text | null} → {was, changed, ver}: the box's ```caddyfile <box> fence, in its Caddyfile-<box> topic when it has one (P-2140), else CloudDeployment's (made in ## Caddyfiles {#caddyfiles} when it has none; null removes it), the text as it is; a host or alias line of the box with no site block in it E_ARG naming it; a line starting ``` E_ARGPOST /api/v2/deploy/secrets ops E_EXISTS E_FORBIDDEN E_NOT_FOUND E_QUOTA E_STATEP-1833 ➕ Create: {key, vault, box?, phrase?, value?} → {vault, line, allowed: [role], skipped: [{role, why}], publicKey?}: a vault entry the definition names that your vault lacks, made in your vault (D2_HAMMER_SHA256: sha256 of phrase, never the phrase; AWS_* and D2_TURNSTILE_SECRET: value; ssh: an ed25519 key pair, the private half a vault key, publicKey answered once; D2_ENC_SECRET: only a pasted value, a fresh one E_ARG; anything else 32 random bytes, never answered); its secret (or ssh key) line written when the definition lacks one (for box), and the entry allowed to the line's roles' live role tokens; already in your vault E_EXISTS; audited A_DEPLOY, names onlyGET /api/v2/deploy/fleet ops E_FORBIDDENP-2519: every box in use, one list → {recipes: [{type, copyOf?, size, region, image, tags, firewall, runbook, max, life: fixed|on-demand, role, has, note, cost: {monthly, hourly, memory, vcpus, disk} | null (DigitalOcean's sizes, kept an hour), count, canCreate}], boxes: [{name, source: definition|create|pool|unknown, recipe, role?, ip, droplet?, created?, size?, region?, status?, monthly?, costSoFar?, state, create?, holds: [mongo|files], drifts: [{what, kind: recipe|ip}], actions: [destroy]}], creates: [{id, recipe, name, region, droplet, state: creating|joining|setting up|ready|failed, lines, ip?, monthly?, defined, ipDefined, todo}], suggest, region, do: {ok, why?}}; DigitalOcean read with your vault's do-api, opened on the server, never shown; a droplet under a d2-* tag d2 doesn't know is unknown (never touched), d2-build-pool ones under the on-demand recipe; without do-api the definition's boxes and recipes, cost null and do.whyPOST /api/v2/deploy/boxes ops E_EXISTS E_FORBIDDEN E_NOT_FOUND E_QUOTA E_STATE E_UPSTREAMP-2519 + Box → Create on DigitalOcean: {recipe, name, region?, cost} → 202 {create, box, droplet, block, ver, monthly}: a fixed recipe's box (an on-demand one is the pool's: E_STATE), a free name (E_EXISTS), under its max (E_QUOTA), cost the recipe's monthly price now (else E_STATE with details.cost); POST /v2/droplets with the recipe's size, region, image or newest snapshot and tags plus d2-box and d2-recipe-<type>, its cloud-init only a one-time join code; its box … from <recipe> block written into CloudDeployment (droplet <id>); DigitalOcean's refusal E_UPSTREAM (its status and message, never the token); audited A_DEPLOY with its cost. POST /api/v2/deploy/boxes/<create>/define → {changed, ver}: its ip into its block once it joined (E_STATE before)POST /api/v2/deploy/<box>/destroy ops E_FORBIDDEN E_NOT_FOUND E_STATE E_UPSTREAMP-2519: {confirm: <its name>, copied?: true} → {destroyed, droplet, ver}: a box of the definition with its droplet id, never the primary (E_STATE), its name typed (E_ARG field confirm), copied: true when it holds mongo or files (E_ARG field copied, details.holds), named in no order, database or copy of line (E_STATE); DELETE /v2/droplets/<id> (already gone is fine), its block out of CloudDeployment; audited A_DEPLOY with its cost this monthPOST /api/v2/deploy/join ops E_FORBIDDEN E_STATEP-2519, a new box's own call (no session, no token): {code} → {box, recipe, key, steps: [{n, text, run?}]}: its one-time join code (from its cloud-init: 10 minutes, one use), from the droplet's own address as DigitalOcean lists it (else E_FORBIDDEN), not before DigitalOcean lists it up (E_STATE: try again); steps: its recipe's runbook's chore <recipe> "first boot" (a step with a copy line and no vault name runs on the box). POST /api/v2/deploy/join/step {key, n, ok, out?} | {key, done: true} → {state}: each step's result (its last 300 characters a line of the create), the setup key until ready (an hour at most), from the same address; a failed step fails it; done once every step said okGET /system design E_PAGE_MISSINGbase d2's Page:System for a member's session (P-1941); on hero it opens the Specification; a token is 303 to /api/v2/system/index; no built-in page (P-2029): 503 E_PAGE_MISSING where base d2 has none. /components likewise (Page:Components, /api/v2/system), /features (Page:Features, /api/v2/codes)GET /api/v2/topics topicslist: {total, category, data: [{name, category, tags, ver, updated, title?, lead?, mayEdit, listed?}]}: title is its first heading, lead its first line (as a Docs tile says them), mayEdit whether the viewer may change it from the Docs list, listed: false on a row the Docs list leaves out here (notlisted outside base d2, Skill:Project on hero and creator); ?inherited=1: also what the project inherits from base d2 and doesn't hold, each inherited: true, fromGET /api/v2/help help?q=: the Help sections that match, as /help?format=json answers them (the search box's drop-down): {ok, q, data}GET /api/v2/domain/counts domaineach topic the viewer sees that declares classes or objects: {total, classes, objects, data: [{topic, classes, objects, errors}]}, as a Docs tile counts themGET /api/v2/editor/frontmatter topicswhat the editor's content assist suggests inside the frontmatter and after show= (^edit-assist-1): {total, from, data: [{key, where, values, source, help}]}. base d2's Settings:Frontmatter with this project's own merged over it (the same key replaces), and the value sources ($tags, $topics, $classes, $members) filled from the project. It only suggests: nothing here validates a key or a valueGET /api/v2/nav pagesthe top bar this viewer gets here, from the project's Nav topic, else base d2's: {from: project|d2|built-in, logo, parts, warnings, bad?}, links already filtered by show= and level=; ?path= for another page's (an admin page always gets base d2's)GET /api/v2/topics/<name> topics E_ENC_KEY E_NOT_FOUND E_NO_SECTION E_PRIVATEmarkdown body; ?format=json for the envelope; ?section=<id> for one section (&format=json: {name, section, text, from?}); a pointer section (its body only `→ [[T#a]]`) answers the section it points to, once, as you would read T, with X-Section-From: <name>#<id> (a pointer to a pointer or to nothing answers the pointer); ?stamp=1 for {stamp, ver, draft} (a page following changes, ^sync-2); a base d2 Page: (its own, or inherited) is for a signed-in account: a visitor gets 401 E_PRIVATE, as on its history and drafts (^d2land-7)GET /api/v2/topics/stamps?names=a,b topics{stamps: {<name>: stamp}}: each topic's stamp in one request (at most 200; the ones you can't see are left out), for dev-sync over several topicsPUT /api/v2/topics/<name> topics E_DRAFT_OPEN E_SKILL_TRACE E_AI_ACCESS E_APPEND_ONLY E_BAD E_BAD_JSON E_CODE_REUSED E_COMPILE E_CONTENT E_DEMIGOD E_DRAFT_CHANGED E_ENC E_ENC_CHANGED E_ENC_KEY E_EXISTS E_GENERATED E_LINK E_LOCKED E_METHOD E_NATIVE E_NOT_FOUND E_NO_SECTION E_ON_BEHALF E_PARSE E_QUOTA E_SECRET E_STATES_UNSAFE E_SETTING E_STEP_JUMP E_STEP_VAR E_STYLE E_THOUGHT E_THOUGHT_STATE E_UNCONFIRMED E_VERSION_REQUIRED E_UNPRIVATEbody = markdown, or the drafts route's JSON envelope {text, ver?, section?} sent as application/json (a JSON object with no string text: 400 E_ARG {field: text}; a body that isn't a JSON object is the markdown, whatever its Content-Type); stored even if broken, parse errors returned; an AI token whose publish of the topic asks for its role (topic.publish, or app.publish for an App, ^drafts-9) saves its draft instead, as a drafts token does → {draft: true, dver, held: why}: without If-Match onto its draft, with one checked (E_DRAFT_CHANGED); ?section=<id> writes one section, to the person's draft for a token that writes drafts, and ?section=<new>&after=<id> or &at=end adds one (E_NO_SECTION without either); a stored Thought: keeps its frontmatter (none: 400 E_THOUGHT), and an AI token's save of one is PUT /api/v2/thoughts/<n>'s (?state=, ?waitingFor=, ?ver=; E_APPEND_ONLY, E_THOUGHT_STATE); a body in another language than the topic's (?language=markdown|json|html, frontmatter language:, an App) is 400 E_CONTENT; an agent's write to another role's topic (the design layer, the design tests) or a behaviour tag is 403 E_SCOPE per the project's topic.publishDesign, topic.publishTests, topic.tag (details {topic, category, role, allowed}); a changed ai-access line from an AI 403 E_AI_ACCESS {from, to}; an added tag that isn't a flat lower-case word, or a rule sending a ….on.… event, 400 E_PARSE {line, got}; a signed CDN link 400 E_LINK {line, link}; a story's {x} that no header declares and no step sets is in its errors as E_STEP_VAR {line}, a jump to a story or step that isn't there E_STEP_JUMP {line} (stored all the same); a save that takes a class's private mark (@viewBy=owner) off while it has objects with a creator 409 E_UNPRIVATE {class, count, classes} until sent again with confirm: unprivate (a header or ?confirm=; P-1645), as a draft's publish, a version or trash restore and a delete are; a retired or doubled behaviour code 409 E_CODE_REUSED {code, line, was, lines: every such line, each with its next}; a guardian's second open report 409 E_EXISTS {topic, state}; hints (N_CHANGELOG, N_TEMP_LINK, N_NO_SPECUI, N_NO_HELP, N_BIG_TOPIC, N_DEFAULT_ACCESS, N_SPEC_WHO, N_PAGE_MODAL: a Page or App calling the browser's alert, confirm or prompt outside a comment, N_SHOW_OLD: a Page or App whose plain data-show holds a d2 condition: did you mean data-d2-show?, N_HTML_FENCE {line, message}: a Page or App whose body is one html code fence, served as its HTML) as warnings [{code, line}], never a refusal; on anyone's save of an App, W_APP_SHAPE {line, message}: a bare boot: or script: line or a or nothing opened, outside its blocks, or a dropped layout the published app has, W_APP_FORMAT {line, message}: a body whose format (HTML, or markdown or a fenced block) differs from the published app's, and W_APP_MIXED {line, message}: an App served as HTML (it starts with <) with a markdown line outside its tags (a heading, a list, a fence, **bold**), which shows as text, and W_APP_HTML_SPLIT {line, message}: a layout: full App with a markdown body (whose HTML blocks pass through) where a blank line ends an HTML block and indented lines after it would show as code; on anyone's save of an App, Page or Lib, W_CSS_VAR {line, message}: a var(--x) with no fallback that neither the topic nor the theme (the default tokens, Style:Base, the project's Style topics) declares, which computes to nothing (on a layout: full App or Page with its own HTML, which carries no theme, any token not its own); on an agent's save of one, N_CSS_COLOR {line, message}: a theme token without a fallback in a colour property, named with its value and hue in the default theme (#9a4b12: a brown); on anyone's save, N_SECRET_INLINE {line, message}: a secret written into a rule (a literal after client_secret, password, api_key or token, or a Bearer literal): name it with secret(…) (^http-11)POST /api/v2/topics/<name>/append {text, date?} topics E_DRAFT_OPEN E_STATE E_AI_ACCESS E_BAD E_CONFLICT E_ENC E_ENC_CHANGED E_ENC_KEY E_FORBIDDEN E_GENERATED E_LINK E_NOT_FOUND E_SECRET{text, date?} (a JSON object; one with no string text is 400 E_ARG {field: text}) or the text itself, whatever its Content-Type: added under the day's ## YYYY-MM-DD heading (made if missing), at the end of that day's entries → {name, ver, date}; the one write an append-only topic takes from everyone but its admins (Design 22); an AI whose publish of it asks (topic.publish or app.publish, ^drafts-9): added to its draft instead → {draft: true, dver, held}POST /api/v2/topics/<name>/unseal {id} topics E_ENC_KEY E_FORBIDDEN E_NOT_FOUND{id}: one encrypted block's text, for the page's box → {name, id, text}; its writer only, signed in as themselves (an AI token, or anyone else, E_SCOPE); every opening audited A_ENC_OPEN, never the text (Design 64)DELETE /api/v2/topics/<name> topics E_AI_ACCESS E_DRAFTS E_GENERATED E_NOT_FOUND E_PROTECTED E_UNPRIVATEto the trash, with its history, for 30 days; Skill:Project (or its old name Skill:process) never (403 E_PROTECTED, for everyone: undo a bad edit from its history, ^addon-12); an agent whose role is person on topic.delete: 403 E_SCOPE (tag it todelete instead)PUT /api/v2/topics/<name>/rename {to, ver?} topics E_AI_ACCESS E_BAD_JSON E_DRAFTS E_EXISTS E_GENERATED E_NOT_FOUND E_PROTECTED E_STATE E_VERSIONTopic.rename (P-2247; also POST /api/v2/cmd/Topic/<name>/rename {to}): one write, the topic's record, versions, everyone's drafts and its comments under the new name; every [[name]], [[name#a|label]] and /topics/name link in the project's topics rewritten as a version of each (by d2 (renamed link: Old → New)), a draft linking it rewritten in place (no new draft); [[<project>.name]] in the other projects the person owns too (their own session); a Spec: or Story: domain rebuilt once, after the move; never refused on content → {ok, renamed: {from, to}, ver, links: {topics, drafts}, projects?}; the old name answers 301 to the new one (GET → {moved}) until a topic of that name is made; to existing (or drafted) 409 E_EXISTS, not a name or another category (a Spec: stays a Spec:) 400 E_ARG {field: to}, a stale ver 409 E_VERSION {ver}; its row is topic.write, a publish's for a Spec:, Story:, Skill: or Page: topicPATCH /api/v2/topics/<name>/stories/<id> {tags, ver} topics E_AI_ACCESS E_BAD_JSON E_DRAFTS E_GENERATED E_NOT_FOUND E_VERSION+tag on a journey (P-2676): rewrites only journey <id>'s $story head to hold tags: "a, b" (an empty list takes the parameter out: the journey has its page's tags again), never the page's front matter; a new version by the writer, through a save's checks and gate (who may edit the page) → {ok, name, story, tags, ver}; tags: at most 20 words (letters, digits, - _ ., at most 40 each; else 400 E_ARG {field: tags}); ver required (400 E_ARG {field: ver}), a stale one 409 E_VERSION {ver}; no such journey 404 E_NOT_FOUND {story}; a token whose writes are drafts 403 E_DRAFTS (PUT the page instead: it lands as a draft)POST /api/v2/topics/batch {names, op, tag?} topics E_BAD_JSONthe Docs List's picks (^docs-7): op tag or untag (with tag, one word) or delete (to the trash), on at most 200 names → {done, skipped: [{name, code}], unchanged}. Each name gets its own route's checks (PUT or DELETE) and is skipped with that code (E_FORBIDDEN, E_NOT_FOUND, E_SCOPE, E_DRAFTS…; a delete of Skill:Project E_PROTECTED, ^addon-12); one new version per topic changed; one that already has the tag, or lacks it, is left aloneGET /api/v2/topics?category=<Cat>&tag=<tag>&inherited=1&q=&limit=&cursor= topics?names=a,b (at most 200): only those topics, unknown ones left out; filtered list, a page at a time: {total, category, data, limit, capped?, next?}; ?q= a part of the name, title or category (case ignored), applied before the page is cut, so total counts the matches; next carries the q (sent back with no q it pages the same matches, with another q it is E_ARG); 100 unless ?limit= says, at most 100 (more is 100, capped: true); next, sent back as ?cursor= with the same query, the next page; total counts the project's own; ?category=Story&tag= (P-2676): a page any of whose journeys has the tag (a journey's own tags: on its $story head, else its page's), each row with journeys: [the matching ids]; a page with no journey by its own tagsGET /api/v2/topics/<name>/history topics E_NOT_FOUND E_PRIVATE{name, ver, data: versions}GET /api/v2/topics/<name>/history/<ver> topics E_NOT_FOUND E_PRIVATEthat version's text; ?format=json for {name, ver, text}POST /api/v2/topics/<name>/history/purge topics E_BAD_JSON E_NOT_FOUNDthe one exception to nothing is lost (^his-9): removes versions ≤ upTo for good (and from a trashed copy of that name), the current text stays, the oldest kept version rebuilds on its own → {topic, purged: [vers], from: <oldest kept>}; the project's owner in their own session only (any token, a demigod's included, an admin, the Architect on another's project: 403 E_SCOPE); audited A_HISTORY_PURGE {topic, versions, by}, never any text; again over the same range: 200, purged: []POST /api/v2/topics/<name>/restore?ver=<ver> topics E_DRAFT_OPEN E_METHOD E_NATIVE E_NOT_FOUND E_QUOTA E_UNPRIVATEa new version with the old text (a draft, with a drafts token, or an AI whose publish of it asks, ^drafts-9)GET /api/v2/search?q=&in=&body=&limit=&cursor= srchtopics by name, title and description (body=1 adds the text; under 3 letters names and titles only); in=Spec,Help,File,Thought,Item,Comment… one list of topic categories and classes to search (unknown: E_ARG {field: in, known}; none: all); a question that runs past 2 s answers partial: true; topics by text; #tag words filter by tag, and bring tagged files; the project's own rows first, then what it inherits, each with from: <base>; the rows a page at a time: 100 unless limit= says, at most 100 (api.list.max; more is 100, capped: true), total all of them, next sent back as cursor= the next page (it carries the q: sent with none it pages the same search, with another it is E_ARG)GET /lib/<name>.js?v=<hash> pages E_NOT_FOUNDbase d2's published Lib:<name> as text/javascript, for d2's signed pages only (the page asking, its same-origin Referer, is a base Page served signed), and the Libs the shell draws with (steps, P-2107) also for the shell's own pages (a same-origin page that is no App, Page, app or widget of the project), else 403 E_SCOPE; v= its current hash, as a signed page names it: public, max-age=604800; else no-cache with an ETag (304 unchanged); no published Lib:<name>: 404 E_NOT_FOUND (^blocks-6)GET /api/v2/search?q=&scope=mine&in=&body= srchevery project you are a member of, one flat list by match (^srch-5): {total, projects, hits: [{project, chip, kind, name, url, snippet, count, score}], pending}. `all:` before the query says the same; &project= narrows it to one of them (and is how the page picks up a pending project); each project under its own view rules; a token for one project is E_SCOPEGET /lib/<name>.css?v=<hash> pages E_NOT_FOUNDthe look a Lib carries (its ```css fence, ^blocks-9) as text/css, to the same signed pages as its script (else 403 E_SCOPE), kept the same way by its own hash; a Lib with no css fence, or none: 404 E_NOT_FOUND. A Page's frontmatter requires: <lib>[, <lib>] links each Lib's CSS and script once, by their hashes, before its own scripts; d2's own Libs (seed-d2/Lib, Lib:dtable first) are served from the shipped copy while base d2 has none publishedGET /topics topics E_PAGE_MISSINGbase d2's Page:Topics for a member's session (base d2's for anyone signed in; P-1941); a token is 303 to /api/v2/topics; no built-in page (P-2029): 503 E_PAGE_MISSING where base d2 has noneGET /search srch E_PAGE_MISSINGbase d2's Page:Search for a member's session (P-1941); a token is 303 to /api/v2/search; no built-in page (P-2029): 503 E_PAGE_MISSING where base d2 has noneGET /backlog topics E_PAGE_MISSINGbase d2's Page:Backlog for a member's session (P-1941); a token is 303 to /api/v2/backlog; no built-in page (P-2029): 503 E_PAGE_MISSING where base d2 has noneGET /help help E_PAGE_MISSINGbase d2's Page:HelpIndex for anyone signed in (P-1941); a token is 303 to /api/v2/help; ?format=json: the box's drop-down rows {ok, q, data} (^srch-8), as before; no built-in page (P-2029): 503 E_PAGE_MISSING where base d2 has noneGET /api/v2/drafts drf E_FORBIDDENmy drafts, a page at a time: {total, limit, capped?, next?, data} with published version and conflict; limit, cursor; capped, next: 100 unless limit= says, at most 100 (api.list.max; more is 100, capped: true); next, sent back as cursor=, the next page; each with viaName (the AI token it was saved through, when one was) and conflicts: [{who, owner, updated}] (the others drafting the same topic); ?all=1, an admin or the Architect: also all: [{who, owner, topic, updated, size, published, mine, viaName?}], everyone else's drafts (anyone else E_FORBIDDEN), paged the same way with allTotal and allNext, sent back as allCursor=GET /api/v2/drafts/stamp drf{stamp, total} over my drafts (saved, published, dropped) and who else drafts those topics: the Drafts page syncs on itGET|PUT|DELETE /api/v2/drafts/<topic> drf E_SKILL_TRACE E_COMPILE E_KEY E_STATE E_AI_ACCESS E_APPEND_ONLY E_BAD E_CODE_REUSED E_CONTENT E_DRAFT_CHANGED E_ENC E_ENC_CHANGED E_ENC_KEY E_GENERATED E_LINK E_METHOD E_NOT_FOUND E_NO_SECTION E_ON_BEHALF E_PARSE E_PRIVATE E_SECRET E_STATES_UNSAFE E_SETTING E_STYLE E_THOUGHT E_VERSION_REQUIREDone draft: its raw markdown (?format=json or Accept: application/json for the record), save, drop; a save sent as JSON (Content-Type: application/json, as a Page's d2.api sends it) is {text, ver?, section?}, the same save as text with ?ver= and ?section= (text required, else 400 E_ARG; an App's or Page's own JSON body that isn't that envelope is taken as it is); ?section=<id> one section of it (&after=<id> or &at=end adds a new one), a pointer section followed once as on the topic (your draft of the target, else the published one; X-Section-From, JSON from); a topic with show=god parts the caller can't see: a ?section= save only, by the Architect's own AI citing his live Allow once for its publish (D2-Override, unspent; the publish spends it), spliced into the whole text, the hidden parts kept; a section holding a show=god part, as it was or as sent, 403 E_SCOPE; a whole save stays 403 E_SCOPE; a save runs the topic PUT's role fences and content checks (E_SCOPE, E_AI_ACCESS, E_PARSE, E_LINK, E_CODE_REUSED) and carries its hints as warnings; no draft yet of a page only the Architect sees, or of a topic whose only hidden parts are his (show=), read by his own AI with askedBy: the published text, ETag none, JSON {name, text, dver: null, fromPublished} (markdown: X-D2-From-Published) (^adm-9, ^show-11). Base d2's Settings:System, Quotas and Permissions, by an AI: read and drafted only by the Architect's own AI on his clear intent topic.update naming it (D2-Intent) or his Allow once for that save (D2-Override, and the reads it needs), else 401 E_AUTH (^dd-31, P-1815)POST /api/v2/drafts/<topic>/publish drf E_DRAFT_OPEN E_SKILL_TRACE E_COMPILE E_CONFLICT E_DEMIGOD E_ENC E_ENC_CHANGED E_ENC_KEY E_LOCKED E_NATIVE E_NOT_FOUND E_QUOTA E_SECRET E_STATES_UNSAFE E_SETTING E_STYLE E_UNCONFIRMED E_UNPRIVATEpublish it; no draft of it 404 E_NOT_FOUND (no draft of GET /api/v2/backlog pipeevery to-do (checkbox line) of every topic: topic, line, text, status, section; yours from your drafts (?topic=)POST /api/v2/backlog/status pipe E_CONFLICT E_FORBIDDEN E_NOT_FOUND{topic, line, raw, status}: set a to-do's status (todo, next, doing, waiting, review, done); always a draft; 409 if the line changedPOST /api/v2/drafts/publish drf E_DRAFT_OPEN E_SKILL_TRACE E_CONFLICT E_NOT_FOUND E_QUOTA E_STYLE E_UNPRIVATEpublish all of mine, or {topics: [names]} only those; an approval names only the drafts it publishes, and named topics with no draft of any 404 E_NOT_FOUND, asking nothing; an AI sends askedBy when its person said so; each draft asks what its own publish asks (the Architect's own pages and parts are never an AI's to publish: E_SCOPE in its result)GET /drafts drf E_PAGE_MISSINGbase d2's Page:Drafts for a member's session (P-1941); a token is 303 to /api/v2/drafts; no built-in page (P-2029): 503 E_PAGE_MISSING where base d2 has noneGET /api/v2/approvals apprthe open approvals waiting on you (an AI token: on its person) in this project, on every level — a Hero project has no pipeline, so its held asks are reached here and on Notices; {total, limit, capped?, next?, data}; limit, cursor; capped, next: 100 unless limit= says, at most 100 (api.list.max; more is 100, capped: true); next, sent back as cursor=, the next pageGET /api/v2/approvals/ appr E_NOT_FOUNDone approval notice (A-n), open or answered: its request, answer and replay — a notice has no pipeline pagePOST /api/v2/approvals//approve|refuse appr E_FAILED E_IN_PROGRESS E_ITEM_CHANGED E_NOT_FOUND E_TOKENthe person answers one (never an AI: E_SCOPE); approve runs the held action as the AI, as the pipeline's answer doesPOST /api/v2/pipeline/answer {approve: small} apprApprove all small: a person approves every approval for them whose item is small, each run as its own answer would → {answered: [{id, ran?}]}; an AI token E_SCOPEGET /api/v2/pipeline pipe E_FORBIDDEN E_LEVEL E_METHODcoders: pipeline.coders from Settings:Project, beside pick, and a coder pick at open with no ?cap= takes it as the cap. ?needs=me (P-1697): every open item waiting on your answer, held by you, or filed for you (your lane named for you, or named for you by choice in a person's lane); another value is E_ARG. the items: {total, roles, statuses, data, limit, capped?, next?}, a page at a time: 100 unless ?limit= says (api.list.default), at most 100 (api.list.max: more is 100, capped: true); next, sent back as ?cursor= with the same query, the next page; ?limit=n: the newest n by last change, newest first, total still counting all (not a whole number from 1: E_ARG) — with ?pickable=1 or ?free=1, the first n of the pick order (P-1967); ?status= (comma list) ?for=<role> ?name= ?kind= ?feature= ?source= ?open=1 ?size=small|medium|large; ?mine=1 (or ?who=mine): what waits on you yourself — items in your own lane, waiting on your input, your review or to-look-at, approvals for you; the work your agents do in your name is not in it; ?pickable=1: what may be taken, in pick order, and for a role with a batch policy (coders) the batch block: {on, ids, items (rows with why), out: [{id, why}]} — what one agent takes together by the policy table (eight small, one medium, a large alone), leaving out alone items, handovers and what collides with work in progress or with each other; ?pickFor=<agent>: the same list, re-ordered so what follows up that agent's own work comes first (after a released item and a handover) — a re-ordering, never a filter, and without it the list is what it always was; ?free=1: the same minus what collides with an item in progress (a row left out carries collides, and collideFiles: the files at stake — only files collide, never topic names; a row whose overlap can't be known stays in and carries mayCollide). Every answer carries pick: {coder, designer: {mode: stop|1|5|open, count, rushed, ready}} and release.mode (demand|now|alldone|auto, with modeBy, modeAt, toGo at alldone) beside release.auto, derived from it (^pipe-90, ^pipe-91); at 5 an AI's ?pickable=1 for that role is empty until ready; at open it is served whenever anything is pickable, the batch capped at ?cap=n (1 to 8: the dispatcher's coderCap; else 8); batch.on says the role picks in batches, its items capped at the mode; an agent's pick never lists a peer review it wrote (^pipe-review-1). ?fields=a,b (P-345): just those, id always; held ({by, reason, since}) and notLive (true) as stored; whyNotPickable only when named, never in the default view or all: a non-pickable item's plain reasons, first first (waits on P-12 (in progress)), left out for a pickable one and for a tester alone (P-1902)GET /api/v2/pipeline/stamp pipe E_FORBIDDEN E_LEVEL{stamp, total, processing}: the list's stamp under the list's own filters, with the board's, so the page and the pipe map sync on itGET /api/v2/pipeline/totals pipe E_FORBIDDEN{projects, items, byStatus, history}: the items counted over every project; the Architect only (a demigod token E_SCOPE)GET /api/v2/pipeline/pickable?summary=1 pipea summary of what may be taken, for a monitor token as well as for anyone who may list the pipeline (P-519): {role, count, bySize, ids: [{id, size, kind}], handover, alone: {first?, running?}, paused, pick, ready, rushed, release: {open?: {id, status, at, taken}, auto, mode}} — release there while processing is stopped too; pick (coder and designer only): the role's pick mode, stop | 1 | 5 | 8; ready: a pick would hand the role something now (at 1 any pickable item, at 5 or 8 that many or a rushed one, a handover or an item given back); rushed: how many are rushed; release.mode: demand | now | alldone | auto, auto derived from it (^pipe-90, ^pipe-91). ?role= (coder by default). Counts and ids only — no titles, no documents; the items themselves are GET /api/v2/pipeline?pickable=1, which a monitor token may not read. Kept, not deprecated: GET /api/v2/d2/Item?q=pickable:true AND forRole:<role>&summary=1 answers it by calling it, and a monitor token reads only thisGET /api/v2/pipeline/map pipe E_FORBIDDEN E_LEVELthe pipe map's data (P-1592): the MapData the built-in page draws its header map from, for Page:Pipeline's expanded agent view; drawn with /assets/pipe-map.js's d2PipeMap(el, data). mode=agents (^agents-6): the Agents map Page:Agents draws — the same, view complex, mode agents, every role that reports (roles, list with what each live agent holds, recent), canEnd, waits — for a member's session, their AI token with write access or the ops token (a read-only token, a non-member: E_FORBIDDEN; signed out: E_AUTH; below pro: E_LEVEL); any other mode is E_ARGPOST /api/v2/pipeline pipe E_BAD_JSON E_EXISTS E_FORBIDDEN E_LEVEL E_NOT_FOUND E_TICKET E_TICKET_WARNmake an item (P-12); an AI parks an idea this way. a handover carries links.chat, the chat its agent was in (links.chat or chat on the body, else the chat its status names or the live chat it leads), so Start again finds it (^chat-27); links.chat on any other kind E_ARG. review: none (default) | true (its own lane) | a role: for peer review (^pipe-review-1), its filer its first author. doc is required, "" for none (400 E_ARG naming it otherwise, ^err-4); a prompt instead starts the document from it. alone: true runs it by itself on the coder side (a refactor; an AI sets it only with askedBy). summary: the item's one sentence, at most 200 characters (E_ARG over that). An AI's kind approval is 400 E_ARG {details: {item: null, field: kind, got}} (approvals are notices); an AI's status waiting, waiting-input or waiting-error needs a one-line note (400 E_ARG {details: {item, field: note, got}}). An AI's status waiting on a person (links.waitingOn a member's handle) with no links.waitsOn is 400 E_ARG {field: status, use: waiting-input} (^pipe-95). An AI's item filed straight into the coders' lane with no touches is 400 E_ARG {field: touches, why} (^pipe-96); handovers, approvals, admin items and releases are not sent work. A coder ticket, where pipeline.ticketCheck is on, also meets the wider check when filed (P-1631): title ≤60 characters with no kind prefix, id, date or version, the doc opening with **Look at:**, every code in codes with a test line in SpecTest or Testing — else 400 E_TICKET {missing, details: {parts: [{part, problem}]}}; no Journeys: line, ## Scenarios or touches is warnings [{code: E_TICKET_WARN, parts}] on the 201. A call held for approval while the same call's approval A-n is open: 409 E_EXISTS {details: {approval, askedAt}}GET /api/v2/pipeline/<id> pipe E_ARCHIVED E_NOT_FOUND E_PAGE_MISSINGone item's document, raw markdown; ?format=json (or Accept: application/json) for the item with its chain and its history lines, and its hosted runs' cost as stored when it has one (cost: {runs, charged, test?}, US$, P-2649); an id the project issued and no longer holds is 410 E_ARCHIVED {file, url? (a member's signed link to the tasks file)}; /pipeline/<id> is base d2's Page:PipelineItem for a member in their session (none published: 503 E_PAGE_MISSING), and sends a token, or ?format=, here (303, format=json when none)PATCH /api/v2/pipeline/<id> pipe E_ARCHIVED E_BAD_JSON E_COMMIT_ITEM E_CONFLICT E_EXISTS E_INTENT E_IN_PROGRESS E_NOT_FOUND E_NOT_PUSHED E_NOT_YOURS E_NO_COMMIT E_NO_TOUCHES E_PINGPONG E_REPO_UNREACHABLE E_REVIEW_MERGE E_REVIEW_ROLE E_SELF_REVIEW E_STATE E_STATUS E_TICKET E_TICKET_WARNan agent's {forRole: coder agent} (forName null, a note, nothing else) passes the lane check on its person's live item.send intent naming the item (D2-Intent; an ended one 409 E_INTENT), P-1738; status waiting-input (P-1697) needs waitingOn, the person whose answer it waits for, and a doc ending with a ## Questions block (pad lines) or the answer marker, or a held coder ticket (kind impl, designer lane, for that person): else E_ARG {field: doc, want}; {waitingOn: null} on a waiting-input item puts it back with its holder, or new; a person's in-progress on an item an agent holds is E_IN_PROGRESS {holder} (answer it with /answer {text}). {status?, review? (none | true | a role: peer review, ^pipe-review-1 — set by an author (its filer, a taker) or its person until it is finished, then only its person; an agent never changes or clears one once set, but with askedBy; E_SCOPE, E_STATUS. An author's done puts it in review for that role's agents, held by nobody — a code item's close with a commit already on pipeline.repo's branch= is E_REVIEW_MERGE, unless it was reviewed already (review.by): then it is built again, waiting-release; the reviewer's done (an agent of that role that wrote none of it: E_SELF_REVIEW, E_REVIEW_ROLE) or its person's accepts it: done, or waiting-release for code, review.by the reviewer), forRole?, forName?, title?, summary?, doc?, note? (alone: allowed on an item someone else holds), stage? (a release's, from its holder: deploying | live {version}: its items ship | gate-failed {gate, reason, owner: designer|coder|POST /api/v2/pipeline/<id>/finish pipe E_ARCHIVED E_BAD_JSON E_COMMIT_ITEM E_CONFLICT E_EXISTS E_IN_PROGRESS E_NOT_FOUND E_NOT_PUSHED E_NOT_YOURS E_NO_COMMIT E_NO_TOUCHES E_PINGPONG E_REPO_UNREACHABLE E_REVIEW_MERGE E_REVIEW_ROLE E_SELF_REVIEW E_STATE E_STATUS E_TICKET E_TICKET_WARN{note?, conclusion?, commit?, touches?, docs?, as?, name?, ver?, askedBy?}: finish it (an investigate item needs its conclusion, ^ops-6), the same close as PATCH {status: "done", …} with these fields (any other field is left out): a code item closed with its commit answers waiting-release; your role's pipeline.finish may send it to reviewGET /api/v2/pipeline/<id>?format=topic pipe E_NOT_FOUNDthe item as a topic: a front matter of the fields that are edited (title, kind, for, size, important, rank, feature, touches, waitsOn, links) over its document. Its id, status, source, taker and dates are not in it (^pipe-58)PUT /api/v2/pipeline/<id> pipe E_COMMIT_ITEM E_CONFLICT E_IN_PROGRESS E_NOT_FOUND E_NOT_PUSHED E_NOT_YOURS E_NO_COMMIT E_NO_TOUCHES E_REPO_UNREACHABLE E_STATUSthat same text back, Content-Type: text/markdown: the PATCH underneath, so every field check holds. A bad line is E_ARG naming the line and nothing is saved; status never changes this way (^pipe-58)POST /api/v2/pipeline/<id>/take pipe E_ALONE_BUSY E_ALONE_NEXT E_ALONE_RUNNING E_CHATTING E_EXISTS E_FAILED E_HELD E_IN_PROGRESS E_NOT_FOUND E_ORDER E_PASS_FULL E_PAUSED E_REVIEW_ROLE E_SELF_REVIEW E_STOPPED E_STOP_PENDING E_TAKEN E_TICKET E_TICKET_WARN{name?, ver?, waitingOn?: "self", askedBy?}: take it (in-progress, or waiting on itself); an AI's take of a coder ticket (kind impl) in the designer lane another agent filed for its person (forName) is 409 E_HELD {forName, by} until the person sends it to the lane without a name or names that agent, and an AI's pick never lists it (P-1766); a peer review waiting on its reviewer is taken only by an agent of its review role (E_REVIEW_ROLE) that is none of its authors, by the name it takes under or its token's agent (E_SELF_REVIEW, naming them; ^pipe-review-1); while processing is stopped an AI's take of coder work is E_STOPPED unless it carries askedBy: its own person — a release item and other roles' items are taken as usual (^pipe-69: processing stopped: no new coder work; releases go on); E_TAKEN if someone did first; E_ALONE_NEXT / E_ALONE_BUSY / E_ALONE_RUNNING: an alone item is first or running on the coder side; a take always succeeds and carries collides / mayCollide: the items in progress it overlaps, or mayPOST /api/v2/pipeline/<id>/putdown pipe E_IN_PROGRESS E_NOT_FOUND E_NOT_YOURSany dispatcher agent may also put down, never close, an item whose holder was declared dead (^pipe-93); a live holder's stays E_NOT_YOURS. {note?}: put it down: its taker (or its person) releases it, in-progress → new with no taker, so it can be parked; another agent E_NOT_YOURS, another member E_IN_PROGRESSPOST /api/v2/pipeline/<id>/fail pipe E_NOT_FOUND E_NOT_YOURS{why}: its holder gives it back as failed on the item's own content; the first agent's releases it to the role (new), a second different agent's makes it status failed, back to its asker and waiting on them, and take is 409 E_FAILED until they release it (^pipe-73). Deaths (gone, stopped) are kept apart in deaths and never countPOST /api/v2/pipeline/<id>/pause pipe E_NOT_FOUND E_STATUS{askedBy?}: hold it: a new or pending item goes to paused, keeping its rank, and comes back to the status it had; E_STATUS on anything else (in progress, waiting, review or parked). A person's call (^pipe-57)POST /api/v2/pipeline/<id>/resume pipe E_NOT_FOUND E_STATUS{askedBy?}: back to the status it was paused from; E_STATUS if it isn't paused (^pipe-57)POST /api/v2/pipeline/<id>/run pipe E_NOT_FOUND E_STATUS{all?, askedBy?}: ▶ Run, the paused row's one tap — the item back to the status it was paused from. With all: that item and the paused items that depend on it, transitively (follows or waitsOn), except one that also waits on something outside the chain that isn't closed. Answers {resumed: [ids], left: [{id, waitsOn}]}; E_STATUS if it isn't paused (^pipe-57, P-516)POST /api/v2/pipeline/pause-all | /resume-all pipe E_LEVEL{role?, forName?, askedBy?}: the list's own filter: pauses every new and pending item it shows, or resumes every paused one, and answers the ids moved. Parked items are outside it at both ends (^pipe-57)POST /api/v2/pipeline/take pipe E_ALONE_BUSY E_ALONE_NEXT E_ALONE_RUNNING E_CHATTING E_FAILED E_HELD E_LEVEL E_NOT_FOUND E_ORDER E_PASS_FULL E_PAUSED E_REVIEW_ROLE E_SELF_REVIEW E_STOPPED E_STOP_PENDING E_TAKEN{items: [ids], as, name, askedBy?}: takes them all or none (askedBy passes a stopped pipeline, ^pipe-69), each as POST /pipeline/<id>/take would → 200 {taken, items, collides?, mayCollide?} (the last two by item). The first that can't be taken refuses the whole, named (409 E_TAKEN, 403 E_SCOPE, 404, E_ALONE_*); a gate that would ask your person is a refusal here: take that one on its own. The ids to send are GET /api/v2/pipeline?role=<r>&pickable=1's batch.ids (^pipe-40)POST /api/v2/pipeline/<id>/rush pipe E_NOT_FOUND E_NO_KEY E_STATUS{on?, askedBy?}: a person wants it next — it goes first in its role's pick order, ahead of important ones; on: false unrushes it, and d2 clears it itself when the item is taken. Only a new or pending item (else E_STATUS). pipeline.rush, asks in every preset, so an AI token needs askedBy (E_SCOPE without one). Answers {rushed, changed, told: {to, how}}: `rush <item>` goes to role:dispatcher on the person's board, and with no live dispatcher for that role d2 asks their monitor to start one (^pipe-68, P-576)POST /api/v2/pipeline/stop | /start pipe E_EXISTS E_FORBIDDEN E_STATUS E_STOPPED{roles?, note?} on stop, {roles?} on start: per role (P-1139) — roles is a list of coder, designer; left out, stop holds coders (the old meaning) and start starts every stopped role; another role is E_ARG, stopping a stopped role E_EXISTS, starting a running one E_STATUS. Stored as processing {stopped, roles, by, at, note, each: {role: {by, at, note}}}; a record without roles reads as coders. A designer stop holds the designer lane's AI takes only; handovers and releases are never held. The switch for the project's pipeline — processing stopped: no new POST /api/v2/pipeline/release pipe E_EXISTS E_FORBIDDEN E_RELEASE_OFF{note?}: an agent with auto-release off and no askedBy: 409 E_RELEASE_OFF (^pipe-77). Files the release item for the dispatchers (kind release, forRole dispatcher, important, small, touches nothing; title Release: full suite, razwip → main, deploy, box suite; its document lists the items done with commits since the last release, and the note) → 201 {item}. Stop processing's gate: a moderator, an admin or the Architect in their own session; any token 403 E_SCOPE, a member 403 E_FORBIDDEN. One open at a time for an AI: 409 E_EXISTS {item}; a person's ask while one nobody took is open drops it (note: replaced by P-m: asked again by PUT /api/v2/pipeline/pick pipe E_FORBIDDENa working role's pick mode (^pipe-90, P-1592): role coder | designer, mode stop | 1 | 5 | open (another, 8 included, is E_ARG; a kept 8 reads open). stop is the role's stop (as POST /pipeline/stop {roles: [role], note}: who, when, note; its board notice; already stopped, unchanged); any other mode starts a stopped role and keeps the number. At 1 an AI's pick hands the next item; at 5 it hands nothing until 5 are pickable for the role, then them together (the batch block capped at 5); at open it takes at fill: ready whenever anything is pickable, the block everything pickable up to the pick's ?cap= (the dispatcher's coderCap), round after round until empty or stopped; a rushed item, a handover or an item given back goes at once with what can go along; no timer. A person's read is never narrowed. Stop processing's gate (^pipe-92): a moderator, an admin or the Architect in their own session; any token 403 E_SCOPE, a member 403 E_FORBIDDEN → 200 {role, pick: {mode, count, rushed, ready}, processing, told?}PUT /api/v2/pipeline/release-mode pipe E_FORBIDDENthe release mode (^pipe-91, P-1592): demand (the default: a person's ↑ Release) | now (the release is filed once the coder items in progress at the press are done, at once when none is, carrying what is finished then; the roles keep picking) | alldone (filed once nothing is pickable or in progress for coders, no coder agent is working, merging or stuck, and an item waits for a release) | auto (the dispatchers release as they judge; release.auto). d2 files the release as the person who set the mode (askedBy and forName), with the board message a press sends; once it ends (done, live, failed or dropped) the mode is demand again. Stop processing's gate (^pipe-92): any token 403 E_SCOPE, a member 403 E_FORBIDDEN → 200 {release: {mode, modeBy, modeAt, toGo?, filed?, auto}, filed?}POST /api/v2/pipeline/<id>/accept pipe E_COMMIT_ITEM E_NOT_FOUND E_NOT_PUSHED E_NOT_YOURS E_NO_COMMIT E_NO_TOUCHES E_REPO_UNREACHABLE E_REVIEW_ROLE E_SELF_REVIEW E_STATUS{comment?, note?, commit?}: a peer review (^pipe-review-1): its reviewer (an agent of its review role that wrote none of it, holding it once taken) or its person accepts it — done, or waiting-release for a code item with its commits, review.by the reviewer; else its person accepts a review (done); with a comment it goes back to whoever finished it, new, acceptedIf: their next finish closes it (a note says how it was resolved), or they return it with questionsPOST /api/v2/pipeline/<id>/seen pipe E_NOT_FOUND✓ Seen: its person (or their AI, as pipeline.review allows) takes a done item off the to-look-at list; E_ARG if it isn't on itPOST /api/v2/pipeline/<id>/unseen pipe E_NOT_FOUNDundoes Seen: a done item back on its person's to-look-at list; that person only, never an AI (E_SCOPE)POST /api/v2/pipeline/<id>/start pipe E_CLOSED E_NOT_FOUNDStart on a daily digest: its person, in their own session (any token E_SCOPE), sends the plan's command to their monitor, or with no monitor marks the plan's items important; the digest is done → {started, plan, item}. E_ARG on any other kind, E_CLOSED once started (^pipe-60)POST /api/v2/pipeline/<id>/sendback pipe E_NOT_FOUND E_NOT_YOURS E_REVIEW_ROLE E_SELF_REVIEW{note}: a peer review (^pipe-review-1), by its reviewer or its person: in progress for its last author again, still for review, review.back {by, at, note} and a back entry in that agent's next; else back to whoever finished it, reviewAgain: its next finish always comes back to review; E_ARG without a notePOST /api/v2/pipeline/<id>/return pipe E_CLOSED E_IN_PROGRESS E_NOT_FOUND E_PINGPONG{note, askedBy?}: back to whoever sent it; an item accepted if or sent back with a comment goes to its reviewer's review with your questions insteadPOST /api/v2/pipeline/<id>/ask pipe E_IN_PROGRESS E_NOT_FOUND{to?, question!}: ask up the chain (coder, tester, guardian → designer; designer → person): a question item (links.asks), this one waiting on it until it's closed with the answerPOST /api/v2/pipeline/<id>/handoff pipe E_CLOSED E_COMMIT_ITEM E_IN_PROGRESS E_NOT_FOUND E_NOT_PUSHED E_NO_COMMIT E_NO_TOUCHES E_PINGPONG E_REPO_UNREACHABLE E_STATUS E_TICKET E_TICKET_WARN{forRole, forName?, title?, kind?, note?, touches?, supersedes?}: done, and the next item made, linked both ways; an agent's handoff of an item for review is E_STATUS: it finishes with done, so its reviewer sees it (^pipe-review-1)GET /api/v2/settings/search?q= srch E_NOT_FOUNDthe settings you may reach, as a tree page › tab › setting, each with its link (href#anchor) and who it's for (all, member, admin, god — the Architect); without q, the whole treeGET /api/v2/settings/model srch E_FORBIDDENSettings:Project as base d2's ProjectSettings class (Spec:SettingsModel) describes it: fields in the class's order, each {name, line, kind, options, help, label, locked, value, problem}; unknown (lines the class doesn't know, kept), problems, ver, and vars, the live switches paired with it (ProjectVars: autoRelease, batch, processing). HTML twin: /topics/Settings:Project?view=propsGET /api/v2/permissions perm E_FORBIDDENwhat an AI may do on its own here: each action's mode (person, asks, tells, free), where it comes from, whether the project customized d2's default; intents: {on, gating, trying, override}, the Intents card (on: the project's intents setting; gating: base d2 widens on them; trying: recorded only; override: one-time overrides on)GET /ai/permissions perm E_PAGE_MISSINGbase d2's Page:Permissions for a member's session (base d2's for anyone signed in; P-1941); a token is 303 to /api/v2/permissions; no built-in page (P-2029): 503 E_PAGE_MISSING where base d2 has nonePOST /api/v2/permissions/customize perm E_EXISTS E_FORBIDDENadmins: copy d2's default into the project to change itPUT /api/v2/permissions perm E_FORBIDDEN E_LOCKED E_NOT_CUSTOM{preset?, modes?}: admins of a customized project, d2 included (else E_NOT_CUSTOM). A key is one of the 32 rows or a verb of one (item.answer: a specific row that decides for that verb before its group; a locked row or a verb of one is E_LOCKED, a hard rule or an old id is E_ARG naming the new row)PUT /api/v2/permissions/presets perm E_FORBIDDEN E_LOCKED E_NOT_FOUND{preset?, modes?, levels?}: the Architect, on d2 only: the presets every project starts from (Settings:Permissions); a locked row is E_LOCKEDDELETE /api/v2/permissions perm E_FORBIDDEN E_NOT_CUSTOMadmins: Reset defaults, back to d2'sDELETE /api/v2/permissions/notes perm E_FORBIDDENadmins: dismiss the notes where migration 066 merged two old rows of one row differently and the stricter mode won (the GET lists them as notes until then)POST /api/v2/pipeline/<id>/drop pipe E_CLOSED E_NOT_FOUND{reason, askedBy?}: an AI names the person who askedPOST /api/v2/pipeline/<id>/restore pipe E_INTENT E_NOT_FOUND E_STATUS{note?}: bring back a dropped item (^dd-35): back to the status it had before the drop (new when that can't be again), restored in its history; its person's (who it is for, who asked for it, or the project's owner), in their session or by their agent on their intent or Allow once (pipeline.restore: person); E_STATUS on an item that isn't droppedPOST /api/v2/pipeline/<id>/reopen pipe E_INTENT E_NOT_FOUND E_STATUS{note?}: reopen a done item (^dd-35): back to new in its lane, reopened in its history; the item's own person (who it is for, who asked for it, its filer, or its project's owner; their AI on their askedBy or intent, pipeline.restore: person), anyone else 403 E_SCOPE; E_STATUS on an item that isn't donePOST /api/v2/pipeline/<id>/demote pipe E_CLOSED E_IN_PROGRESS E_LEVEL E_NOT_FOUND{topic?, section?}: a person demotes an item to a to-do (default ToDo:Project, section From the pipeline, made if missing); the item's status is todo (/todo answers until the next release, with a note)POST /api/v2/pipeline/<id>/todo pipe E_CLOSED E_IN_PROGRESS E_LEVEL E_NOT_FOUNDthe old name of /demote, answering the same with a note until the next releasePOST /api/v2/pipeline/<id>/note pipe E_IN_PROGRESS E_NOT_FOUND{text}: add to its document; the status stays. Anyone may add one to an item another agent holds: its taker is told (`told`); on a release whose gate failed it is the fix arriving and the gate re-runs (`rerun`)POST /api/v2/pipeline/<id>/move pipe E_CLOSED E_NOT_FOUND{project}: a person moves it to another of their projects; copied there, marked moved herePOST /api/v2/pipeline/promote pipe E_CONFLICT E_EXISTS E_LEVEL{topic, line, raw, to?, title?, forRole?, size?, prompt?, text?}: Create ticket, a to-do becomes an item (title, forRole, size and prompt over the line's); with to: "thought" (and text?) Create thought, a thought of the caller's own with a link back → {thought, link}; either way the to-do is ticked with its link, as a draft; a line already linking one is 409 E_EXISTS, a changed one 409 E_CONFLICTGET /api/v2/history his E_METHOD?q=&type=&layer=&feature=&anchor=&item=&writtenIn=&topic=&person=&agent=&since=&until=&limit=&cursor=: the entries, newest first, paged (next is the cursor): 50 unless limit= says, at most 100 (api.list.max; more is 100, capped: true), {total, data, limit, capped?, next?}; q: a part of the text, the item or the topic it was written in (case ignored; next carries it); type: one or a comma list; person=GET /history his E_PAGE_MISSINGthe Query screen: base d2's Page:History for a member's session on pro and master (P-1941); a token is 303 to /api/v2/history; hero and creator get their line; no built-in page (P-2027): 503 E_PAGE_MISSING where base d2 has noneGET /api/v2/history/ his E_NOT_FOUNDone entry: {ok, entry}, or 404GET /api/v2/history/log his?limit= (default 5, at most 100): the Project log's rows (^prj-2), newest first — decisions, changes and each topic save (one topic's saves by one author in a day folded) — as the viewer sees the topics, never Thought: or Settings:: {ok, rows: [{at, kind: decision|change|save, text, topic?, ver?, by}]}, on every levelPOST /api/v2/history his E_FORBIDDEN E_PARSE{type: decision|change|ai|pipeline, text (markdown, may span lines), about?: {layer: spec|design|build|skill, anchors?}, person?, feature?, item?, writtenIn?, replaces?}: add one; at and author are d2's. A decision needs person, a decision or change needs about.layerPOST /api/v2/history/import his E_FORBIDDEN E_PARSE{entries: [...]}: mods, admins and the Architect; at (normalised to ISO UTC), author and source kept as given; 201 when anything was added; a line whose source is already in is skipped and countedGET /topics/Log:ProjectChanges histhe generated views, the last 7 days, read-only (a write is 409 naming POST /api/v2/history): Log:ProjectChanges (decisions and changes), Log:AiChanges (ai), Log:PipelineChanges (pipeline), Log:Performance (run); the old Log:Project, Log:Ai, Log:Pipeline 301 there; ?layer=spec|design|build|skill|all, ?feature=, ?item=, ?anchor=GET /history his?log=ProjectChanges|AiChanges|PipelineChanges|Performance&type=&from=&to=&item=&person=&agent=&feature=&layer=&cursor=: the Query screen over the whole history the store keeps (7 days; older is in the _archive/history-*.jsonl.gz files it names), pro and masterGET /billing acc E_PAGE_MISSINGthe Billing page: base d2's Page:Billing for the person's session (Page:AccountPlan until it is published; P-2145); a visitor goes to the log-in and back; a token E_SCOPE (the person's own page); neither Page, 503 E_PAGE_MISSING naming bothGET /billing (on a project) acc E_PLAN E_PROVIDERthe project's Billing (^sell-11, P-2258): on a project with a plan on sale, for anyone signed in but its owner and admins (who keep AIPutty's own), and for an app user anywhere: their plan there, renews or ends on, Get, Switch and Cancel, Card and receipts (the project's own Stripe portal); nothing of AIPutty's own plan, add-ons or credits. Its forms POST /billing/buy {plan, period}, /billing/change {plan} and /billing/cancel, same origin, each a 303 to Stripe's Checkout or back with ok= or err=GET /account/plan accthe old Plan page's address: 302 to /billing, its query kept (P-2145)GET /api/v2/payments/plan acc E_FORBIDDENyour plan: plan, planUntil, planStatus, nextPlan, move?: {to, label, price, at} (an old plan's move, scheduled, P-2226), prices (the person only); plans: [{plan, label, price, limits}] (each card, its name as Settings:Quotas labels it), use: [{project, rows: [{key, label, used, usedText, limitText, pct}]}] (this period, each project you own), why (payments aren't open), mode (sandbox | live), manageUrl; payWith: [{id, label, may, why?}], how one may pay (PayPal greyed: may false, P-2219); addons carry label, and a line paid for on its own its price and until; monthly: {amount, currency, text}, the plan by the month plus those lines, tax apart (P-2145)POST /api/v2/payments/checkout acc E_NOT_FOUND E_ORIGIN E_PLAN E_PROVIDER E_TEST_ACCOUNT E_TOO_BIG{plan: a plan of Settings:Quotas for sale (its price not marked not sold; E_PLAN otherwise, naming the plans for sale), addons?: [{addon, quantity?}], back?: a path on this site where the provider returns the person once paid, default /billing (?done=1&from=free added, a #fragment kept; anything but a path: E_ARG; WelcomeKind's Get the Monthly plan comes back to /welcomekind, P-2497)}: {url} to the provider's checkout (with Stripe, one line for the plan and one for each add-on); the plan changes when its webhook arrives. provider?: card; paypal (or a box whose provider is PayPal) is E_PROVIDER 409, PayPal being off for now (P-2219). The person only: E_SCOPE for any tokenPOST /api/v2/payments/membership acc E_NOT_FOUND E_ORIGIN E_PLAN E_PROVIDER E_TEST_ACCOUNT E_TOO_BIG{addon}: a membership d2 sells (an add-on of kind membership naming one project): {url} to Stripe's checkout, on its own subscription whatever your plan; the webhook makes you a member of the project, and its end takes that back (E_PROVIDER where the provider isn't Stripe). The person onlyGET /api/v2/payments/portal acc E_NOT_FOUND E_ORIGIN E_PLAN E_PROVIDERa fresh Stripe customer-portal address (cards, invoices, cancelling) for the person's customer: a 303 to it, or POST for {url}; E_PLAN when nothing was paid through Stripe yet. The person only; it is the Plan page's manageUrl for StripeGET /api/v2/payments/project-plan acc E_NOT_FOUND E_PROVIDER{ok: true} once selling is on (Stripe set up on the box; else 503 E_PROVIDER), as Page:Publish asks; with ?project= the person's plan there: {project, receiptName, member, join, plan (the one held now, else the free plan), free, bought?: {plan, period, status: active | ending, paidTo, until?, next?, nextAt?, pastDue?: {since, by, why}}, given?: {plan, note?} (an admin's, for free), portal (a customer there to manage), plans: [{plan, prices: [{period, first, recurring, once}]}] (those on sale, top the highest)}. Nothing of d2's own plan, add-ons or credits. The person's session onlyPOST /api/v2/payments/project-plan acc E_FORBIDDEN E_NOT_FOUND E_ORIGIN E_PLAN E_PROVIDER E_TOO_BIG{project, plan, period: year | month}: a plan of the project's Settings:Plans on sale for that period (hidden, the free plan or no price for the period: E_PLAN naming those on sale) → {url} to Stripe's Checkout (subscription mode, d2's account: the product POST /api/v2/payments/project-plan/change acc E_NOT_FOUND E_ORIGIN E_PLAN E_PROVIDER E_TOO_BIG{project, plan}: the plan bought there becomes another on sale for its period: a dearer one at once, prorated on an invoice now; a cheaper one from the end of the period paid ({at: now | period-end}); the plan held undoes a downgrade or a cancel waiting. Nothing bought there E_PLAN; Stripe refusing E_PROVIDER, nothing changedPOST /api/v2/payments/project-plan/cancel acc E_NOT_FOUND E_ORIGIN E_PLAN E_PROVIDER E_TOO_BIG{project}: the plan bought there ends at the end of the period paid ({until}); it stays on until then, then the free plan (paid only: a visitor). Cancelled already E_PLANPOST /api/v2/payments/project-plan/portal acc E_NOT_FOUND E_ORIGIN E_PLAN E_PROVIDER{project} → {url}: a fresh Stripe customer-portal address for the person's own customer on that project (its card, subscription and receipts, nothing of d2's own); GET ?project= is a 303 to it. Nothing bought there yet E_PLANGET /api/v2/project/earnings acc E_FORBIDDEN E_NOT_FOUNDon a project, its owner and admins (their session; any token E_SCOPE): what its plans sold (P-2259, ^sell-14, ^sell-16) → {project, feePct (d2's fee: Settings:Quotas sell.feePct, 5; sell.feeGodPct, 0, on the Architect's own), month: {month, sold (before tax), tax (collected: AIPutty's to pay, never the owner's), card, fee, net, new, cancelled, failed}, balance (the owner's earnings, US$), byPlan: {<plan>: sold}, sales: [{id, at, plan, buyer, country, price, tax, card, fee, net, refundable, refunded?}] (newest first, 200)}. US$ numbers; the fee and the card cost are on the price before taxPOST /api/v2/project/earnings/refund acc E_FORBIDDEN E_NOT_FOUND E_ORIGIN E_PROVIDER E_STATE E_TOO_BIG{sale}: the project's owner alone (their session) refunds one sale whole (^sell-15): d2 refunds it at Stripe, its tax too; it is booked back (the price, the tax and d2's fee; the card cost stays), and the plan it paid for ends now (stopped at Stripe, the member on the free plan) → {sale, refund, amount, balance}. Refunded already, or no charge at Stripe, 409 E_STATE; another project's sale 404 E_NOT_FOUND; an admin 403 E_FORBIDDENGET /api/v2/project/earnings/statement acc E_FORBIDDEN E_NOT_FOUNDthe project's owner alone: a year's statement for their own income tax (^sell-16), CSV: date, plan, buyer country, price, tax, card, fee, net; refunds and disputes as negative linesPOST /api/v2/payments/cancel acc E_NOT_FOUND E_ORIGIN E_PLAN E_PROVIDER E_TOO_BIGd2 cancels the subscription at the provider, then cancelling until planUntil; E_PROVIDER if it refuses (the plan stays active)POST /api/v2/payments/switch acc E_NOT_FOUND E_ORIGIN E_PLAN E_PROVIDER E_TEST_ACCOUNT E_TOO_BIG{plan}: a bigger plan at once, a smaller one at planUntil; {url} when the provider needs an approvalPOST /api/v2/payments/addon acc E_NOT_FOUND E_ORIGIN E_PLAN E_PROVIDER E_QUOTA E_TEST_ACCOUNT E_TOO_BIG{addon, have?}: one add-on with a price in Settings:Quotas, on top of an active Yearly or Monthly plan (Free: E_QUOTA naming the plans; a held level or feature E_PLAN; a cancelled plan E_PLAN; a plan paid through PayPal E_PROVIDER 409, add-ons are sold by card only for now; optional {have}: how many of it you hold, so a resent request is E_PLAN, never a second line; one purchase at a time): the provider adds it to the subscription as its own line, it is on at once and its first charge is prorated → {addon, amount, currency, prorated, until} (^addon-2, ^addon-7). The person only; the form twin is POST /account/plan/addonPOST /api/v2/payments/stop acc E_NOT_FOUND E_ORIGIN E_PLAN E_PROVIDER E_TOO_BIG{addon}: Stop one add-on you bought on top of the plan (^addon-11): its line ends at the provider at the end of the period paid for, no refund; it stays on until then (until), then goes; uses says what of your use it carries (you get a notice; that stops growing, nothing is deleted). Not one with the plan or the Architect's (E_NOT_FOUND); stopping already or the plan cancelled E_PLAN; the provider refusing E_PROVIDER, nothing changedGET /api/v2/credits accthe person's d2 credits (P-2071): {balance and remaining (US$, two decimals: the same, the balance now), consumed (this calendar month's charges, UTC, US$), unlimited (the Architect: charged like anyone, remaining 0, no packs, no top-up; P-2219), payWith: [{id, label, may, why?}] (PayPal greyed), topup: {at, amount} | null, packs, card, brake (your spend brake in US$, null when off) and brakeWindow (minutes) (P-2253), loads: the last 50 {at, kind: load | reload | grant | test-grant | close, amount, money: test | real (whose money, P-2493: a test grant, a tester's account or Stripe's test mode is test; P-2613), note?}, byModel: this month's [{model, runs (its runs, one per run), calls (its model calls), charged, own}] (P-2513, P-2570), byRole: the person's charges per role [{role (their run's agent's role; other for what has none: a Story run, an app's hour), day (today), week (from Monday, UTC), month (UTC): US$ two decimals, test? (true on a tester's account's rows: Testing, apart)}], each period's rows adding up to what credits paid in it, the month's to consumed (P-2080), spend: the last 30 UTC days of the person's own runs, newest day first, [{day, agent (their API agent's id, d2's shared smart agent's, a hosted role's, story, or project/repo for an app's hour), kind: chat | look (a Meld look's triage) | test (an agent's Test) | story | app, model, runs, calls, charged (US$ credits paid), own (US$ their own key cost), test? (true on a tester's account's lines: Testing, shown apart, P-2613)}] (P-2570): every charge is on one line; never another person's}. Charges are not itemized per call: what an agent used is on its ApiUsage rows. The person's session only: E_SCOPE for any tokenPOST /api/v2/credits/checkout acc E_ORIGIN E_PLAN E_PROVIDER E_TEST_ACCOUNT E_TOO_BIG{pack: 10 | 25 | 50 (Settings:Quotas credits.packs), topup?: {at, amount}, provider?: card (paypal: E_PROVIDER 409, P-2219), back?: a path on this site where Stripe returns the person, default /billing (?done=1 or ?cancelled=1 added, a #fragment kept; anything but a path: E_ARG, P-2375)}: {url} to Stripe's Checkout (payment mode, the card saved for top-ups); credited by the webhook once; a `topup` given is set once the payment succeeds. A test account (a tester's, or a d2test- address) on a live Stripe (P-2493): {url, mode: test} to Stripe's test mode (the test key, a test customer of its own, no top-up: `topup` E_ARG), or E_TEST_ACCOUNT 409 with no test key set; any live payment by a test account (a plan, a membership, a switch, an add-on, a top-up) is E_TEST_ACCOUNT. The person's session only: E_SCOPE for any token; any member, with or without a plan (P-2145)PUT /api/v2/credits/topup acc E_ORIGIN E_TOO_BIG{at, amount} or {off: true}: when a charge leaves the balance at or below `at` US$, d2 reloads `amount` US$ on the saved card; a refused reload turns the top-up off and tells the person. The person's session only: E_SCOPE for any tokenPUT /api/v2/credits/brake acc E_ORIGIN E_TOO_BIG{amount} (US$, 0.01 to 10000) or {off: true}: your own spend brake (^credits-14, P-2253): an agent of yours spending more than it within spend.brakeWindow (10 minutes) pauses and asks you Keep going? in its chat; spend.brake (US$ 10) until you set one → {brake: amount | null (off)}; GET /api/v2/credits answers it (brake, brakeWindow in minutes). The person's session only: E_SCOPE for any tokenPOST /api/v2/credits/brake acc E_ORIGIN E_STATE E_TOO_BIG{run: the chat's id, go: true}: your tap on Keep going? (^credits-14, P-2253): the API agent the spend brake paused in that chat of yours goes on, its window starting again; not paused, or not your chat, 409 E_STATE. The person's session only: any token, the agent's own included, E_SCOPEPOST /api/v2/payments/webhook/<provider> acc E_COPY E_GONE E_NOT_FOUND E_TOO_BIGthe provider's signed events, each applied once by its id (400 E_AUTH on a bad signature, whichever provider); on a copy box 503 E_COPY (^sys-18)GET /api/v2/vault vaultyour secrets: names, notes, last four, who may use each; never values (people); {ok, total, limit, capped?, next?, data, tokens}: limit, cursor; capped, next: 100 unless limit= says, at most 100 (api.list.max; more is 100, capped: true), by name (d2-user-enc first); next, sent back as cursor=, the next pageGET /api/v2/vault/<name>/users vault E_NOT_FOUND{name, tokens: [{tokenId, project, token, prefix, at, live, agent?}], agents: [{tokenId, agent, of, project, carries, canRemint, pending}]}: the tokens and agents allowed to use that secret, yours; asked when a drawer opens (P-2147); a person only, every AI token E_SCOPEPOST /api/v2/vault vault{name, value | generate: true, note?, tokens?, kind?, roles?, rules?}: save a secret, optionally allowing tokens now; generate: true (^vault-20): d2 makes the value, 32 random bytes as 43 base64url characters nobody sees (not in the answer, history or audit; the row shows generated); an agent token on its person's word (^vault-17: their live clear intent or Allow once, intentGating on) makes a slot (no value: {name, note, roles}) or a generated one, never with a value (E_SCOPE); rules: the projects whose rules may read it with secret("name") (^http-9, a list or a comma line, at most 20; a secret only, d2 can't open a key); from a frame (a signed base Page, Page:AiAgents: the list, this, PUT and DELETE of an api-<x> name, never a value, reveal or grant) only a name api-<x> and no tokens, roles or rules, else 403 E_SCOPE {field: name}PUT /api/v2/vault/<name> vault E_EXISTS E_NOT_FOUND{rename, everywhere?} (P-2146): a new name, in its owner's own session only (any token or frame E_SCOPE), alone in its PUT; unused (E_EXISTS) and not one of d2's own (d2-user-enc, d2-demigod, d2-chat-launcher, support-email-apppwd: E_ARG); grants, reads, suspension and kind move with it, nothing is opened → {secret, uses, warning?} or, with everywhere: true, {…, drafted, changed, left, next}: each topic naming it rewritten as your draft, an API agent's key set. {value?, note?, notify?, kind?, rules?}: a new value is re-sealed for the tokens already allowed; rules replaces "used by rules in" ([] or null clears it)GET /api/v2/vault/<name>/uses vault E_NOT_FOUND{name, uses: [{project, kind: deployment|step|rule|apiagent, topic?, object?, lines?, draft?}]} (P-2146): where your own projects' topics name it (vault:<name>, {vault:<name>}, secret("<name>")) and your API agents' keys, at most 200; your own session onlyDELETE /api/v2/vault/<name> vaultdelete it; agents lose it at oncePOST /api/v2/vault/<name>/grants vault{tokenId, value?, roles?}: allow one of your tokens (picked by id), in your own session; an agent token only on its person's word ({roles: [coder]} or {tokenId}, no value: d2 seals the owner's copy; ^vault-17), any other AI token E_SCOPE; a key takes the value again (E_ARG without it), a secret none: d2 seals your copy, counted with Reveal (E_RATE); a value sent must be the saved one; audited A_VAULT_ALLOW with from: value or owner-copyDELETE /api/v2/vault/<name>/grants/<tokenId> vaulttake it back: its person's own session, or their own agent token on their word for that one call (^vault-17: their live clear intent, D2-Intent, or their Allow once, D2-Override; intentGating on), never a value (^vault-18); an agent may name a role (coder) for every role token of it herePOST /api/v2/vault/<name>/suspend|resume vaultsuspend every agent's access at once, the grants kept (reads answer E_SECRET_SUSPENDED); resume gives it back to them all: its person's own session, or their own agent token on their word for that one call (^vault-17: their live clear intent, D2-Intent, or their Allow once, D2-Override; intentGating on), never a value (^vault-18)GET /api/v2/vault/<name>/reads vaultthe read history (90 days)POST /api/v2/vault/<name>/reveal vault E_NOT_FOUND{copy?}: the value, to its owner in their own session (Cache-Control: no-store; a read on the secret's list); a token E_SCOPE, but for the Architect's own agent on his intent and his Allow once together for that one call (P-2194)POST /api/v2/vault/resolve vault E_ENC_KEY E_FORBIDDEN E_NOT_FOUND E_PARSE E_SECRET E_VAULT_KEY{names: [vault names, 1 to 20], why: "<chore> / <step>"}: a runbook step's copy (Page:Deployment's 📋) → {values: {name: value}}, Cache-Control: no-store; the Architect's own session only (any token, his AI's and the demigod's too, and anyone else E_FORBIDDEN); all or nothing: a name not in the vault E_NOT_FOUND {name, fix: add}, a key E_VAULT_KEY {name, fix: secret}; each opening a read listed on the secret, the call one audit record A_VAULT_COPY with the names and why, never a value; 30 a minuteGET /api/v2/vault/<name> vault E_SECRET E_SECRET_SUSPENDEDan AI agent, with an allowed token: {value}; E_SECRET otherwise, E_SECRET_SUSPENDED while its person has suspended it; 30 a minute (MCP vault_get)GET /api/v2/agents agents E_FORBIDDEN E_LEVEL E_METHODmy board {me, agents} and other people's agents on the project; an agent under a stop it hasn't acknowledged carries stopPending {id, from, at, readAt?} (readAt is set by that agent's own read of the board, never the sender's) and the last acknowledged one as stopAckedGET /api/v2/agents/totals agents E_FORBIDDEN{projects, agents, byState, messages, history}: the agents counted over every project and person; the Architect only (a demigod token E_SCOPE)POST /api/v2/agents/status agents E_BAD_JSON E_EXISTS E_FROM E_HOLDING E_LEVEL E_PHASE E_QUOTAaskedBySigned?: the signed word of your person's you acted on (P-2093) marks it used, {by, at} on its message (the answer's used: its id); one that doesn't check, or that never reached you, is a hint and never fails the status. an agent token's own status is checked (DesignSkillChecks#g2): its first is up (else E_PHASE {agent, state}); context (0 to 100, or unknown on handing-over) and every on every one, every within its role's range, agents.every.<role> in Settings (a coder ≥1800, in a chat ≤3600), timing on a coder's or dispatcher's done, runner on a coder's first up, a handing-over's item its open handover (else E_ARG {field, got, want}); a done while holding items in progress is E_HOLDING {state, items}; read.since while a chat, an ask or a back heads its next: the status is posted and only the read is refused — no messages, and warnings carries {code: E_ORDER, message: …answer <kind> <id> first, details: {next: {kind, id}, batch}} (P-1660); hints in warnings: N_NO_HANDOVER, N_NO_RESOLVE {lane, started}. {agent, role, state (up, idle, working, merging, waiting, done, stuck, gone), text, item, feature, every, waitingOn, sentTo, receivedFrom, changed, context, for (a dispatcher: role dispatcher), startedBy, timing (a run's minutes per stage: launch, reading, setup, build, tests, merge, deploy, boxTests, docs, checkpoint; start is the retired name for launch + reading and is refused beside either), partial (true: this run was cut short, so its timing is a floor and not a rate — only with timing), waitingFor: prompt|item (with state waiting only), ack: <the pending stop's id> | true (clears it; nothing pending is no error — the answer says so in hint), follows: <the name of the agent whose place you take> (any role, an agent of yours whose token has ended or ends within the hour: d2 marks its row done and moves its for roles, who started it, its pending stop, the items it held and its live chats to you; the answer says so in followed. A row your mint already closed with follows answers followed with no items. More than an hour left is E_ARG — a dispatcher or the monitor renews instead, keeping its name), step?, reason?, chat?, detail?, phase?, holding?, runs?, capacity?, read?, runner?, stopPending?}; text is at most 2000 characters; role is the token's when the post says none, and the monitor token's statuses are always role monitor; stuck notifies the person; a second dispatcher for a role is E_EXISTS; d2 shows dispatching while its agents work; the monitor token may post gone (only gone) for another of your agents, which releases that agent's items and keeps its own rolePOST /api/v2/agents/<name>/dead agents E_ALIVE E_NOT_FOUND{reason, askedBy?, salvage?}: its starter, its person or the monitor declares a silent agent dead (and a live dispatcher of the same person one a dispatcher that has ended started, after the same wait, never with process-gone (E_SCOPE); that also revokes its token, audited and told to the monitor; an agent that never posted is found by its start record) — once its last call is older than agents.deadAfter (1800 s), or at once with reason process-gone; else 409 E_ALIVE. At once, no wait: the monitor citing askedBy (a person's handle, then their words or message id), on any agent of its person's board; its person or the Architect on their own session (the Architect on any board; ?owner= names it). The row goes gone (deadBy, askedBy), its items back to its role (new); its person's or the Architect's own declaration, on their session, also revokes its agent token (revoked: true), the monitor's and a starter's keep it (a post brings it back)GET /api/v2/agents/next?wait=<s> agentsan agent's long-poll (its agent token, or D2-Agent): what waits on it, as one list in the order to work it — 200 {ok, items: [{kind: chat|ask|back|thought|mail|item, id, href, at, from, text (300 characters), chat?, thought?, urgent?, size?, skipped?, again?, waits?, claimedBy?}] (claimedBy: a chat message or a thought another agent claimed, listed and never handed, ^ui-chat-33, ^thoughts-ui-65; an ask: a chat asked of its role reaches every live agent of the role someone started and every one that loops on next, chatting elsewhere or not, until one joins; a coder ticket another agent filed in the designer lane for its person is never listed, P-1766) (an all-my-projects agent token, not a support grant's: its person's chats and asks in every other project of theirs its token reaches join the list too, each chat and ask entry, and each of chats, with project, the other projects' href on that project's own host, answered there; a person's chat asked or written in one of them wakes its wait; items, mail and thoughts stay this project's; chat stays this project's, ^chat-21) (urgent first, then chat with the thoughts sent to it in time order, ask, back, mail, item; oldest first in a band, items in pick order), batch: [the head and what belongs with it], chats: [{id, unseen}], warnings?, and, for a release, pickable: [ids], mail: the unread count, chat: an asked chat's id or null} as soon as anything unskipped is there, 204 when wait runs out; the person's chat messages and thoughts are handed at most chat.batch (3) at a time across its chats (^ui-chat-32): those handed before and not answered first, again: true, then new ones, the rest listed after them with waits: true; its own token's call picks up the new chat messages it hands (seen); all of the batch handed before and none answered: the same again with warnings: [{code: W_NOT_ANSWERED, details: {open: [ids]}}], never a refusal or a skip; a thought is answered when the agent saves it with a state; nothing else is marked by reading; d2 holds it at most 10 s (wait 11 to 25 is held to 10), and only while its person was active in one of its live chats in the last 10 s, else it answers at once; wait is optional: missing or 0 answers at once (200 or 204); a call with the agent's own token keeps its row alive (its at, its state and every kept), as a status post still does and the chat's line says listening; a board message, a pipeline save, a chat ask, the person's chat message or a thought changed where they are with Meld on wakes it; wait over 25 or any other parameter is E_ARGPOST /api/v2/agents/<name>/resume agents E_NOT_FOUND E_STATUSa paused agent back to what it was doing (working if nothing): its starter or its person (else E_SCOPE); E_STATUS if it isn't paused. The agent is told on the board → {agent, state, resumedBy}POST /api/v2/agents/next?wait=<s> agents E_BAD_JSON E_ORDERreport and ask in one call, with the agent's own token (any other is E_SCOPE): status posted as POST /api/v2/agents/status, done (the ids of your person's chat messages, picked up) marked done, seen (a message id) marked seen up to it, then the answer GET next would give; every part is checked first and a bad one refuses the whole call (E_ARG naming it, or the status's own refusal), nothing writtenPOST /api/v2/agents/next/skip agents E_BAD_JSON E_NOT_FOUNDan agent, with its own token, steps past one entry of its next: why (at most 200 characters) is a notice on the board and, for a chat message, a line in that chat; the entry comes back at the head after 30 minutes, or when it changes → {skipped, items, batch}; while a batch is unhandled, taking an item past it, marking mail read past a chat, an ask or a back (urgent mail is handled by reading it), or done on another chat is 409 E_ORDER {batch} (a take for another agent, a handover and an item the agent's start named pass)POST /api/v2/agents/<name>/end agents E_NOT_FOUND E_STATUSswitch an agent off, at once: its person on their own session, the Architect on any person's board (?owner= names it), or its starter chain (else E_SCOPE); its agent token is revoked, the row goes gone (ended by POST /api/v2/agents/alert agents E_BAD_JSON{kind: spend|token|crash|down|other, text (at most 500), agent? (the token's own name when left out)}: a keeper tells its person something failed → 201 {ok, flagged: true, mailed, repeat?, hint?, alert}: an alert in the Chatbox, the red Harness: <kind> flag in the person's top bar (to /page/Agents) until they dismiss it or that agent posts up, and a mail to the person for down and spend only, when the Mailer is on, within the project's emailsDay (mailed: false and why/hint saying why otherwise: not a mailed kind for crash, token and other); the same kind from the same agent within the hour only updates the flag (repeat: true, no mail or message)DELETE /api/v2/agents/alert?agent=&kind=&why= agentsthe person dismisses their Harness alerts: all of them, one agent's, or one kind; an agent's own token clears only its own (another agent named: E_SCOPE); why (query or a {why} body) is kept with who and when on the cleared record → {ok, cleared}. An agent's up or done clears its alerts too, and a status with fixed: <kind> clears its role's alerts of that kindGET /api/v2/agents/alerts agentsthe person's own session: what their top-bar flags count, for the Agents page → {alerts: [{agent, kind, text, at, since, mailed?, count?, old?}], cleared: [the newest 10, with clearedBy, clearedAt, why?], elsewhere: [{project, href, agent, kind, text, at}: their alerts and agents waiting on them in other projects], monitorAsks?: [{at, why, agent, href}] (the Architect's)}; old: its agent has gone or finished; kind outbound (^http-13) is d2's own, on the Architect's base board, count its trips in the hour. A token is E_SCOPEGET /api/v2/agents/messages?for=<agent>&to=&since=&limit=&all=1 agents E_PHASEthe agent's messages: to it, its role, or all (same person and project), each with its id (the store's, 24 hex); for (or agent, the same parameter) is whose inbox, to=<agent | role:<r> | all> just that addressee, since= an ISO stamp or a message id, limit= the newest N (from 1; more than api.list.max, 200, is 200, capped: true); no limit, a page of api.list.default (100): after since= the oldest, with next (the last id given) to send as since= or cursor= for the rest, else the newest, with next (`~` and the oldest id given, sent back as after= or cursor=) for the older ones, until none is left; older: true is the same, kept one release; total and totalCount: every match, count: the page's rows; answered ones only with all=1; the Report list: q= a case-insensitive contains (never a pattern), from= one sender, sort=newest|oldest|sender|item, a page at a time {total, limit, capped?, next?}, next sent back as cursor= with the same query; an agent's reads are msg.agent.readsPerMinute (E_QUOTA); without for, all of yours; any other parameter is E_ARGPOST /api/v2/agents/messages agents E_EXISTS E_FORBIDDEN E_FROM E_PAUSED E_PINGPONG E_QUOTA E_SIGNED E_STAMP E_STOP_PENDING E_TOanswer where you were asked (P-1702): an agent's answer here to its person's open chat or thought request (replyTo a chat message or the thought's mail, or naming the thought: <handle>-<n>, Thought:…, /thoughts/…) is 400 E_ARG {field: text, channel: <chat id | thought>} answer where you were asked: <link>; open while unanswered, being answered, or answered in the last thoughts.answerQuiet minutes (30); notices, another agent and the person are free. stamp?: a signed forward (P-1666): the stamp of the person's own chat line forward to <who>[ as <act>]: <text> (30 min, once), with to that recipient and text byte-identical → the message reads from: user:<person>, via: <agent>, verified {person, chat, msg, at, act} and, with D2_ENC_SECRET, sig; else 409 E_STAMP {cause: unknown | used | expired | recipient | text}, nothing spent; from: user:… , via, verified or sig sent by an agent are dropped. askedBySigned?: the person's signed word forwarded (P-2093), with words (their message verbatim), message? and intents? (the ids d2 read from it): checked as POST /api/v2/signed/verify checks it, and by a sender in its chat or one a verified forward reached, → the message keeps signed {person, chat, message, at, until, words, intents?}, d2's alone; it is the person writing, so never a round trip and its pairs start over; else 409 E_SIGNED {cause: unknown | project | person | expired | text | message | intent | agent}. text at most 4000 characters, or 20000 with askedBy naming your own person (^agents-63; else E_ARG naming both); two round trips, then the person (^pipe-87): between two background agents that are not starter and started, the 3rd answer since their person last wrote to either is 409 E_PINGPONG {count, pair, thought, see}; both get a d2 stop message, the person a Stuck thought, role:monitor a notice, and A_PINGPONG is audited; notices never count. {from, to: agent | role:<r> | all, text, item?, replyTo?: <id>, timing?: a run's minutes per stage (launch, reading, setup, build, tests, merge, deploy, boxTests, docs, checkpoint; start kept), partial?: true (the run was cut short: a floor, not a rate), stop?: true} → {message: {id}}; a reply sets answeredBy on the one it answers (E_ARG if unknown or answered); E_SCOPE across people; E_TO for a target that doesn't exist on the project or a lane the message routes refuse ({reason: unknown | off | reply-only, lane, mode, decidedBy}); 429 E_QUOTA over a msg.* quota ({quota, limit, used, retryAfter}); askedBy: the Architect's handle passes a closed lane, marked bypass (5 a day); information only. stop: true goes to one agent by name and also sets stopPending {id, from, at} on its row, pending until that agent acknowledges it (a status with ack, or a replyTo to the stop); only the Architect, the designer or the agent's starter may send one (else E_SCOPE), and an agent with no row is E_ARGPOST /api/v2/agents/messages/clear agents E_ARCHIVE{to?}: a support or demigod token, or an admin in their session, moves the project's messages (or only those to one target) to the log, as the daily sweep does → {cleared, to?}; anyone else E_SCOPE. Audited A_BOX_CLEARGET /api/v2/audit?code=A_PINGPONG agents E_FORBIDDEN[&limit=, 50, at most 100 (api.list.max): more is 100, capped: true] the project's ping-pong stops (^pipe-87), items and mail, newest first: {total, limit, capped?, data: [{at, code, severity, project, message, record: {kind, item | pair, ids, at, stoppedBy, thought?}}]}; only that code, only this project; a moderator or above in their own session, or the monitor's token. An agent token of one of d2's own roles (designer, coder, tester, dispatcher, monitor, ops, guardian, conductor) whose person is the Architect, on a project he owns, reads the whole log here instead, as GET /api/v2/razadmin/audit answers it (its filters; only the records of the projects he owns; project= another is E_SCOPE; his demigod and guardian agents every project's, on any project, P-2420), each read audited A_AUDIT_READ (^audit-1, P-2101); any other token E_SCOPE, and any call but a GET with a token E_SCOPE (^audit-4). Kept 30 days (audit-keep). The /agents page lists the last 20 under Ping-pong stopsGET|PUT /api/v2/agents/routes agents E_CONFLICT E_FORBIDDEN E_LEVEL E_VERSION_REQUIRED?fromRole=<role> (a member, or an AI token for its own role or a role of its person's agents on the board, else 403 E_SCOPE): the lanes from that role, resolved through the channel, chain and rules layers as a post meets them, {fromRole, lanes: {<role> | person | all: free | reply | off}, byAgent?: {<target>: true}} (byAgent: the chain decides it per agent; the answer is for the reader when it is an agent of that role, else for one its person started); &to=<role | person | all> narrows it to one, {fromRole, to, mode, decidedBy: channel | chain | rules | default, rule?, byAgent?}; an unknown role or target is 400 E_ARG {field: fromRole | to, want}; it only reads, no lane opens. Without fromRole, the project's whole message routes table (Design 53.1), for its admins only (else 403 E_FORBIDDEN): {layers: {channel, chain, rules}, channel: {"<from>.<to>": off | reply | free}, chain: {adjacent, skip, side, pins}, rules: [{from, to, mode}]} and its version (ETag); PUT the whole object with If-Match: <version> (E_CONFLICT if stale, 428 without), by the project's admins; Rules only by a Conductor (E_LEVEL); at least one layer on (E_ARG)GET /api/v2/agents/messages/<id>/verify?sig= agents E_ENC_KEY E_NOT_FOUNDa signed forward checked (P-1666): {valid, from, to, text, chat, msg, at, via, verified}; valid when its sig is the HMAC d2 makes of those fields (and the copy's ?sig= too); a message that isn't one {verified: false, valid: false}; 503 E_ENC_KEY with no D2_ENC_SECRETGET /api/v2/agents/routes/stats?days=3 agentsper-lane message counts over the last days (1-30), per-rule hits and which layer passed eachGET /api/v2/agents/quotas agentsthe msg.* quotas from base Settings:Quotas, each {limit, used, note}; used is the calling agent's (D2-Agent) and the project'sGET /api/v2/agents/changes?since= agentsthe Memory: and Skill: topics changed since thenGET /api/v2/skills?role=&action=&feature=&code=&concept=&search=&what=&process=1&setup= core E_FORBIDDEN E_NOT_FOUNDthe d3 skills (also at /api/v2/skills/d3): a role's start as text/markdown, ending with the project's process cut for the role, or one pull; &process=1 re-reads the project's Skill:Project (else base's) alone, cut by the role asked (its head, untagged sections and those tagged for it; whole with no role), any token that reads skills, a monitor's included; &setup=solo|full|apploop a setup skill, unfilled; the rest as /api/v2/skills/d3; /d2-connect.skill is the starter to install onceGET /api/v2/skills/sizes core E_FORBIDDEN?days=7|30 (the Architect's session or his demigod agent, else E_SCOPE; other days E_ARG): the sizes a day, one row a day per project and role written the first time sizes are read that day (not by a job), kept 30 days: {ok, project, level, days, data: [{day, roles: {role: {size, budget, total, cut}}}]}, oldest first; ?parts=1 (the same readers; &since= an ISO time, default 7 days, for the pull log): each role's start in parts: {ok, project, level, since, roles: {role: {size, budget, parts: [{part, kind: role|feature|extras|process, chars, sharedBy: [roles], dupes: {count, first}, pulls: {ask, search}}]}}}, largest first, dupes the lines of 24 characters or more found word for word in another part of that start; days with parts is E_ARG. Without them: each d3 role's start in characters, against the budget, with what was cut to fit, the size of the process block it carries, cut for the role (null: none), and the total served: {ok, project, level, sizes: {role: {size, budget, cut, process, total}}}GET /api/v2/skills/d3?role= core E_FORBIDDEN E_NOT_FOUNDthe d3 skills, the same handler as /api/v2/skills: the compiler for the published topics tagged d3skill (d3-f-<feature>, d3-r-<role>, d3-r-<role>-<project>, d3-process): a role's start as text/markdown, cut to skills.d3.budget; one pull with &action=a,b &code=E_X &concept= &feature= &what=rules|errors|debug &search=; with a Skill:Project (the project's own, else base's) the start ends with it, cut for the role, outside the budget, and &action=process (or &feature=process) answers it alone; &format=json: {ok, role, size, budget, cut, blocks, own?}; E_NOT_FOUND names the roles or the nearest names; a role token reads its own role's start only (no pull), logged as role:<role>GET /api/v2/skills/d3/log?since=&role=&summary=1 core E_FORBIDDENthe pull log, newest first, 500 at most: {ok, total, rows: [{at, project, role, agent, ask, blocks, why, size}]}, size the characters returned; summary=1: the sizes instead of the rows, {ok, roles: [{role, why, calls, min, avg, max}], agents: [{agent, role, calls, chars}]}; a designer's or admin's session or token, else 403 E_SCOPEGET /api/v2/agents/history agents E_MOVEDretired: 410 E_MOVED naming the Chatbox query (the old agent history topic is gone)GET /api/v2/esp/here esp E_ESP_OFF E_NOT_FOUNDyour AI token: the page and section your person's ESP tab shows (the one focused last), as {on: true, url, section, …, level}; no tab of theirs with Meld on is 200 {on: false, level, message} (P-2680: off is an answer, not a 404); 404 E_ESP_OFF (with reason: "ai-forbidden page", oldReason: "private page", while that tab is on a security page: the vault, tokens, permissions, the monitor's channel, Raz Admin's raw data…; reason: "members only" while it is on base d2 or d2spec and your person isn't a member there, ^esp-12, as for a go and a look; reason: "hidden by you" while it is on a page your person hid from their agents, P-1649)GET /api/v2/agents/log?agent=|role=&limit=&cursor= agentsone agent's or one role's log as rows, newest first: its Chatbox messages and alerts, and its current status; {total, limit, capped?, next?, data}; limit, cursor; capped, next: 50 unless limit= says, at most 100 (api.list.max; more is 100, capped: true; a limit that isn't a whole number from 1 E_ARG); next, sent back as cursor= with the same query, the page afterPOST /api/v2/esp/go esp E_BAD_JSON E_ESP_OFF E_TOO_BIG{url, section?, why (≤120)}: take your person's ESP tab there; agents.esp decides (person E_SCOPE; asks: they tap Go; tells, free: it moves; to a security page it always asks) → {id, tab, mode}; E_ARG (not a d2 page they can read), E_ESP_OFF, E_RATE (one go per tab per 3 s)GET /api/v2/agents/starts?since=&reason=&by=&agent=&limit=&cursor= agentsyour agents' starts of the last week, newest first, a page at a time (limit, cursor; capped, next: 100 unless limit= says, at most 100, api.list.max, more is 100 with capped: true; next, the last id of the page, sent back as cursor= the page after) {total, limit, capped?, data: [{id, at, agent, role, kind, startedBy, person, mintedBy, reason?, ref?, promptHash?, promptChars?, envNames?}], next?, prompts, keptDays}: one per agent-token mint (POST /api/v2/tokens/agent takes reason: order | handover | rotation | auto-restart | backoff | ops | chat, ref ≤ 80, prompt ≤ 64 KB, startedBy, env names); people and the monitor, any other token E_SCOPE; HTML twin /agents/startsGET /api/v2/agents/messages?all=1&kind=&from=&to=&item=&q=&since=&until=&limit=&cursor=&format= agentsthe Chatbox query: messages and alerts (kind message|alert), from an agent, person, role:<r> or coder-*, newest first, {total, data, archives, limit, capped?, cursor?, next?}: 100 unless limit= says, at most 100 (more is 100, capped: true); next (and cursor, the same) sent back as cursor= gives the next page (the last entry's id; with q=, a cursor that carries it: sent back with no q it pages the same matches, with another q it is E_ARG); format=jsonl downloads it; HTML twin /agents/chatboxGET /api/v2/esp/go/<id> esp E_NOT_FOUND{state: pending|went|declined|expired}POST /api/v2/esp/look esp E_ESP_OFF{why (≤120)}: see the page your person's ESP tab shows, once; agents.esp decides (person E_SCOPE; asks: they tap Share; tells: it answers and says so; free: quietly) → {id, tab, mode}; the tab has 30 s; E_ESP_OFF (no ESP tab)GET /api/v2/esp/look/<id> esp E_NOT_FOUND{state: pending|done|declined|expired, page?: {url, title, state, text, errors, at, layout?}, reason?}: state is the page's window.d2state(), text its visible text (≤8 KB) without passwords, form fields, opened encrypted blocks or anything marked data-private, errors its last 20; kept 2 minutes; no screenshot; from a security page, or a Thoughts page of a person who keeps a sealed (#encrypted) thought, declined with reason "ai-forbidden page" (oldReason "private page"), message "not shared on this page" and nothing kept (^esp-11: the vault, tokens and every Raz Admin page among them); an encrypted topic its writer reads open goes as its locked form; layout: the view (w, h, scrollY, docH…), what is open over it and up to 60 blocks in view, and on an App or Page in a frame layout.frame, the frame's own (its view and scroll, its blocks in view, its box on the page; ^esp-9)GET /api/v2/esp/look/<id>/picture esp E_NOT_FOUND E_STATEa look asked with picture: true: waits ≤30 s for your person's tap on Send picture → 200 image/webp (or image/jpeg) bytes, passed through and never kept; ✕ or no tap → 200 {picture: declined}; one waiting at a time (E_STATE)GET /api/v2/agents/starts/prompt/ agents E_NOT_FOUND{hash, chars, text}: the prompt one of your kept starts was given, stored once per hashGET /api/v2/agents/chatbox/archives chatsthe chatbox archive files (_archive/chatbox-<from>--<to>.jsonl.gz), newest first: path, from, to, count, size, a signed members urlPOST /api/v2/esp/here esp E_NOT_FOUND E_ESP_OFFa tab with ESP on (the person's session): {tab, url, section?, title?, focused?, claim?} or, on a security page, {tab, private: true, focused?} and nothing of the page (claim: the person opened the chat here, so this is their ESP tab now and every other tab of theirs drops Meld at its next stamp check); GET /api/v2/esp/here in the session answers the same as an AI's; POST /api/v2/esp/gone {tab} when it goes off or closes; GET /api/v2/esp/stamp?tab= its poll; POST /api/v2/esp/go/<id> {state: went|declined} its answer. Meld is the person's (^esp-10): a signed-in person's session with no role here, on a project they can see (one open to anyone), may post here and gone, poll the stamp and answer a go or a look too; on base d2 and d2spec only a member may (^esp-12): anyone else 403 E_ESP_OFF {reason: "members only"} and the tab's presence goesPOST /api/v2/esp/gone {tab} esp E_ESP_OFFthe tab's session (any token E_SCOPE): this tab went off or closed, so it is no longer where an AI takes its person; a beacon's text/plain body is read too; a signed-in guest's session too → {}; a non-member on base d2 or d2spec 403 E_ESP_OFF (^esp-12)GET /api/v2/esp/stamp?tab=&focusedAt=&espTab= esp E_ESP_OFF{stamp, target, go?: {…, href}}: the tab's poll, in its session (any token E_SCOPE): the pending go for it (stamp is its id, "" for none), and whether it is the tab an AI would take; a non-member on base d2 or d2spec 403 E_ESP_OFF (^esp-12)POST /api/v2/esp/go/<id> {state} esp E_NOT_FOUND E_ESP_OFF{state: went|declined}: the tab answers a go, in its session (any token E_SCOPE) → {id, state}; went is written to the history as the agent taking its person there; a guest's session too, on a project they can see (^esp-10); a non-member on base d2 or d2spec 403 E_ESP_OFF (^esp-12)POST /api/v2/esp/look/<id> esp E_NOT_FOUND E_ESP_OFFthe tab answers a look, once, in its session (any token E_SCOPE): {url, title, state, text, errors} (≤32 KB) or {state: declined}; a second answer or a late one E_ARG; a guest's session too, on a project they can see (^esp-10); a non-member on base d2 or d2spec 403 E_ESP_OFF (^esp-12)POST /api/v2/esp/look/<id>/picture esp E_NOT_FOUND E_STATE E_ESP_OFFthe tab, in its session, on the person's tap (any token E_SCOPE): image/webp or image/jpeg ≤100 KB (E_ARG above), piped into the agent's waiting GET; no agent waiting → 409 E_STATE; {state: declined} (JSON) says no; only the meta line is recordedPOST /api/v2/esp/on esp E_ESP_OFF{why?}: turn ESP (Mind Meld) on in your person's ESP tab (the open tab that last had it on, else the one used last); agents.espOn decides — free and seen: it turns on (mode on); anything else: a bar to tap (mode bar), 60 s → {id, mode}; read the outcome at GET /api/v2/esp/go/<id>; E_ESP_OFF (no visible tab), E_RATE (one per person per 30 s)POST /api/v2/esp/off espstop this agent following; your person's level and tabs stay; always allowed → {ok, stopped, ids, tabs}; your esp/here, esp/go and esp/look then answer E_ESP_OFF until your next esp/on goes through or your person picks a levelGET|PUT /api/v2/me/meld esp E_LOCKED E_ESP_OFFa guest's session may PUT it on a project they can see (^esp-10), a non-member on base d2 or d2spec not (403 E_ESP_OFF, ^esp-12); the person's Meld level, on the account (every tab and device): GET → {level, hidden: [paths], locked: [levels], quiet?, need?, activated: [levels], boxes: {quiet, nudge, active, locked}, hints: {off, quiet, nudge, active}} (Help:Meld's level-* and hint-* lines); PUT {hide: <path>} | {unhide: <path>} → {level, hidden}: a page hidden from the person's own agents (exactly that path, query and hash ignored; at most 200; a security page E_ARG): their agents' here, look and go answer 404 E_ESP_OFF {reason: "hidden by you"}, a look's answer from it is declined, a go to it asks (P-1649); PUT {level: off|quiet|nudge|active} → {level}, a locked one (a new account's Nudge and Active, until 10 Chat uses) 409 E_LOCKED {quiet, need}; the person's session only (any token E_SCOPE); Off turns Meld off in every tab (each at its next stamp check) and stops the chats' Meld moved lines; the stamp and GET /api/v2/esp/here carry levelPOST /api/v2/account/seen esp E_ESP_OFF{what: esp}, the person's session: Mind Meld's first-tap help seen, kept on the account (every device); a guest's session (no role here, a project they can see) too (^esp-10); a non-member on base d2 or d2spec 403 E_ESP_OFF (^esp-12); devsync is retired (E_ARG)GET /api/v2/guardians guardianseach guardian: its watch, state, last run, summary line and open findings (master: the four watches)GET /api/v2/guardians/err-see guardiansGUARD-ERR-SEE: every error reply's see and help pointers, ok false with failures naming the code and the missing topic or section on base d2POST /api/v2/guardians/probe guardians E_LEVEL{note}: the security guardian asks before probing (guardians.probe: person E_SCOPE, asks 202 approval, tells runs)GET /api/v2/guardians/askedby?since= guardiansa guardian agent token on its own project, or the project's admins (else E_SCOPE) → {uses: [{agent, role, person, action, target, at}], since, through}: every call that passed because of askedBy (audit A_ASKED_BY), oldest first, at most 500 a call; through is the next sincePOST /api/v2/reports/<Report:topic>/send guardians E_EXISTS E_FORBIDDEN E_NOT_FOUND{finding: f1, line: 1, title?, doc?}: a person ticking a suggested item (the checkbox is the button): makes it (links.report) and marks the line - [x] … → P-n; an AI token is E_SCOPEPOST /api/v2/reports/<Report:topic>/send-all guardians E_FORBIDDEN E_NOT_FOUNDa person's Send all at the bottom of a report (a guardian's or a tester's): every suggestion not sent yet, in report order, made as /send makes it and its line marked - [x] … → P-n, the report saved once → {items: [{finding, line, id}], skipped: [{finding, line, code}]}; nothing left → no items; an AI token is E_SCOPEPOST /api/v2/pipeline/<id>/answer pipe E_CLOSED E_NOT_FOUND E_NO_SECTION E_SECTION_CHANGED{text} from the person it waits on (P-1697): the answer goes on the item and it goes back to its holder (in progress) or new in its lane, as /return {note}; {doc} with only its ## Questions ticks changed is an answer too (^ui-esp-30); at Meld Active the answer also goes into the person's chat with its role (^ui-esp-29). Otherwise: {answer: approve | refuse, note?, section?, accept?, comment?, text?, all?, send?}: a person answers an approval item. On a kind: batch item it answers a review batch instead (^pipe-59): {section, ver, accept: true} | {section, ver, comment}, {all: true, ver} for every section still open, {text, ver} for one comment on the whole batch, {send: true} to pass on what is waiting. The person's, or their AI with askedBy; E_SECTION_CHANGED when the section has changed since, E_NO_SECTION when there is no such sectionPOST /api/v2/ws agents E_BUSY E_HTTP E_METHOD E_NOT_FOUND E_NOT_YOURS E_PERM E_STATE E_STATUS E_SECRET{item}: a workspace for the item your agent token holds (open, yours): the project's repo (Settings:Process workspace.repo) cloned on a build box at your branch razwip-<agent> (from workspace.base, razwip, when it doesn't exist yet) → {ok, id, branch, from, until}; one per agent; your role's ws.use row off (person) E_PERM; not an agent token E_SCOPE; another's item E_NOT_YOURS; a closed one E_STATE; no repo set 409 E_STATUS; its deploy key (vault repo-<name>) not a secret d2 can open E_SECRET; a GitHub https repo clones and pushes with an installation token of the project's GitHub connection, minted per call (P-2516): disconnected, not one of its repos, or the app removed 409 E_STATUS; no free slot 409 E_BUSY {waitingFor: no build slot | starting a build box (the build pool made one, P-2518)} with Retry-After: post waiting with it and ask again; it ends after workspace.maxMinutes (60)GET /api/v2/ws/<id> agents E_BUSY E_HTTP E_METHOD E_NOT_FOUND E_NOT_YOURS E_PERM E_STATE E_STATUSyour workspace: {ok, id, item, branch, box, until}; another's E_NOT_FOUNDPOST /api/v2/ws/<id>/<tool> agents E_BUSY E_HTTP E_METHOD E_NOT_FOUND E_NOT_YOURS E_PERM E_STATE E_STATUSone tool in your workspace: read {path} (a file's text, or a folder's names), write {path, text} (the whole file), search {query, path?} (lines, 200 at most), run {cmd} (one of the project's workspace.commands, in the sandbox, never on the box: {ok, code, out} cut to 20 KB), diff (status and diff), commit {message} (names the item's P-n, else E_ARG) → {sha}, push (to razwip-<agent> only; a GitHub repo of a disconnected project 409 E_STATUS) → {branch, sha}; a path outside the work tree or in .git E_ARG; another command E_SCOPE; its time up E_STATEDELETE /api/v2/ws/<id> agents E_BUSY E_HTTP E_METHOD E_NOT_FOUND E_NOT_YOURS E_PERM E_STATE E_STATUSyour workspace ends: its folder wiped on the box, its slot freePOST /api/v2/ws/join agents E_METHODa build box's runner (d2-ws) joins: {code, url} with the one-time code d2 made for it (10 minutes, once) → {box, key}, its runner key, which d2 sends on every call to it; a wrong, used or late code 401 E_AUTH; a build pool box (P-2518) only from its droplet's own address, its url on its own address, else 401 E_AUTH and its code not spentGET /api/v2/ws/boxes agentsthe joined build boxes, the Architect's own session only (else E_SCOPE): {boxes: [{name, url, joined, live, slots}], pool: {size, region, max, slots, idleMinutes, monthMax, spent, boxes: [{name, droplet, state: creating|joining|ready, ip, live, created, idleSince, monthly, costSoFar}]}} (P-2518: the build pool's boxes and this month's spend against build.pool.monthMax)POST /api/v2/ws/boxes agents E_METHOD{box}: a one-time join code for a build box made by hand (the pool makes its own), the Architect's own session only (else E_SCOPE) → {code, box, expires} (10 minutes, once)GET /api/v2/monitor/messages?since= agents E_METHOD E_NO_KEY{total, data}: the god-monitor channel, the monitor's side, read with the monitor's token only (anyone else E_SCOPE); ?since= an ISO time (else E_ARG); the Architect's side is the Monitor pageGET /api/v2/github agents E_HTTP E_METHOD E_NOT_FOUND E_STATE E_STATUSthe project's GitHub connection, for a member or the project's agent token: {ok, app (the d2 GitHub App is set up), connected, account, accountType, by, at, repos: [{id, name, full, private}], admin (you may change it)}; never a tokenGET /api/v2/agents/hosted agents E_FORBIDDEN E_METHODthe roles d2 runs on this project (P-2512), a member in their own session (a token E_SCOPE) → {ok, level, conductor, owner, roles: [{role, hosted, model, agents: [{agent, chat, running, context}], today: {cost, runs, calls}}], coders, dayMax, today, paused}: each role's switch (d2 or your AI) and model, its live hosted agents, and what it spent today (US$, from the owner's credits): runs its runs (one per answer), calls its model callsPOST /api/v2/monitor/messages agents E_BAD_JSON E_METHOD E_NO_KEY E_NOT_FOUND{text, event?}: the monitor writes to the Architect on its channel, with the monitor's token only (anyone else E_SCOPE) → 201 {ok, message}POST /api/v2/github/connect agents E_HTTP E_METHOD E_NOT_FOUND E_STATE E_STATUSConnect GitHub: {url}, the d2 GitHub App's install page on GitHub (choose only select repositories), with a one-time state (10 minutes) that brings you back to /github/callback; the project's admin in their own session (else E_SCOPE); the app not set up on this d2 409 E_STATUSPUT /api/v2/agents/hosted agents E_LEVEL E_METHOD E_FORBIDDEN{role, on, model?}: who runs a role, d2 (on, a smart agent over OpenRouter on model, Kimi K3 by default, the tester's story.agentModel) or your own AI (off); the project's owner in their own session only (a token E_SCOPE, anyone else E_FORBIDDEN), on a Conductor (master) project only (else 403 E_LEVEL naming Conductor); the monitor isn't hosted (E_ARG) → {ok, roles} as GET; it is the agents.hosted line of Settings:ProjectGET /api/v2/monitor/agents agents E_METHOD{ok, project, total, role?, data}: what d2 knows about each agent on the project (^agtok-14), the monitor's token only (anyone else E_SCOPE): state, label, lastSeen, phase, step, stopPending, context, holding (ids and statuses only, never a title or a document), startedBy, mintedBy, expires, prompt; ?role= ?state= ?all= narrow as the board doesPOST /api/v2/github/sync agents E_HTTP E_METHOD E_NOT_FOUND E_STATE E_STATUSthe installation's repos read again from GitHub (after a change there) → the connection; not connected 409 E_STATUSPOST /api/v2/agents/hosted/start agents E_LEVEL E_METHOD E_FORBIDDEN E_STATEStart my team: the hosted designer and dispatcher start at once (coder and tester start when they have work), the owner in their own session on a Conductor project; a role with nothing waiting (no pickable item, mail, asked chat or waiting release) runs no model: d2 posts it up and its ready line in the owner's chat, and its first work wakes it there; no role hosted, or the day's budget reached, E_STATE → {ok, started: [role]}GET /api/v2/monitor/agents/<name> agents E_METHOD E_NOT_FOUND{ok, agent}: one agent's row of GET /api/v2/monitor/agents, the monitor's token only (anyone else E_SCOPE); none on the project E_NOT_FOUNDPOST /api/v2/github/repos agents E_HTTP E_METHOD E_NOT_FOUND E_STATE E_STATUS E_EXISTS{name, private? (true)}: New repo on the connected account (auto-initialised), added to the d2 app's installation → {ok, repo: {id, name, full, private}}; a name GitHub refuses (taken) 409 E_EXISTSGET /api/v2/monitor/roles/<role>?since= agents E_METHOD E_NOT_FOUNDwhat a role has to act on (^agtok-16, MON-25), the monitor's token only (anyone else E_SCOPE): ?since= required (an ISO time, or the id of the last message counted; none E_ARG), the caller keeps the cursor; ids, stamps and counts, never a title, a document or a message's text; a role the project doesn't have E_NOT_FOUNDDELETE /api/v2/github agents E_HTTP E_METHOD E_NOT_FOUND E_STATE E_STATUSDisconnect: the project forgets its installation; every clone, push, pull request and merge stops at once (409 E_STATUS); the app stays on GitHub until its person removes it therePOST /api/v2/github/pulls agents E_HTTP E_METHOD E_NOT_FOUND E_STATE E_STATUS{repo, head, base? (main), title, body?}: the dispatcher opens a pull request on one of the project's repos → {ok, number, url, repo}; any other role or a session E_SCOPE; nothing to merge or one open already 409 E_STATEPUT /api/v2/github/pulls/<n>/merge agents E_HTTP E_METHOD E_NOT_FOUND E_STATE E_STATUS{repo, sha?, method? (merge | squash | rebase)}: the dispatcher merges a pull request, after its tests pass → {ok, sha, repo}; GitHub's refusal (main protected, checks failing, not mergeable) 409 E_STATE with its words; any other role E_SCOPEGET /api/v2/repoapps agents E_CREDITS E_HTTP E_METHOD E_NOT_FOUND E_QUOTA E_STATE E_STATUSthe project's repos' Node apps, for a member or the project's agent token: {ok, apps: [{repo, address, state (deploying | running | stopped | failed), stopped? (idle | credits), release, public, at, by, error?}], running, max (apps.running), hourFee (US$ an hour, apps.hourFee), box (apps1 has joined), admin}GET /api/v2/repoapps/<repo> agents E_CREDITS E_HTTP E_METHOD E_NOT_FOUND E_QUOTA E_STATE E_STATUSone app with its last 200 log lines from apps1 (install and run): {ok, app, logs}; no such app E_NOT_FOUNDPATCH /api/v2/repoapps/<repo> agents E_CREDITS E_HTTP E_METHOD E_NOT_FOUND E_QUOTA E_STATE E_STATUS{public}: Make public (anyone may open its address) or members only again; the project's admin in their own session (else E_SCOPE)POST /api/v2/repoapps/<repo>/deploy agents E_CREDITS E_HTTP E_METHOD E_NOT_FOUND E_QUOTA E_STATE E_STATUS{release, ref? (main)}: the dispatcher deploys a repo of the project's GitHub connection at a release: cloned at ref on apps1, its package.json's start script required (else 409 E_STATUS), npm ci --omit=dev, npm start in its own container, swapped in once it answers on / (the old one answers until then) → {ok, app}; a repo name that can't be an address (letters, digits, single dashes) 409 E_STATUS; past apps.running live apps 409 E_QUOTA; the owner's credits short of an hour 409 E_CREDITS; apps1 not joined 409 E_STATUS; any other role or a session E_SCOPEPOST /api/v2/repoapps/join agents E_METHODapps1's runner (d2-apps) joins: {code, url} with the one-time code d2 made for it (10 minutes, once) → {box, key}, its runner key; a wrong, used or late code 401 E_AUTHGET|POST /api/v2/repoapps/box agents E_METHODthe Architect's own session (else E_SCOPE): GET {box: {name, url, joined} | null}; POST {box} a one-time join code for apps1's runner → {code, box, expires} (10 minutes, once)POST /api/v2/tokens/agent tok E_DEMIGOD E_PLAN E_QUOTA E_STOP_PENDING E_TOKEN_SUSPENDED E_TOO_BIG{name, follows?, ttl?, role?, reason?, ref?, prompt?, env?, startedBy?} with your role token → 201 {token, agent, role, expires, mintedBy}: the name you are given is the one you asked for, or it with the next free number. The new token copies your person, role, level and scope; ttl (seconds) may only shorten it, and it never outlives your role token. An agent or tool token minting is E_SCOPE; at most 10 a minute. The Architect's demigod token mints too, with {name, role}: the role of the agent that read d2-demigod, one it was handed to (else E_ARG), → {…, demigod: true}; that agent acts as the Architect, a fixed few excepted (E_DEMIGOD). A dispatcher or the monitor renews instead of minting again (rank 15 below). follows (^agtok-17): any agent whose token has ended, or ends within the hour, mints again with follows: <its old name> — a row of the same person and role on this project's board; d2 closes the old name at once (gone, renewed as <new>, no alert), puts a row up for the new name saying whose place it took, moves its taken items (one note each) and live chats to the new name, and answers followed: {agent, items, chats}. Another role's, a name with no row, one already renewed as another agent, or one whose token has more than an hour left is E_ARG, and nothing is minted. Header D2-Started-By: Bearer <the starter's own agent token> (a live dispatcher or monitor of yours, any role; a live designer of yours, a tester only — a designer starting any other role is E_SCOPE): records startedBy on the new token, which lets that starter revoke it later even though a role token out of your vault is what minted it — any other kind, role, person or an ended one is E_ARG/E_SCOPEPOST /api/v2/tokens/agent/renew tok E_QUOTA E_STATE E_TOKEN_SUSPENDEDa dispatcher or the monitor only: your live agent token as the Bearer and your role token in D2-Role-Token → 201 {token, agent, role, expires, mintedBy, secrets, renewed: {was, now}}. The same name, a fresh lifetime from your role's row, your secrets re-sealed; the old token is revoked at once, so call it in your last sixth and use the new one from the next call on. Your board row, held items, for roles, stops and messages all stay, because the name is what they hang off. Any other role is E_SCOPE; an expired token is 401 E_AUTH — once it has ended nothing proves you are the same agent, so mint again and post follows: <your old name> on your first statusGET /api/v2/tokens/agents tok E_METHODthe agent tokens this token minted, the live ones first, each newest first, a page at a time: {ok, total, limit, capped?, next?, ended (how many have ended), agents: [{agent, role, created, expires, lastUsed, revoked, startedBy?, live}]} — never their values; archived=1: the ones archived 30 days after they ended (P-2499), {archived, agents: [{agent, role, created, lastUsed, ended, why, archived, startedBy?, live: false}]}; limit, cursor; capped, next: 100 unless limit= says, at most 100 (api.list.max; more is 100, capped: true); next, sent back as cursor=, the next page. An agent token lists none, because it minted noneDELETE /api/v2/tokens/agents/<agent> tok E_NOT_FOUNDa person's own session revokes their own agent's token, the Architect's any agent's of the project; another member's session E_SCOPE (^agents-71, P-2032) → 200 {revoked, by}. With a token: revoke a token by name: yourself (your own agent name, whatever your role — end your own run), one you minted, or one you started (its startedBy is your agent name — a dispatcher cleaning up a coder it minted with a role token out of the vault; the answer says started: true). Its next call is 401 E_AUTH, at once. Any other name you hold here is 403 E_SCOPE, saying what this token may revoke; a name nobody of yours holds here is 404 E_NOT_FOUND. Revoking a role token ends every agent it minted, at oncePOST /api/v2/tokens/agents/<agent>/suspend tok E_NOT_FOUND E_TOKEN_SUSPENDEDpause a token by name, the same ones DELETE may revoke (yourself, the agent tokens you minted, the agents you started) → {suspended, kind, self, already}: every call with it is refused 403 E_TOKEN_SUSPENDED until resumed; a paused role token's agents are paused with it. Nothing is re-minted, the expiry keeps running. /resume → {resumed, …}: the same token again. The person pauses any of theirs on the AI tokens page (Suspend beside Revoke). Both audited (A_TOKEN_SUSPEND, A_TOKEN_RESUME)POST /api/v2/tokens/agents/<agent>/resume tok E_NOT_FOUNDthe paused token by that name works again, the same ones /suspend may pause → {resumed, kind, self, already}; audited A_TOKEN_RESUMEPOST /api/v2/tokens/support support E_FORBIDDEN E_METHOD E_NOT_FOUND E_SUPPORT_LINE E_TOO_BIG{hours?: at most 8, default 2; support?: tell | free, default tell; name?}: an admin or the owner, in their own session (never a token), opens support access to this project → 201 {token, name, expires, support, scope: one-project, cap: mod, mayDestroy: false}, shown once. The token ignores this project's permissions for pipeline, docs and agents actions (tell: its person gets N_SUPPORT_ACTED for each write; free: only for consequential ones), never deletes the project or changes its owner, members or join (E_SUPPORT_LINE); every write audited A_SUPPORT_ACT; Suspend on the tokens page pauses itPOST /api/v2/tokens/support/link support E_FORBIDDEN E_GONE E_METHOD E_NOT_FOUND E_TOO_BIG{project, hours: 1|2|4|8, support?: tell | free}: Let a support AI in, from the Support page — an admin or the owner, in their own session (any token: E_SCOPE) → 201 {line, url, expires}: the same support access, its token reaching the AI by a one-time link /support/claim/<id> (GET says what it is; POST {name} → the support role token and its first agent token, once; then, or once the access ends or is revoked, E_GONE)POST /api/v2/tokens/demigod tok E_FORBIDDEN E_METHOD E_NOT_FOUND E_ORIGIN E_TOO_BIG{hours?: at most 24, default 2; support?: tell | free, default tell; name?, roles?}: the Architect only, in his own session → 201 {token, …, scope: all-owned, cap: admin, mayDestroy: true}, shown once. The same bypass as a support token over every project he owns and base d2, ops actions too (always told), and it may cross the hard line: DELETE /api/v2/project {name} with it deletes a project he owns. Anyone else E_FORBIDDEN; audited like supportGET /api/v2/tokens/demigod tok E_METHOD E_NOT_FOUND{live: false} or {live: true, name, roles, granted, readBy, expires, support, suspended, actions, activity}: the live demigod, the Architect only, in his own session (anyone else 404); ?all=1 → {data}: every one ever made, newest first, ended ones tooDELETE /api/v2/tokens/demigod tok E_METHOD E_NOT_FOUND E_ORIGINEnd now: the Architect only, in his own session, revokes the live demigod and deletes d2-demigod from his vault → {ended: its name, or null when there was none}GET /api/v2/tokens/demigod/actions?cursor= tok E_DEMIGOD E_METHOD E_NOT_FOUNDthe Architect only, in his own session: every call a demigod agent made, reads included, newest first, 50 a page → {data: [{at, agent, demigod, method, path, status}], next?} (send next back as cursor=). The path only: no query value, no body. The Demigod tab shows the same under Actions. A demigod agent itself: E_DEMIGOD; any other token: E_SCOPEPOST /api/v2/tokens/<id>/remint {…} tok E_FORBIDDEN E_GONE E_METHOD E_NOT_FOUND E_TOO_BIG{role?, level?, mode?, window?}: Remint, in your own session (any token E_SCOPE, asked or not) → {remint: {name, role, level, secrets, pending, again, mode, window?, collect}}: the new value waits for one of the old token's agents to collect itGET /api/v2/tokens tok E_FORBIDDENthe live tokens first, then the ended ones; archived=1: the archived ones instead (P-2499: ended more than token.archiveDays ago), the Architect's every one with byMonth: {total, months: [{month, count}]}. {data: [{id, name, kind, role, scope, project, prefix, created, lastUsed, expires, note, level, drafts, test, suspended, suspendedBy, ends: soon|ended|null, secrets (a count), live, remint?: {pending?: {mode, until?, role, level, changed}, done?: {at, carried, was?, windowEnd?}}, agents? (a role token's, a count): {live, ended}}], counts: {agents, agentsLimit, tokens, tokensLimit}, others? (an admin's: everyone else's, the same rows plus email, secrets a count only, agents' too), offer: {roles, levels, scopeOwned, draftsLocked, god, everConnected, renewal: {expires, read}|null}}: your own tokens on this project, as the AI tokens page draws them, in your own session (any token E_SCOPE, an AI's included); never a token's value, never a secret's name or an agent's row (P-2147: a role token's agents are GET /api/v2/tokens/GET /api/v2/tokens/made/ tok E_METHOD E_NOT_FOUND E_ORIGIN{name, token, project}: the token POST /tokens just made (it redirects to /ai/tokens?made=<k>), for Page:Tokens' shown-once box: the person who made it, in their own session, same-origin, within 5 minutes, once (deleted on read), Cache-Control: no-store. Any token E_SCOPE; another person, a second read or a lapsed one E_NOT_FOUND; cross-origin E_ORIGINGET /api/v2/tokens//agents tok E_FORBIDDEN E_NOT_FOUND{data: [{id, name, role, expires, lastUsed, follows?, suspended?, ended, secrets: [names], remintPending}]}: one of your own role tokens' agents, newest first, ended ones too; asked when its drawer opens; another person's token is E_NOT_FOUND; in your own session (any token E_SCOPE)POST /api/v2/tokens tok E_EXISTS E_FORBIDDEN E_INTENT E_METHOD E_ORIGIN E_PLAN E_QUOTA{kind: role, role, name?, level?, days?: 1 to 365, default 90}: a role token on this project, its value kept in your vault as d2-<role> (an existing one gets it, its allows kept) → 201 {token: {id, name, role, project}, secret}: never the value, for a person and an agent alike (^vault-17). its person's own session, or their own agent token on their word for that one call (^vault-17: their live clear intent, D2-Intent, or their Allow once, D2-Override; intentGating on), never a value (^vault-18); an agent's at most its own token's level. Never the monitor's (E_ARG). role: maker is retired (^d2land-11): still made, with warnings: [{code: N_ROLE_RETIRED, details: {role: maker, use: designer}}]POST /api/v2/tokens/agents/remint vault E_INTENT E_NOT_FOUND{tokenId: a role token's id} or {secret: name}: Re-mint (P-1303) every live agent of that role token, or of the secret's role tokens: same name, same value, its role token's current secrets at its next vault read → {reminted: [{agent, secrets}]}. its person's own session, or their own agent token on their word for that one call (^vault-17: their live clear intent, D2-Intent, or their Allow once, D2-Override; intentGating on), never a value (^vault-18)POST /api/v2/tokens/agents/<agent>/remint vault E_INTENT E_NOT_FOUNDRe-mint one live agent of yours by name, as aboveDELETE /api/v2/tokens/<id>/remint tok E_FORBIDDEN E_METHOD E_NOT_FOUNDcancels a pending remint, in your own session → {cancelled}POST /api/v2/tokens/remint/collect tok E_GONE E_METHOD E_NOT_FOUND E_SUSPENDED E_TOKEN_SUSPENDEDan agent of the reminted token (the Bearer), the old role token in D2-Role-Token → {token, name, role, level, expires, carried, left}: the new value, once, its secrets carried over; inside an Update remint's window the old token as the Bearer gets the current value again → {…, late: true, windowEnd}GET /api/v2/tokens/renewal tok E_GONE E_METHOD E_NOT_FOUND{token, expires}: an agent of the maker token its person renewed reads the new role token once (a second read 410 E_GONE); with a remint pending, collect's alias (D2-Role-Token carries the grants)GET /api/v2/crons cron E_FORBIDDEN E_METHOD E_NOT_FOUNDthe project's crons, a page at a time (limit, cursor; capped, next: 100 unless limit= says, at most 100, api.list.max, more is 100 with capped: true; next, sent back as cursor= with the same query, the next page): {total, limit, capped?, next?, limits, data: [{name, schedule, tz, msg, args, enabled, by, next, last, lastResult, failures, runs, stoppedBy: null | failures | <who switched it off>}]}; the Architect: ?all=1 every project's, all: [{project, url (its address), total, data}], its projects paged the same way (allTotal, allNext, sent back as allCursor=). Crons are made by rules: diesel.cron (name, schedule, tz, msg, args)GET /api/v2/crons/<name>/runs cron E_FORBIDDEN E_NOT_FOUND{name, type: diesel | builtin, total, limit, capped?, next?, data}: its runs over the last 7 days, newest first, a page at a time (limit, cursor; capped, next: 100 unless limit= says, at most 100, api.list.max, more is 100 with capped: true; next, sent back as cursor=, the next page); a built-in job's are the Architect'sPOST /api/v2/crons/<name>/run cron E_BUSY E_FORBIDDEN E_METHOD E_NOT_FOUNDadmins: run it now (a flow); a built-in job (?type=builtin) the Architect'sPOST /api/v2/crons/<name>/on|off cron E_FORBIDDEN E_NOT_FOUNDadmins: switch it on or off; a built-in job (?type=builtin) the Architect'sGET /crons cron E_PAGE_MISSINGbase d2's Page:Crons for a member's session (P-1941); a token is 303 to /api/v2/crons; no built-in page (P-2027): 503 E_PAGE_MISSING where base d2 has none. /flows likewise: Page:Flows over /api/v2/diesel/enginesGET /embed corethe sandboxed frame embedhtml widgets and framed apps run in, with a policy of its own; the page hands it the HTML (P-221)POST /mcp core E_LEVEL E_METHODd2 as an MCP server: Streamable HTTP, stateless; tools whoami, pipeline_*, agent_status, messages_read, message_send, changes_since, topic_*, memory_*, skill_*; claude mcp add --transport http d2 https://<project>.aiputty.com/mcp --header "Authorization: Bearer <token>"GET /api/v2/trash topics E_FORBIDDEN E_METHODdeleted topics, kept 30 days, newest first, a page at a time: {total, limit, capped?, next?, days, data}; limit, cursor; capped, next: 100 unless limit= says, at most 100 (api.list.max; more is 100, capped: true); next, sent back as cursor=, the next pagePOST /api/v2/trash/<name>/restore topics E_DRAFTS E_EXISTS E_FORBIDDEN E_NOT_FOUND E_UNPRIVATEput it back as it was, with its history; ?id= picks one; an AI whose publish of it asks (^drafts-9): its text comes back as the AI's draft {draft: true}, the trashed copy keptGET /trash topics E_PAGE_MISSINGbase d2's Page:Trash for a member's session (P-1941); a token is 303 to /api/v2/trash; no built-in page (P-2027): 503 E_PAGE_MISSING where base d2 has noneGET /thoughts thoughts E_PAGE_MISSINGand /thoughts/focus/<handle>-<n>?project=: base d2's published Page:Thoughts and Page:Focus, at their real addresses; a visitor (or an AI token) goes to /login?next=; with none published, 503 E_PAGE_MISSINGGET /api/v2/thoughts thoughts?view=recent|untagged|archive|shared&tag=¬=&project=&q=&before=: your thoughts from every project you're in, newest first, 50 a page, the due ones (a reminder fired, not opened since) first: {data: [{name, project, text, tags, created, updated, ver, remind?, due?, waitingFor?, sealed?}], counts (with shared: how many shared thoughts of others you read), hidden, next}; view=shared: other people's thoughts tagged #shared on the projects where you are a builder, mod or admin (not archived, never d2's Stuck or chore thoughts), read-only, newest first: {view: "shared", total, data: [{…, owner: <their handle>}], next}; sealed (tagged encrypted): {by}, open for its writer's own session; {by, locked: true, d1?} for anyone else (their AI token too), whose text is then a line naming the writer; q never matches a sealed one's wordsGET /api/v2/thoughts/stamp thoughts{stamp}: changes whenever one of your thoughts does (written, archived, moved, deleted, sent, answered) or your Inbox's skip list; the Thoughts page polls itGET /api/v2/thoughts/app thoughts?view=&tag=¬=&project=&q=&before=&limit=: what a Thoughts Page draws up front, in one read; your session or an AI token for its person (a frame only from a base d2 Page:). limit: 1..100, default 20, else E_ARG. The first call: {ok, project, view, tag, only, cards: [{name, project, text, tags, created, updated, ver, home, streak?, to?, focus?, in?, waitingFor?}], total, next, counts, hidden, hiddenN, hiddenCount, skip, flow, focuses: [{key, title, thoughts, topics, created}], inbox: {keys, n}, mine, to, canCapture, any, who, voice, stamp}; a card's streak and to: as GET /api/v2/thoughts gives them (its promotion, true or false, absent when never set; its addressee); inbox (Meld on): every Inbox thought's <project>/<name>, not only this batch, red tags left out; with before=<next> only {ok, cards, next}. The red tags' thoughts are left out (hiddenCount). ETag "<stamp>-<hash of the query>" (Cache-Control: private, no-cache), If-None-Match → 304 with no body until a thought of yours, your red tags, Meld or your projects change; Server-Timing build;dur= (desc=hit when kept)GET /api/v2/thoughts/draft thoughts{text, at}: the capture box's unsaved text, one per person on any project ({text: ""} when none); your session, an AI token for its person, or a base d2 PagePUT /api/v2/thoughts/draft thoughts E_METHOD{text}: keeps it ({text, at}); empty text deletes it; over 20000 characters E_ARGPOST /api/v2/thoughts thoughts E_ENC E_FORBIDDEN E_NOT_FOUND E_PARSE E_QUOTA{text, archive?, project?}: a new thought on that project of yours (default: this one); its #tags tag it, #top is kept as #important; #encrypted seals it with your key (your session only: an AI token's is 403 E_ENC; no server key 503 E_ENC_KEY); 201 {name, project, tags}; an Idempotency-Key header (8 to 64 letters, digits, - or _; else E_ARG) makes it once: the same key again from you on this project answers the thought it made, 200 {…, replayed: true} (the phone app's offline thoughts, P-2668)GET /api/v2/thoughts/<name> thoughts E_NOT_FOUND?project=: {name, project, text, tags, created, updated, ver, waitingFor?}: one thought of yours (someone else's, or none, the same 404), or someone else's tagged #shared where you are a builder, mod or admin, with owner (their handle) and shared: true, read-only: any write on it, its Focus or a claim is 403 E_FORBIDDEN; a monitor token reads only a thought sent to it (@monitor)GET /thoughts/<handle>-<n> thoughts E_NOT_FOUND?project=: a thought's own address (an N_REMIND links it): its person goes to /thoughts?open=<handle>-<n>&project=, which clears its due; anyone else 404PUT /api/v2/thoughts/<name> thoughts E_APPEND_ONLY E_CLAIMED E_ENC E_FORBIDDEN E_METHOD E_NOT_FOUND E_PARSE E_STATE E_THOUGHT_STATE E_VERSION?project=: {text, ver, state?, force?, reason?}: a new version; an AI token's save of a thought another agent claimed is 409 E_CLAIMED {by, at, note?, thought} (^thoughts-ui-65), and its claimer's answering save (a state other than ai-thinking) ends the claim; an AI token's text keeps what was there and adds under it (else 409 E_APPEND_ONLY {thought, firstChangedLine}); an AI token's save says state (ai-thinking while it isn't answered, ai-question, ai-done, waiting + waitingFor; else 409 E_THOUGHT_STATE {got, states}); an agent's answer (ai-question, ai-done, waiting) on a thought it hasn't claimed (not ai-thinking, no claim of its own) is 409 E_STATE {verb: edit, state, from: [ai-thinking], got, claim: true}, claim it first (a monitor token's answer on its own thought, as before), and with ai-question, ai-done or waiting its text ends in the answer marker, added when it isn't last (marker: added); an #archive an AI token adds is taken out (warnings: W_ARCHIVE); its ai-question carries ## Questions and the answer marker, and its waiting never names its person (else E_ARG {state, missing}); a person's own saves are never checked; {ver, state: closed} (the person's own session only; an agent's is 409 E_THOUGHT_STATE; no text, else E_ARG) marks it done and keeps it: sent, ai-thinking, ai-question, ai-done and waiting dropped, never the person's turn, out of the streak, kept in Threads or its Focus after ai-done; {ver, state: open} takes closed off; any send drops it; a stale ver is 409 E_VERSION, but a person's text is first merged three-way with what changed since that ver and saved when nothing overlaps (200 merged: true), else 409 {ver, conflicts} (^thoughts-47); adding or dropping #encrypted, or a new text on a sealed one, is its writer's session's only (else 403 E_ENC); a sealed one takes no remind: line (E_ARG); a new answer marker sets ai-done or ai-question (state: ai-done | ai-question | waiting sets that one; done is stored as ai-done) and drops sent and ai-thinking; waiting needs waitingFor (P-n, <handle>-<n>, agent:<name> or /path, else E_ARG), kept on the thought until another state, and the thought goes back to its agent (tagged sent, a board message) when that ticket closes or that thought is answered; {state: ai-thinking} (or working; text and ver optional), an agent's only (else E_ARG): picked up, in place of sent; an answer ending ai-done while a ticket on this project is open (not done or dropped) that its new round names (P-n, or a link to /pipeline/P-n) or the thought waited for is 409 E_STATE {open: [P-n]}, nothing saved; force: true with a reason (else E_ARG) saves it, *done with P-n open: reason* written under the answer; every Focus listing it takes its tag again (ai-question, else ai-thinking, else waiting, else ai-done)POST /api/v2/thoughts/<name>/claim thoughts E_CLAIMED E_METHOD E_NOT_FOUND?project=: claim before you work (^thoughts-ui-65): an agent (its agent token, or D2-Agent with its person's AI token) claims a thought it works, a Focus's member as a chat message is claimed → 200 {thought, claim: {by, at, note?}}; the first claim wins: another agent's standing claim is 409 E_CLAIMED {by, at, note?, thought}; its own again is 200. The claim stands until its agent saves the thought with a state other than ai-thinking, lets it go (DELETE …/claim → {released: true}; none of its own E_ARG), or ends; meanwhile another agent's save is E_CLAIMED, and next lists the thought with claimedBy and never hands it to another agent. In memory: a restart lets every claim go. The person's session E_SCOPE, an AI token naming no agent E_ARGPOST /api/v2/thoughts/<name>/archive thoughts E_ARCHIVE_PERSON E_ENC E_FORBIDDEN E_NOT_FOUND?project=: {on}: archive it, or bring it back (a sealed one stays sealed, for anyone); a person's only: an AI token is 403 E_ARCHIVE_PERSONPOST /api/v2/thoughts/<name>/remind thoughts E_FORBIDDEN E_NOT_FOUND E_QUOTA?project=: a sealed thought takes none (E_ARG); {when: tomorrow | friday | next-week | YYYY-MM-DD[ HH:MM] | null, askedBy?}: the ⏰ Remind me: writes its remind: line (in your time zone, 09:00 when no time), null takes it out; an AI token only with askedBy, its person's words (E_ARG); at most 200 pending per person (E_QUOTA); {ok, name, tags, ver, updated, remind}POST /api/v2/thoughts/<name>/move thoughts E_ENC E_FORBIDDEN E_NOT_FOUND E_QUOTA?project=<from>: {to}: a sealed one only by its writer's session (else 403 E_ENC), sealed again there; a new thought on that project of yours (same text, tags and created), this one to the Trash; 201 {name, project}POST /api/v2/thoughts/<name>/send thoughts E_FORBIDDEN E_NOT_FOUND E_VERSION?project=: {text?, ver?, focus?} (a stale ver merged as PUT does: merged: true, else 409 E_VERSION {ver, conflicts}, ^thoughts-47) or {relayed: chat | mail, chat?}: relayed, the page carried it to the agent itself: nothing is posted, it is only tagged thread and sent (the answered tags and ai-thinking dropped), 200 {name, tags, ver, to}; chat names an open chat of yours (its agent, else its role, is to), mail goes to role:designer unless the thought has an @; any other relayed, a missing or unknown chat, or a text with it is E_ARG; focus, <project>/<name> of a Focus of yours, links the message back to it; your session only (an AI token E_SCOPE, a demigod agent stamped demigod): saves it, tags it thread and sent and posts it from you on the message box (the text since the last answer marker) to an @role or @agent typed since that marker, else the role of the agent that answered last (never its name), else the thought's @role or @agent, 201 {to}; E_ARG no or unknown addressee, or a sealed thought (never sent), E_TO a closed lanePOST /api/v2/thoughts/<name>/streak thoughts E_FORBIDDEN E_NOT_FOUND?project=: {on}: promote it into your Inbox (^^), or take it outPOST /api/v2/thoughts/streak/send thoughts{keys: [<project>/<name>], mode?}, your session only (an AI token E_SCOPE, a demigod agent stamped demigod): those of keys that are in your Inbox and not skipped, in one message (a sprint) to role:designer, 201 {sent, message, mode, tasks: [{feature, title, keys}]}; mode: split (the default, one task per feature its thoughts name, those naming none together) or one (one task), else E_ARG; tags them sent, clears skip; an answered one goes as go: <project>/<name>, ok, go under its last answer marker when nothing is typed there; no keys or none left is E_ARGPOST /api/v2/thoughts/streak/clear thoughts{keys: [<project>/<name>]}, your session only (an AI token E_SCOPE): Clear all, to start a new sprint: those of keys in your Inbox get streak: false (out of it whatever their tags, until ticked again) and leave skip; any other key is ignored; {cleared: n}GET /api/v2/thoughts/<name>/focus thoughts E_METHOD E_NOT_FOUND?project=: a Focus (a thought tagged focus) and its members, thoughts then topics: {title, notes, ver, sent, archived? (an archived Focus: a tab's chat lets it go, P-2525), follower? {agent, chat} (yours), members: [{n, kind, project, name, section?, title, text, ver, editable, also, tags?, waitingFor?, missing?}] (thoughts, then topics, then tickets: kind item {n, kind, project, name: P-n, title, status, href}), links: [[a, b]], stamp, related: [{name, title, snippet}], model: [{id, kind, label, count, thoughts, topics, title, text, href}]}: stamp as GET /api/v2/thoughts/stamp; related, up to 8 topics of its project its text and thoughts name that it doesn't hold, each with a snippet in words (^srch-6); model, the map's model lobe: the classes (kind class), features and behaviours of its project's Spec its thoughts name, most-named first, thoughts as <project>/<name>, topics as member n ([] when none); not tagged focus is E_ARGPOST /api/v2/thoughts/<name>/focus/add thoughts E_ENC E_FORBIDDEN E_NOT_FOUND?project=: {members: [{project?, name, section?} | {project?, item: P-n}]}: all or nothing; each your thought, a topic you can read or an item (a ticket, listed as [[P-n]], a link only) on a project of yours (404 naming it), no Focus, up to 40 in all; answers the readPOST /api/v2/thoughts/<name>/focus/remove thoughts?project=: {member: {project?, name, section?} | {project?, item: P-n}}: takes its line out; answers the readPOST /api/v2/thoughts/<name>/focus/send thoughts?project=: your session only (an AI token E_SCOPE): {mode?}: the Focus in one message to role:designer (a sealed thought in it as its key only, not tagged sent; a sealed Focus E_ARG), 201 {sent, message, mode, tasks: [{feature, title, keys}]}; mode: one (the default, one task) or split (one task per feature its thoughts name), else E_ARG; tags it and its thoughts sent; no member or sent already is E_ARGPOST /api/v2/thoughts/<name>/focus/follow thoughts E_STATE?project=: your session only (an AI token E_SCOPE): follow here (P-1908): the Focus's following moves to that live chat of yours, on purpose, and the one before stops following it → 200 {name, follower: {agent, chat}}; no such chat of yours E_ARG, one not live or with no agent yet E_STATE. A Focus has at most one follower: the agent of your live chat on it (its subject the Focus, or the chat Meld moved with to it); thoughts you add or change there with Meld on go to it only, and GET …/focus answers who and follower from itPOST /api/v2/thoughts/<name>/merge thoughts E_FORBIDDEN E_NOT_FOUND?project=: {into: {project?, name}}: into's text gains this one's after a blank line, this one is archived (merged-into); every Focus listing it lists into; E_ARG into itself, a Focus, a chat, sent or sealed thought, over 20 KBPOST /api/v2/thoughts/unshare thoughts E_FORBIDDEN E_METHOD E_NOT_FOUND#shared out of every thought of yours (on that project of yours, else on all of them), one new version each; your session or your AI (an AI takes #shared off, never adds it: a write adding it to a thought is 403 E_FORBIDDEN; a new thought your AI writes follows your Share, a #shared in it taken out with W_SHARED when Share is off): {unshared, keys: [<project>/<name>], kept?} (kept: still tagged, a sealed one this reader can't open or one whose text is only the tag)DELETE /api/v2/thoughts/<name> thoughts E_FORBIDDEN E_NOT_FOUND?project=: to your Trash, from where it is restored; your session only (Thought.delete is a person's own, no row: an agent token E_SCOPE)GET /api/v2/me/thoughts thoughts E_METHOD{hidden: [tags]}: your red tags; your session, or an AI token for its personPUT /api/v2/me/thoughts thoughts{hidden: [tags]}: the red tags, kept for you on every device; GET /api/v2/me has thoughtsHiddenGET /api/v2/me/streak thoughts E_METHOD{skip: [<project>/<name>]}: the Inbox thoughts left out of your next sendPUT /api/v2/me/streak thoughts{skip: [<project>/<name>]}: the Inbox thoughts left out of the next send; GET /api/v2/me has streakSkipGET /api/v2/me/flow thoughts E_METHOD{flow}: whether Meld is on for youPUT /api/v2/me/flow thoughts{on: true | false}: Meld on or off for you, on every device and project; 200 {ok, flow}; GET /api/v2/me has flowPUT /api/v2/me {thoughtsShare} thoughts E_BAD_JSON{thoughtsShare: true | false}: Share on Thoughts, kept on your account (on unless turned off), your session only (a token E_SCOPE); 200 {ok, thoughtsShare}; GET /api/v2/me has thoughtsShare. With it on, a new thought your AI writes is tagged #sharedGET /api/v2/brain thoughts E_METHOD E_NOT_FOUNDthe brain map's data for this project in one read, as Viz:brain's loadMap builds it: {ok, C: [{name, fields, group, tip, also}], F: [{id, name, beh}], stamp}: C the classes of its Spec: topics (the first 12 by name), F the 36 largest features of Spec and SpecUi (beh up to 40), only from topics you can read; ETag "<stamp>", If-None-Match → 304 until one of those topics (or Design, its d2-design parts, Testing) is written; a private project's needs a member, else 404GET /api/v2/topics/<name>/comments topics E_FORBIDDEN E_NOT_FOUND E_QUERY?section=<anchor> (only that section's) or all=1 (the default, every one): {ok, topic, total, open, data: [{id, topic, section?, code?, text, by, owner, agent?, at, updated?, tags, state, private?, answers?, sent?, sentTo?, thought?, ver, changed}]}, oldest first; open counts those not done or archived; changed: the section's text moved since it was written; a private one only for its writer (and their agents); an alias of GET /api/v2/d2/Comment?topic=POST /api/v2/topics/<name>/comments topics E_FORBIDDEN E_NOT_FOUND E_PARSE E_QUOTA{section?, code?, text, tags?}: a new comment on that section (a heading's anchor; none: the topic) or behaviour code; #tags in the text tag it too; private: only you see it; thought: also a thought of yours (thought: its name); @designer in a session's comment sends it to role:designer at once (sent; notSent {code, message} when it couldn't be); else, from the person's session with their Meld on and not private, it is handed to the agent following them (their Meld tab's Focus follower, else their one live Meld-on chat's; two roles: nobody): sent and sentTo: POST /api/v2/topics/<name>/comments/batch topics E_FORBIDDEN E_NOT_FOUND E_PARSEyour session only (an AI token E_SCOPE): the topic's open comments not yet sent, private ones left out, in one board message to role:designer; marks them sent; 201 {ok, sent, message}; none left is E_ARGGET /api/v2/comments topics E_FORBIDDEN E_QUERY?state=open|waiting|asks|done|archived&topic=: every comment of this project you see, newest first: {ok, total, data} as above; an alias of GET /api/v2/d2/CommentGET /api/v2/comments/<id> topics E_FORBIDDEN E_NOT_FOUNDone comment; another's private one E_SCOPE; an alias of GET /api/v2/d2/Comment/<id>PATCH /api/v2/comments/<id> topics E_CHANGED E_FORBIDDEN E_NOT_FOUND E_PARSE E_VERSION_REQUIRED{text?, tags?, state?, answer?}: text and tags its writer's (and their agents'); answer (appended: {by, at, text, agent?}) and state (open, waiting, asks, done, archived) its writer's or a designer agent's; anyone else E_SCOPE; 200 {ok, comment}; an alias of PATCH /api/v2/d2/Comment/<id> (and of its resolve, for state done alone, and of its answer, for an answer), the gate naming the act by the body: comment.resolve, comment.answer or comment.edit, which needs If-Match: v<n> (428 E_VERSION_REQUIRED, a stale one 409 E_CHANGED); this route takes noneGET /api/v2/steps eng E_NOT_FOUNDthe step dictionary (Spec:d2-steps here, else base d2's): {ok, topic, project, errors: [{message, line}], msgs: [{name, params: [{name, type, opt?, many?, def?}], kind, say: {<lang>: line}, codes, confirm?, by?, native?, phase?, line}]}GET /api/v2/steps/stories eng E_NOT_FOUND E_STEP_JUMP E_STEP_VAR?chat=<id>: the stories agents posted in your chat (your own session), each with its message's id as msg; ?topic=<name>: the topic's stories ($story lines, and its 4-space code blocks) → {ok, topic, ver, pageTags (its front matter tags), mayTag (the viewer may edit the topic: +tag on each journey, P-2676), tagWords (the words +tag offers: auto, security, onboarding, then the tags in use), errors: [{message, line, code?, var?}], presets: [{name, vars, line}] (the topic's $vars lines), stories: [{kind, id, title, params, codes, line, src, vars: [{name, default?, declared?, setBy: [n], usedBy: [n]}], presets: [{name, vars}] (each with the values that are this story's variables), tags (its own tags: on its head, ownTags: true; else its topic's), steps: [{i, n, kind, say, label?, codes, native?, phase?, href?, script?, vault?: [names], choose?, under?, uses?, sets?}]}]}; a step's say line has its arguments filled in, a vault value only ever as its name, a story variable {a.b} as written; a {x} no header declares and no step sets is an error {code: E_STEP_VAR, var, line}, a jump to a story or step that isn't there {code: E_STEP_JUMP, line}; a d2.journey step carries jump: {story, at?, href} (href: /page/Journey/Story:<id>/<n>?from=<this story>&v.<name>=<value>…, the story and step as the page's path tail), a step with an or line side: [{story, at?, href}], and a choose whose other options only jump side: [{opt, story, at?, href}] with those options left outGET /api/v2/runs eng E_FORBIDDENa member's own session (an AI token E_SCOPE): your runs in this project, newest first, a page at a time (limit, cursor; capped, next: 100 unless limit= says, at most 100, api.list.max, more is 100 with capped: true; next, sent back as cursor= with the same query, the next page), {ok, total, limit, capped?, next?, runs: [{id, topic, story, title, step, state, total, at, updated}]}; open=1 only the open ones (running, waiting, paused, failed)POST /api/v2/runs eng E_FORBIDDEN E_NOT_FOUND E_STATE E_STEP_VAR{topic, story, mode? (headless, engine or ui; P-2107), send? (ops or tester: Send to ops / Send to tester), vars?: {name: value} (any variable of the story: its header's, defaults replaced, or one a step sets; params the same), preset? (a $vars <name> (<var>: <value>, …) line of the story's topic; none such 400 E_STEP_VAR {preset}), replay? (a run of yours: its values again, and a fresh run even when one is open; none such E_NOT_FOUND), runner?}: the values are vars, then the preset, then the replayed run's, then the header's defaults, then — a tester's run (its token beside a test account's session), with no person to ask — picked for each variable still unset (an email a fresh test address of the tester's run, project.name a free d2test-journey-<story>-<n>, project.level the level the story's id names, any other <name>-<n>; recorded on the run as picked); a person is asked for the rest or {chat, story, msg?, runner?} (a story an agent posted in your chat, in a ```story block holding one $story run: the run records the chat and the message): your run of that story, your own session only (an AI token E_SCOPE) → 201 {ok, id, run, steps}; mode defaults to the runner's (runner: ui → ui; engine, tester, ops → engine; neither: the story's runner:, else ui when every step is a page step, else engine); runner tester or ops is send; a send to a lane the project hasn't is E_STATE; an open run of it already yours with the same mode and send → 200 the same with resumed: true (another ends it and starts afresh); a story with errors E_STATE {errors}; each step goes to its actor: the checks it reaches run at once, an agent's steps (ops', a tester's, the person's in a run sent to a tester) go to that lane as one pipeline item of kind run per block of them in a row (run.hands [{to, item, steps}], run.handed the newest; the lane is told, and each live launcher of its role by name, ^step-18; with nobody of the lane running the hand carries note: no <lane> agent is running; the item waits), and the run waits on the person's (run.waitsOn {i, n, actor, lane}); a headless run stops at the first step that needs an actor (failed, its result {code: E_ACTOR, actor, check.seen: the step and whose it is}). A run stores {id, project, topic, story, title, by, step, state, mode, sent?, runner, handed?, hands?, waitsOn?, results: [{i, done, skipped?, note?, at, check?: {ok, seen}, code?, actor?}], params?, preset?, picked?, replayOf?, total, at, updated}, never a vault valueGET /api/v2/runs/<id> eng E_FORBIDDEN E_NOT_FOUNDyour run with its story's steps: {ok, run, steps, story: {topic, id, kind, title}}; another's run E_NOT_FOUND; a tester's or ops' run handed to its lane is read by the agent token that has its item taken too (P-1891), any other AI token E_SCOPE. A form step (form.open) carries fields: [{name, type, value?, opt?}], its message's parameters with the story's values (a {vault:x} a name); a result may carry values (a form's), doing, result and by (the handed agent's)POST /api/v2/runs/<id>/step eng E_FORBIDDEN E_NOT_FOUND E_STATE E_STEP_JUMPthe handed agent (P-1891: the agent token that has one of the run's run items taken; any other AI token E_SCOPE): {i, state: doing|done|failed, note?, result?}, the run's next step only, and one of that item's steps (another 409 E_STATE): doing marks it in progress (note: what it is doing now, at most 300 characters), done keeps result (at most 4096 characters, cut: true when it was cut; a value that looks like a secret is kept as ‹secret›, a {vault:x} stays a name) and moves the run on (running, done after the last), failed stops it failed with the reason (note) until its person retries (the step is the agent's again) or ends it → {ok, run, cut?}; a hand to ops is its person's grant (P-2136): once the run is over, its marks are 403 E_SCOPE. The person: {i, done?: true, values?: {field: value} (a form step's: its fields only; a secret only as {vault:<name>}, else 400 E_ARG), capture?: text (a person's step with capture=<name>: needed on its done, kept as the run's variable <name>; one that looks like a secret 400 E_ARG), skip?: true, note?, retry?: true, did?: fill|append|click|read, page?, confirm?, value?, set?: {variable: value}, follow?: k}: follow k, or done on a d2.journey step: the run goes on as a new run of that story from its step, the variables carried (the old one done with wentOn, the new one with cameFrom; → {ok, run: the new run, from, bar}; a story or step gone 409 E_STEP_JUMP); set: the values of the variables step i names that have none yet (the step stays; → {ok, run, steps, bar}); did read: the text of the box (value) into the variable the step sets (d2.ui.read → x); d2.project.new done: {project.name, project.level, project.host} read from the real project when you are in it; step i done (a choose's note is the option picked), skipped, or its check run again (retry); `did`: a step inside a page that the step bar filled in or pressed on `page` (done by d2), E_SCOPE on a security page or at a part marked data-confirm (`confirm: true`); a failed run takes only a retry or a skip of the failed step, a done or ended one nothing (E_STATE); done on a step handed to an agent's lane while its item is open 409 E_STATE (skip still goes); then the run moves to its next step and runs the checks it reaches → {ok, run, bar} (bar: the step bar for the run now, "" when it is over)POST /api/v2/runs/<id>/step/<i>/do eng E_FORBIDDEN E_NOT_FOUND E_STATEa vault or token step (d2.vault.addSecret, d2.vault.allow, d2.token.create; P-2112), from d2's run view in your own session (a base d2 Page, or its frame; an app, a widget or a project's own Page E_SCOPE; an AI token E_SCOPE): {values?: {value}} — only the value an addSecret is pasted with (else 400 E_ARG {field: value}); the name, note, roles and role are the story's, whatever the page sends → 202 {ok, approve, what, run, i, step, until}, Cache-Control: no-store: d2's bridge draws d2 · Approve with what (Put x in your vault (only the box may use it), Let dispatcher use d2-token, Make a dispatcher token), never the page; the step must be the run's next, the person's (else 409 E_STATE); asking again replaces the ask; the value waits in the server's memory only, 10 minutesPOST /api/v2/runs/<id>/step/<i>/script eng E_ENC_KEY E_NOT_FOUND E_SECRET E_STATE E_VAULT_KEYa d2.ops.runScript step of a run its person handed to ops, filled, for the ops launcher's code alone (P-2136, ^vault-23): the agent token that took the hand's run item, with the role token it mints with in D2-Role-Token (the same person, project and role; the model's own token alone, or any other, 403 E_SCOPE); only a step of the person's grant (the hand-off: {run, steps, role, person, at, until, sig} on run.hands, signed; another step or run, the run over, a day past the hand-off: 403 E_SCOPE), only a runScript step (403 E_SCOPE), only after the agent marked it doing, within 10 minutes of that, once per doing (409 E_STATE); every {vault:x} filled from the person's vault as Copy fills it (inside single quotes, shell-quoted; the vault's own refusals as it answers) → {ok, run, i, step, box, host, envelope: {v, run, step, box, nonce, expires, sha256, script, sig}, mask: [the values], expires}, Cache-Control: no-store: the envelope is signed with the box's own key (its admin vault entry in the CloudDeployment definition; none 409 E_STATE) and the box's d2-script runs it once, for that box, within 10 minutes; audited A_VAULT_SCRIPT {run, step, secrets: names, box, agent}, never a valuePOST /api/v2/runs/<id>/step/<i>/approve eng E_ENC_KEY E_EXISTS E_FORBIDDEN E_KEY E_NOT_FOUND E_PLAN E_QUOTA E_SECRET E_STATE E_STATUS{approve: <id>, answer?: approve | cancel}: d2's own Approve box's call (P-2112), unmarked, from a page d2 draws in your own session (any frame, app or widget, or an AI token, E_SCOPE): approve does the step as you with the vault's and tokens' own code (secret.add kind secret, allowed to your live role tokens of its roles; secret.grant by roles, from your copy; token.create, kept in your vault as d2-<role>), marks the step done (note: what was done, never a value) and audits it (A_STEP_VAULT) → {ok, done, run, token?, tokenName?} (token: a new token's value, once, for d2's box alone; Cache-Control: no-store); cancel → {ok, cancelled}; used once: another's, a used one or none 404 E_NOT_FOUND; past its 10 minutes 409 E_STATUS; the step changed since Do it (another step next, its arguments changed) 409 E_STATE; the vault's and the tokens' own refusals as they answerPOST /api/v2/runs/<id>/state eng E_FORBIDDEN E_NOT_FOUND E_STATE{state: paused|running|ended}: pause it (the navbar comes back), resume it, or end it → {ok, run}POST /api/v2/stories/<name>/runs eng E_BUSY E_CREDITS E_FORBIDDEN E_NOT_FOUND E_QUOTA E_STATE E_UNVERIFIED{kind?: tester (the default: Run with test agent, any text; a line too loose to run, not runnable or its target not there by that name, goes to d2's test agent on story.testerModel, whose tools are the Story's own steps, read the page and edit the Story: the steps it walked green go into the Story in that line's place, its ^ids kept, as its next version when the run ends, and such a walk verifies nothing) | d2 (Run by d2: only a text whose sha256 a test agent's green walk verified, else 409 E_UNVERIFIED {sha}), branch?: [option, …] (each choose's option in the order the walk meets them, a label or its number from 1; one not named takes its first), vars?: {name: value}, from?: a failed run's id (Run with test agent from here: the test agent takes that run's failed step whatever its code, and what it can't get past is filed for the testers; not a failed run of this Story, or kind d2, 400 E_ARG {field})}: d2 walks Story:<name> (this project's, else base d2's) now, in a Chromium it starts for the run and closes after it, starting on the box's base host (base d2's, where sign-up and log-in live) or, with host: project in its $story head, on this project's own host (no cookie of the starter's goes with it; a host that is neither project nor base 400 E_ARG {line}): its $story's lines in order, each a fixed step (go, click, fill, choose (an option, or a checkbox it ticks), type in chat, expect, wait for, d2.page, d2.project.new, d2.credits.testGrant, d2.repo.connect, d2.repo.new, expect d2.repo, agent), never code; the repo steps (P-2604) call GET /api/v2/github and POST /api/v2/github/repos as the walk's person, only on a project its own account is admin of in its own session (else E_STORY_STEP: a tester's walk is admin of none), and a walk never signs in to GitHub: d2.repo.connect passes on a project already connected, else E_STORY_STEP (needs a person's GitHub); d2.repo.new sets {repo.name}, {repo.full}, {repo.url}; an agent step (agent [model=<id>] "<message>" | paste "<button>" [expect <page> "<text>"] [within <s>]) hands its message, or the text the page has the person copy, to the run's one stand-in agent (story.agentModel in Settings:Process, base d2's openrouter on d2's key, its cost Testing on the run's person; one tool, http, to this project's host and base d2 only; 40 model calls and story.agentStepMax a step; the tokens it got revoked at the run's end), and passes on its expect, never on the agent's words; the first line that fails stops it, a line d2 can't map (not runnable) named → 202 {ok, run} (state running). A project admin's own session, or their own designer or tester agent (P-2531: the row run.startStory for its role, tells by default: the run starts and its person is told; set to person, 403 E_SCOPE with its Allow once, then the same call with override; any other role, a role or tool token 403 E_SCOPE; its person not an admin 403 E_FORBIDDEN): it keeps by (the agent) and for (its person). Every run walks in the tester lane (P-2538): a fresh tester d2 makes for that run alone (its sign-ups only the run's test addresses, {tester.email} its first and {tester-password} a password made for the run, never kept; sessions only for accounts it made; sandbox payments only, test credits); at the run's end, passed, failed or cut off by a restart, the tester ends and every account and project it made is deleted with its mail; a vars value naming a real account 403 E_SCOPE {field: vars}, a payment step on its walk 403 E_SCOPE {line}; one run at a time on a box (another running 409 E_BUSY); a box with no browser 409 E_STATE; a topic with no $story, a bad head, or a branch naming no option 400 E_ARG {line}. The run keeps {id, story, topic, ver, sha (sha256 of the text walked), kind, host (the host it was started from), startHost (the host its walk began on), by, for? (an agent's run: its person), tester (its tester's agent name), cleaned? ({accounts, projects, failed?}: what its end deleted), from? ({run, step}), tightened? ({lines, ver?}: the lines the test agent wrote into the Story), state (running, passed, failed), verified? (its sha: a tester run that ended green), at, ended, ms, console: [{at, type, text}] (the last 200), branches: [{q, line, opts, took}], steps: [{i, line, src (as written), codes, ok, ms, at, page, code?, message?, seen? (the page's text when it failed), agent?: {model, turns, cost, calls, said}, tester?: {model, turns, cost, calls, said} (the test agent on this line), tightened? (the steps it walked green in its place), was? ({code, message}: why the line failed before), bug? (it found the page broken)}], total, failed?, error?, cost (US$ its agent steps spent)}, never a secret's value; a step's code: E_STORY_UNRUNNABLE, E_STORY_TARGET (no button, link or field by that name), E_STORY_EXPECT, E_STORY_HTTP (a page answering 4xx/5xx), E_STORY_CONSOLE (a page or console error), E_STORY_VAR (a variable with no value), E_STORY_AGENT (the agent stopped: a limit, its model failing; or still working at its within), E_STORY_STEP. A run by d2 whose person isn't the Architect is paid (P-2515): stories.runFee (Settings:Process, US$ 0.01) from their credits as it starts, and each agent step's call on their credits (held before it is sent, its cost plus the markup kept), counted as Story runs; it keeps paid and fee; a balance short of the fee 409 E_CREDITS, their spend cap 409 E_QUOTA, nothing startedGET /api/v2/stories/<name>/runs eng E_FORBIDDENthe Story's runs here, newest first (the last 50, and the one that verified the newest verified text): {ok, story, sha? (the Story's text now), verified (a test agent's green walk verified that sha: Run by d2 may go), may: {start} (the viewer may start a run), queued: [{kind, why, at}] (the runs d2 will make by itself: after a release, a publish of a page its green walk visited, story.agentEvery; at most one an hour a Story), runs: [{id, kind, by, state, at, ms, total, failed?, cost, verified?}]}GET /api/v2/stories/<name>/runs/<id> eng E_FORBIDDEN E_NOT_FOUNDone run, as storyRun.start keeps it: {ok, run} (its screenshots as shots: [the steps' i that have one])GET /api/v2/stories/<name>/runs/<id>/stream eng E_FORBIDDEN E_NOT_FOUNDthe run as it goes, pushed (text/event-stream): `run` (as GET …/runs/<id>) on connect, `step` (a step's result) as each ends, `frame` (a JPEG of the run's browser, base64) about once a second while it runs, `console` ({at, type, text}) each console line, `end` (the whole run) when it is over, and then the stream closes; a run already over: `run` and `end` at once; the page /stories/<name>/runs/<id> draws itGET /api/v2/stories/<name>/runs/<id>/shots/<i> eng E_FORBIDDEN E_NOT_FOUNDthe screenshot taken as step i ended (image/jpeg); the newest 5 runs of a Story keep theirs, an older run 404 E_NOT_FOUNDGET /api/v2/stories/nightly eng E_FORBIDDENthe morning line (P-2515), the dashboards': {ok, on (stories.nightly in Settings:Project, on a Pro or Conductor project), line (*3 Stories passed* or *1 broke: signup, step 4*; empty with no nightly run in the last 36 hours), day?, passed, running, broke: [{story, step, line, src, code, run (its page)}]}: each Story's newest nightly runPOST /api/v2/stories/<name>/compare eng E_BUSY E_CREDITS E_FORBIDDEN E_NOT_FOUND E_QUOTA E_STATE{models: [1 to 3 model ids, each once], branch?}: a shoot-out (P-2515), a project admin's own session only (a token 403 E_SCOPE): d2 walks the Story as the test agent once on each model (its agent steps on it), one after another, one run at a time on the box, each a paid run (as Run by d2: its fee and its agent steps from the person's credits as Story runs, the Architect's on d2's key); a walk of a shoot-out verifies nothing and posts no line of its own. When all are over, the cheapest walk that passed has its model write a one-line verdict on each (none passed, or its answer unreadable: d2's own line), and the Story gets one line → 202 {ok, compare: {id, models}, href (its page, /stories/<name>/compare/<id>)}; the first walk refused as a run is: E_BUSY, E_CREDITS, E_STATE…GET /api/v2/stories/<name>/compare/<id> eng E_FORBIDDEN E_NOT_FOUNDa shoot-out as it goes: {ok, id, story, models, state (running, over), stopped? (why a walk wasn't made), judge? (the model that wrote the verdicts), columns: [{model, run?: {id, state, ms, total, failed?, error?, cost, fee, steps: [{i, line, src, ok, ms, code?, message?}], noticed: [{i, line, code, message}], verdict?, verdictBy?}}]}, one column a model in its orderPOST /api/v2/stories/<name>/compare/<id>/default eng E_FORBIDDEN E_NOT_FOUND{model}: Make default (P-2515), a project admin's own session only: one of the shoot-out's models whose walk passed becomes the project's stories.model (Settings:Project), the model its Story walks' agent steps run on → {ok, model}; a model not in it or whose walk didn't pass 400 E_ARG {field: model}POST /api/v2/chats chats E_BAD_JSON E_CREDITS E_FORBIDDEN E_NOT_FOUND E_OFF E_STATUS{role, agent? | apiAgent?, subject: {kind: thought|focus|task|page, ref}}, apiAgent: one of the person's API agents for the role, by its id (^apiag-12; their ApiAgent on base d2): not theirs 404 E_NOT_FOUND {field: apiAgent}, another role's 400 E_ARG, paused 409 E_STATUS, one of d2's shared smart agents with an empty balance 402 E_CREDITS; a restart or a new subject stays with it while it is theirs and not paused; or {continues: <an ended chat's id>} (Start again on this subject, ^chat-27): the person's session asks a chat → 201 {chat: {id, state: asked}, launcher: true|false, reach} (reach as GET /api/v2/chats/reach); the chat holds one message from the person, markdown chat started by <handle>; chats off → E_OFF; one live chat per agent (^chat-7): a chat asked of an agent ends the person's open chat with it first, as Done does (why done); no cap on chats with a role; continues: the new chat keeps the old one's role, subject and context, carries continues and handover: <P-n> (the newest handover linked to the old chat, else its agent's newest open one filed after it started), the old one gets continuedBy, and it goes to the old agent while live and chat-free, else a live agent of the role with no live chat (the oldest), else none named: a fresh one is started for it and no agent busy in a chat of its own is offered it or joins it (^chat-22), d2 saying so in one line (<old> is gone: <new> has your chat); a chat still open E_STATUS, another's or none E_NOT_FOUND; their own agent nobody started (in their app) may be named; an agent on their board that isn't theirs → E_ARG; nobody to answer the role (reach none) → hint: Nobody is listening (/topics/Help:AgentRecipes#listening), the chat kept; the person's unsent text from their last chat with the role that d2 closed comes back on the new chat as unsent {text, at}POST /api/v2/apiagents/<id>/test agents E_CREDITS E_HTTP E_NOT_FOUND E_QUOTA E_SECRET E_STATUSone of the person's API agents (their ApiAgent on base d2, by its id), tested before Save on Page:AiAgents (^apiag-11): the person's own session only (any token, or Basic with one, E_SCOPE), on any project of theirs → one call of its model at its provider (base d2's AiProvider baseUrl, the only address the key goes to) with *say hello in one line* → 200 {text, tokens: {in, cached, out}, cost (US$), model}, counted on the day's ApiUsage and against the daily cap; not theirs E_NOT_FOUND; the cap reached (their ApiBudget costDay, else agents.costDay, 5) 409 E_QUOTA; no such provider 409 E_STATUS; a key d2 can't open (a key, not a secret d2 may use) 409 E_SECRET; an agent paid by d2 credits (payBy: credits, P-2071) runs on d2's key: the most its call can cost (the model's price at its provider × its prompt and max_tokens, plus the markup) is held from the balance before it is sent, and its real cost plus the markup kept (P-2253): no room for that, a zero balance or a model d2 can't price 409 E_CREDITS, nothing sent; d2's key paused by its breaker (spend.d2DayMax) or the Architect's own runs at spend.mineDayMax, or a tester's at spend.testDayMax (P-2493), 409 E_QUOTA; the provider unreachable or refusing 502 E_HTTP {message, scrubbed of the key}. The same key runs the agent in the person's chats: their typed messages and pad answers wake it (never 👋 I'm here or Meld's moves), one run at a time per chat, as an agent token minted from their own role token for the role (P-1991)GET /api/v2/models/prices?ids=<id,id…> agents E_FORBIDDENwhat each model costs at d2's openrouter provider (P-2665), for Page:AgentOnboardSmart: a member of the project or their AI token (no key needed) → {models: [{id, prompt, completion, per1M: {in, out}}], at}: prompt and completion US$ a token, per1M US$ a million tokens in and out, from the provider's own price list (its GET /models, kept an hour; at, when it was read, null when it never was); an id it doesn't price is left out; ids missing, or more than 50, E_ARG {field: ids}GET /api/v2/chats?state=asked|live|ended&limit=&cursor=&messages=1 chats E_METHODyour chats on this project (its owner's session: on every project they own, each with its project; an all-my-projects agent token, not a support grant's: its person's chats on every project of theirs its token reaches, each with its project, answered on that project's own host, ^chat-21), newest first, {total, data, limit, capped?, next?, launcher, settings}: each without its messages — last {from, at, text (120 characters)}, ids (the answers' ids, for unread) and unseen (your messages the agent hasn't been handed); every asked and live chat and the newest 20 ended, or the newest ?limit=n, either a page at a time: 100 unless ?limit= says, at most 100 (api.list.max; more is 100, capped: true), next sent back as ?cursor= with the same query the next page; total counts them all; ?messages=1: each chat whole, with its messages; an asked chat an agent is already starting for (its up status names it, chat: <id>) says claimed: <agent>; the chat launcher polls ?state=asked, which leaves those out for its tool token; for the launcher each chat carries agent, its current agent (null while none has joined), so it feeds no other session. Kept, not deprecated: GET /api/v2/d2/Chat lists this project's, and only this route answers an all-my-projects token its chats across projects and ?state=askedGET /api/v2/chats/reach chats E_METHODwho can answer a chat with each role (Design #reach, #listening), the person's session only (an AI token E_SCOPE) → {runners: [{agent, up, at}] (the chat launcher by its heartbeat, two missed: down), roles: [{role, state, agent?, via?, line, can, waits}] (every role the person has a background agent or a listening app agent in, and always designer, a dispatcher among them; never monitor; state, in the order d2 picks: now a chat loop (started by app-chat) heard within twice its every, then now the person's own agent nobody started (in their app: its token and its role token theirs, live, not ended) that read agents/next within twice its every or 2 minutes, via: app, line <agent> in your app is listening, busy instead while it chats in a live chat of its own (another person's never shows), busy its agent in another chat, step working, merging or handing over, wake between passes with a runner up, fresh none running with a runner up, none no runner: Nobody is listening), listening: [{agent, role, how: loop|app|runner}], apiAgents: [{id, role, name, provider, model, paused: false}] (the person's API agents not paused, ^apiag-12)}; an agent nobody started is a role's agent only as the person's own app agent; POST /api/v2/chats's 201 carries the same reachGET /api/v2/chats/<id> chats E_METHOD E_NOT_FOUNDone chat with its messages, and, while live, agentNext {state: listening|working|stopped, at, every, batch?: {a, n}} (batch: while its agent holds a batch, answered of handed, ^ui-chat-32) (its agent's next: waiting on it, a call within every, none for 2 × every, or its row ended — gone, done, stopped; d2's line under the chat and its pad) and agentWaiting (false once its row ended): whether its agent's runner is waiting on next (an agent-token next pending or ended in the last 5 s); anyone but its person, the chat launcher, the chat's agent and the agents invited into it gets 404 (^ui-chat-33: another agent of the person's, even of the same role; while it is asked, the agent it names or an agent of its role, and an agent whose status names it, read it too); the person's session reads it without the agents' asides, each message an agent has claimed with claimedBy, and who {label: designer-145 +2, title: designer-145 · designer-146 (reviewer) · …} while others are in it (with: [{agent, as, at, by}]); on a project its person owns, their session reaches their chats of every project they own (read, next, messages, seen, alive, end), a message sent from another project than the chat's carrying project; context: [{kind: item|topic|thought|story|focus|page|agent, ref, title?, by: subject|here|<handle or agent>, from?: <message id>, at}], newest first, at most 50 (^ui-chat-40); while live, agentNow {state, step?, item?, text?, at, here, line} from its agent's latest board status (^chat-26): line in the chat (here: chatting in it, its runner waiting on it, or it answered since), between passes (idle, quiet, waiting), else in the background[: <step> · <item>]; continues, continuesAt (the old chat's start) and handover on a chat that continues an ended one, continuedBy on that one (^chat-27); while open, fullness (0–100): how full its agent's context is, from that agent's latest board status (^chat-28)PATCH /api/v2/chats/<id> chats E_BAD_JSON E_NOT_FOUND E_STATUS E_TOKEN_SCOPE{on?: <project> | null, pad?: true|false}: the chat's agent (its own token) or its person; on moves the chat's work to another project its person owns → the chat with on; the chat stays where it was started; a project they don't own E_TOKEN_SCOPE; null, or the chat's own project, moves it back; pad turns pad mode on or off (the agent writes every reply as a pad; next answers pad); an agent invited into it E_SCOPE, any other agent of the person.s or another person 404 (^ui-chat-33)GET /api/v2/chats/<id>/next?since=<message id>&wait=25&for=<agent> chats E_NOT_CHAT_AGENT E_NOT_FOUNDthe chat launcher's tool token: every answer carries agent (the chat's current agent, null while none has joined) and invited: [agents], and a change of agent (a join, a take-over, a leave) answers a held call; ?for=<agent> that isn't the chat's agent or one it invited is 409 E_NOT_CHAT_AGENT {agent}, at once or when the agent changes under a held call (left out, nothing is checked; an asked chat no agent has joined refuses no one); the launcher's wait for the chat's own agent is that agent's next: the chat's agentNext reads listening while it waits and for 2 × wait after, working once the agent posts working or merging, then stopped, and agentWaiting is true while it waits (another token's for= is ignored and credits no one). held until a message comes (the person's for an AI token, the agent's for the person's page) or the chat ends, at most wait seconds (0-25), then 204; the answer carries pad (pad mode on or off); an AI token's also carries unanswered: the ids of the person's messages its pass has picked up (seen) that no reply answers (an Ask again stands for the message it asks again), and with wait=0 it answers at once while there are any; an AI token is handed at most chat.batch (3) of the person's open messages (^ui-chat-32): the picked-up, unanswered ones first, again: true (an Ask again's own id as asksAgain), then new ones, which the chat's own agent's call picks up (seen); the rest wait for a later call; all of them reminders: warnings: [{code: W_NOT_ANSWERED, details: {open: [ids]}}], never a refusal or a skip; the page's also answers when the agent's status changes or its runner starts or stops waiting (agentWaiting)POST /api/v2/chats/<id>/messages chats E_BAD_JSON E_FORBIDDEN E_NOT_FOUND E_OFF E_STATUS E_TOO_LONGcontext {workbench?, focus?, section?}: where the person's tab is (P-2525), their own session only (an AI token E_ARG): kept on the message as context {workbench?, focus?, page, section?}, page the topic of the tab's path (else the path), and d2's line before it when that changed since their last message (now on DELETE /api/v2/chats/<id>/messages/<mid> chats E_NOT_FOUND E_STATUStake back a message (^ui-chat-39): the person's own session, their own message while it is still sent (no seen, no claimedBy) → 200 {removed, chat}: it leaves the chat and no next hands it; the intents read from it end; a pad CONTINUE's version is open again. Already picked up or claimed 409 E_STATUS; an agent's or d2's message, or any AI token, 403 E_SCOPE; another person's chat, or no such message, 404 E_NOT_FOUNDDELETE /api/v2/chats/<id>/context/<kind>:<ref> chats E_FORBIDDEN E_NOT_FOUND E_STATUStake one thing out of the chat's context (^ui-chat-40), and with no <kind>:<ref> (DELETE /api/v2/chats/<id>/context) every one (Clear): the chat's person, in their own session → 200 {chat, dropped, context}; it stays out until a message names it again; an AI token 403 E_FORBIDDEN; an ended chat 409 E_STATUS (its context is kept, read-only); a thing not in it 404 E_NOT_FOUND; a kind not one of item, topic, thought, story, focus, page, agent 400 E_ARGGET /api/v2/chats/<id>/pad chats E_METHOD E_NOT_FOUNDthe person's pad with the chat's role, Pad:<role> (tags: pad), which outlives the chat: {name, tags, latest, keep, versions: [{v, msg, chat, at, title?, answered?, state?: {on, notes, at}, text}]}: every pad an agent of the role posts in the person's chats is saved as the next version (its message carries padVer), each version names its chat, and the chatpad draws only the open chat's (and the chats it continues) pads: a new chat with the role starts on none, a link to another chat's version says whose it is and asks before going there (P-2343); a chat that kept a pad of its own before answers that one (its person's newest became their pad with the role); every version is kept (what goes is Page:Pad's: DELETE …/pad/<v>) behind a safety cap of 500 (keep), past which the oldest no PinPad of the person's names goes; total: how many there are; ?before=<v> the versions below v, ?limit=n (1 to 500) the newest n of them, more: true when older ones are left; GET …/pad/<v> one version {version} (deleted or never: E_NOT_FOUND); the chat's person (their session) and the chat's own agent (its token) only: an agent invited into it E_SCOPE, any other agent of the person.s or another person 404 (^ui-chat-33); it is no topic of the project, so no list or search shows itPUT /api/v2/chats/<id>/pad/<v> chats E_BAD_JSON E_NOT_FOUND{on: [the indexes of its checked inputs], notes: [its note boxes' text], asks?: [{g: the group's index, text}] (the entries of its ?, ^ui-turbo-17)}: the person's session keeps what they ticked, typed and asked in pad version v until it is answered (a reload or another device shows it); all empty clears it → {version}; an AI token E_SCOPE; a version pruned E_NOT_FOUND; a CONTINUE posted with pad: v answers it and the kept state goesDELETE /api/v2/chats/<id>/pad/history chats E_NOT_FOUNDthe pad's Clear all (P-1649), kept for old callers: the person's own session deletes every earlier version of their pad with the chat's role but those a PinPad of theirs names (^dd-22) → {name, deleted: n, latest}; the newest version stays answerable and the numbering carries on; an AI token E_SCOPE, another person 404DELETE /api/v2/chats/<id>/pad/<v> chats E_NOT_FOUND E_OPEN E_PINNEDone version of the person's pad deleted (^dd-22; Page:Pad's own Clear all and keep rules) → {name, deleted: v, latest}; the person's own session only (an AI token E_SCOPE, another person 404); the open version, the newest, 409 E_OPEN; deleted or never E_NOT_FOUND; a version a PinPad of the person's names is 409 E_PINNED: unpin it firstPOST /api/v2/chats/<id>/join chats E_NOT_FOUND E_STATUSthe agent's own token: asked → live; a live chat is taken over (^chat-12, ^chat-35) by any of the chat's person's own agents of its role (an agent token of theirs, minted here from a role token of theirs with that role: no status naming the chat, no startedBy), or an agent started for it (its status chatting, chat: <id>, with startedBy, the chat's role, and its token's role when it carries one), when the chat's agent is gone, done, stopped or quiet, its token ended or revoked, or its status handing that chat over: it becomes the chat's agent (seen and done are its, the old one's E_SCOPE) and d2 adds <new> joined (took over from <old>) → {chat, tookOver}; a joiner not started for the chat E_SCOPE; a live chat whose agent still runs E_STATUS; an asked chat that continues an ended one, by an agent with a live chat of its own, E_STATUS {agent} (^chat-22); an asked chat for the joiner (its agent) is joined whatever started it, one open to any agent of its role only by an agent someone started or the chat's person's own agent of that role, its token theirs (else E_ARG; another person's agent never reaches it: 404); a chat gone, ended or not the joiner's 404 E_NOT_FOUND {chat, next}: next the person's newest open chat of its role it may join, never the one asked for, or null (^pipe-97). A live chat whose agent ends (done, gone, stopped, its token ended) and that no fresh agent takes over by the sweep after next ends, why agent, d2 adding <agent> has finished: the chat is ended (^chat-5); handing-over doesn't end it; the person's own agent (its token's role) refused a join, a message or a next gets details.next (^chat-38): {method, path, body, by?}, the call that works (its join, its message, the person's open chat of its own role, or for an ended chat POST /api/v2/chats {role, subject, continues}, the person's Start again), or {none: why}; one of another role E_SCOPE {role, next} instead of 404POST /api/v2/chats/<id>/leave chats E_BAD_JSON E_NOT_FOUND E_STATUS{text?}: an invited agent leaves the chat (its claims let go, d2 adds <agent> left); the agent in the chat, with its own token, hands it to the next agent of its role: text (default starting new <role>) is posted as its message, the chat is asked again with no agent and any agent of the role may join; anyone else E_SCOPE, an ended chat E_STATUSPOST /api/v2/chats/<id>/invite chats E_BAD_JSON E_NOT_FOUND E_STATUSa second agent, on the person's word (^ui-chat-33): the person's session or the chat's agent (its own token) invites one of the person's running agents, or a role's background agent (one someone started, not in this chat, between passes before busy, the newest first; none E_STATUS {role}), into a live chat (else E_STATUS) → 201 {invited: {agent, as}, chat} (again: 200, its as changed); the chat's with lists it, d2 adds <by> invited <agent> as <as> and the agent's next carries the chat (chats: [{id, invited}]); a reviewer's messages are asides, seen by the agents in the chat only, never by the person nor counted as answers; a pair's show to the person and answer as the chat's agent's do; anyone else E_SCOPE, an agent not the person's E_SCOPE, the chat's own agent E_ARG; DELETE …/invite/<agent> (the person or the chat's agent) takes it out, its claims let goPOST /api/v2/chats/<id>/claim chats E_BAD_JSON E_CLAIMED E_NOT_FOUND E_STATUSclaim before you work (^ui-chat-33): an agent in the chat (its own token) takes the person's open messages ids → {claimed: [ids]}, each picked up (seen); the first claim wins: one standing for another agent is 409 E_CLAIMED {by, at, note?, id} and nothing is claimed; a claim stands until a reply of its agent names the message in answers (or answers it), the message is done, or its agent lets it go (DELETE …/claim/<id>), leaves or is taken out; next shows each claimed message with claimedBy and never hands another agent's; an agent's message with aside: true goes to the agents in the chat only (never the person's, never an answer); the person's session, the launcher or an agent not in the chat E_SCOPEPOST /api/v2/chats/<id>/seen chats E_BAD_JSON E_NOT_FOUND E_UNDONE{upTo: <message id>}: the chat launcher's tool token, or the agent in the chat, says its pass was handed the person's messages up to upTo: each without seen gets seen: <ISO>, never changed after; an ended chat is fine; an unknown id E_ARG, anyone else E_SCOPE; when the agent already holds some picked up and not done (no reply answers them, not marked done) and these would make more than 3, 409 E_UNDONE {undone: [ids]} naming them and nothing is marked (with none held, the whole batch is taken)POST /api/v2/chats/<id>/done chats E_BAD_JSON E_NOT_FOUND E_ORDERanother chat or urgent work heading the agent's next is E_ORDER {batch}; {ids: [message ids]}: the agent in the chat, with its own token, is done with these of the person's messages: each gets done: <ISO> (the panel draws it light green), never changed after; each must be the person's in this chat and already seen, else E_ARG naming it and nothing changes; the person's session, the launcher's tool token or another agent E_SCOPE → {done: <count newly done>, chat}POST /api/v2/chats/<id>/alive chats E_BAD_JSON E_NOT_FOUND E_STATUS{typing?: true, draft?, open?, page?}: the person's open page, kept on the chat as alive; open: true while the chat panel shows this chat (expanded or docked), false when it is collapsed or shows another chat, page: the tab's path (else E_ARG), kept in memory as the chat's panel, older than 70 s counting as closed (P-1768); neither a person being away nor a quiet spell ever ends a chat (^chat-5); typing: kept for old panels, it holds nothing; draft: the box's unsent text, held in memory, saved on the chat only if d2 ends it (budget, its agent ended), for the next chat with the rolePOST /api/v2/chats/<id>/end chats E_NOT_FOUND{why: done | cancel}: ends it; why busy 400 E_ARG (^chat-37: an agent mid-step keeps its chat); the joined agent's board state goes back to what it was; budget and agent are d2's own (idle and away only on chats ended before); an agent invited into it E_SCOPE (it leaves instead, ^ui-chat-33)POST /api/v2/chats/<id>/stop chats E_METHOD E_NOT_FOUND⏹ Stop (P-2537): the chat's person, or one of the project's admins, in their own session (an AI token E_SCOPE; another person's chat 404): a smart agent's run ends after the model call in flight, never mid-write, takes no further turn and posts *Stopped by you*; its spend so far is charged. → {stopped: true}; a second stop, none going, or an API agent's run on the person's own key → 200 {stopped: false}. While one may be stopped the chat's read carries stoppable: true; after it, runStopped {at, by} until a run takes what was queuedPOST /api/v2/chats/<id>/answer-now chats E_METHOD E_NOT_FOUND E_STATUSAnswer now (P-2537): the person's session, after a stopped run: one run takes every queued message together → 202 {started: true}; nothing queued, or a run going → 200 {started: false}; an ended chat E_STATUSPOST /api/v2/chats/<id>/new-subject chats E_BAD_JSON E_FORBIDDEN E_NOT_FOUND E_OFF E_STATUSNew subject (^chat-28, ^ui-chat-41): the person's own session ends the open chat as Done does (why done, the agent writes its summary, ^chat-6) and starts a new chat with the same agent (fresh: Start a fresh POST /api/v2/chats/<id>/cancel chats E_NOT_FOUNDthe same as end with why cancelPOST /api/v2/waivers cons-ui E_BAD_JSON E_METHOD E_NOT_FOUND E_WAIVER{rule, task?, chat, message}: an AI token, in a live chat of its person, naming the person's message → 201 {id: W-n}, audited A_WAIVER; anything else, or a rule on the floor (tokens, ai-access, permissions, vault, deletes, moving work to another person) → E_WAIVERGET /api/v2/intents?live=1 chats E_METHODthe caller's person's intents on this project, newest first → {total, data: [{id, action, targets, details?, for?, line, from: {chat, message}, text, at, until, live, usedBy, used?, relayedTo?, ended?: {at, by, why}}]}: d2 reads each of the person's chat messages (and a pad's CONTINUE) into intents and signs them (^dd-6, ^dd-7); line is what the chat shows, ⇒ topic.publish(Landing, Landing1); ?live=1 only those not ended, not past until (intents.ttl in Settings:System, 2 h) and with their chat open. The person's session or their own AI tokenGET /api/v2/intents/<id> chats E_NOT_FOUNDone intent of the caller's person → {intent} as in the list; another person's, or none, E_NOT_FOUNDPOST /api/v2/intents/<id>/end chats E_NOT_FOUNDEnd on its line (^dd-10): the person's own session ends it → {intent} (ended why ended); an AI token E_SCOPE; one already ended answers as it is. Citing an ended intent is E_INTENT (cause ended)POST /api/v2/intents/<id>/relay chats E_INTENT E_NOT_FOUNDan agent that may cite it hands it on (^dd-9): to, another agent of the same person (dispatcher-15; a role name widens nothing), may then cite it, for its own action and targets only → {intent} with relayedTo; an agent that may not cite it E_INTENT {cause}; the person's session E_SCOPEPOST /api/v2/intents/misread chats E_BAD_JSON E_NOT_FOUNDd2 read a message wrong (^dd-16): the person's agent or their session (the line's Wrong?) says so: message, the person's chat message id; read, what d2 showed (left out: the lines of the message's intents, or nothing); should, what it should have been (1 to 500 characters); intent, one of the message's, which ends (why misread) → 201 {misread}, audited G_MISREAD and listed on the Gate tab's Misreads; a message not the person's E_NOT_FOUNDPOST /api/v2/intents/read chats E_BAD_JSON E_METHOD E_OFFthe reader's test bench (^dd-28, Page:Intents): each line read as a chat would read it → {ok, lines: [{readings: [{action, targets, details?, for?, shown}]}], version}; shown is the chat's ⇒ line; the names read against are the chat's own (the project's topics and drafts, and the Page:/App: names it borrows through pages-from for the caller) plus topics, or, with topicsOnly: true, topics alone; page, a topic name or a path, filled only when the text points at it; items, the item ids in context; an empty text reads []. Nothing is signed, kept or citable, no audit line. Members of base d2 and d2spec, on that project, in their session or through their AI's token; anyone else E_SCOPE, a visitor 401; Settings:Project intents: off E_OFF; more than 200 lines, a line over 1,000 characters, E_ARG {field}GET /api/v2/intents/verbs chats E_METHODthe intent verbs (P-2254): d2's dictionary's class messages that carry @covers, the one list the reader signs, the $ assist offers first and the gate covers by → {ok, verbs: [{verb, covers, line, words}]}: verb topic.update, covers the route actions it stands for (topic.write, topic.append, draft.save, project.setSettings), line the assist's covers write, append, draft.save, words what the reader reads it byGET /api/v2/intents/names?q=<prefix>&name=<target> chats E_METHODthe names a typed intent's target may be (P-2254), as the reader knows them here (on d2spec base d2's as d2.<name>): names, at most 50 starting with q (any case; then those with a later part that does); with name, {name, exact, line?, near?}: line the near miss the box shows before sending (no topic Settings:UserSettings on d2; did you mean UserSettings?); a member's, else E_SCOPEPOST /api/v2/signed/verify chats E_BAD_JSON E_METHOD E_SIGNEDis a forwarded message your person's word (P-2093)? Every message a person posts in a chat from their own session carries sig, d2's askedBySigned value over {person, project, chat, message, the text's hash, at, until} (until: at + intents.ttl, 2 h); an AI token's never does. An agent forwards it with the person's exact words; the receiver checks them here: askedBySigned, text the words verbatim, intents (ids) the readings it was handed, if any → {ok, person, chat, message, at, until, used: [{by, at}], intents: [{id, line, live}]} (the intents d2 read from that message). 409 E_SIGNED {cause}: unknown (not d2's, or edited), project, person (not the caller's person's), expired, text (not the words signed), message (no longer kept), intent (one d2 didn't read from it). Checking spends nothing; an agent marks it used by citing it on its status (askedBySigned). The caller's session or their own AI tokenPOST /api/v2/overrides chats E_BAD_JSON E_DEMIGOD E_OFFa one-time override (^dd-25): the agent refused something its person asked for in this chat asks d2 → 202 {override: <id>, again?, shown}; action, the route action it was refused (topic.publish) or an intent verb (topic.update); target, what it acts on (the topic, the item); args, the call's body as it will repeat it (a JSON object, or the markdown text) and query its query string, which an Allow once is bound to; refused, the refusal's code (E_SCOPE or E_INTENT) and rule (the permission row that refused it, in person mode for the agent's role); why, the person's ask in their words (1 to 300 characters); chat, the person's live chat the agent is in, on this project or another of theirs (a member there; left out: the newest one, this project's first, else the newest on any; P-2276). d2 draws its d2 · Authorize override box in that chat, wherever it lives (the override stays this project's, chatOn naming the chat's project, and the answer given there applies here) (a d2 entry, kind override, shown: true); with no live chat, shown: false and nothing is drawn. One open ask per agent, action and target: asking again answers the same id (again: true). Not overridable, E_ARG {field, reason}: a locked row or a design fence, a row not in person mode, any other code (a state refusal stands), an item that is closed. A pipeline item's state refusal (E_CLOSED, E_STATUS on a dropped, moved or done P-n) is asked only by an agent of that item's person (who it is for, who asked for it, or the project's owner; ^dd-35), its row person or asks; a closed item otherwise only for its restore or reopen; a move never (locked). A god-only action (^dd-31: a who: god route, named as target by its action when its path has none; a god page's publish; a draft of Settings:System, Quotas or Permissions, topic.update) is the Architect's alone: asked of him it is marked god (refused may then be E_FORBIDDEN or E_AUTH, rule the action itself), his box offers Allow once and No only; asked of anyone else, reason god-only; the god settings' publish never (reason god-settings). Setting or taking off view: god or edit: god (^show-7, ^show-10) is one too: action draft.save or topic.publish, refused {code: E_SCOPE, rule: access.god} (the refusal names that rule), args the draft's text (a publish: the agent's draft); the box spells the change out (change: Page:Vault: set edit: god); args that change no god level, E_ARG field args. the Architect's target may name another project he owns, d2.Toolbox (^dd-33; anyone else's is their chat's project's); one naming a project the asker's person doesn't reach from here, E_ARG {field: target, reason: project}. The target is kept in one form, this project's thing bare (d2.Vault asked on d2 is Vault), another's as GET /api/v2/overrides/<id> chats E_NOT_FOUNDone override of the caller's person → {override: {id, agent, action, target, name (the row in plain words), refused, why, chat?, chatOn?, at, until, state: open|once|chat|done, answer?: {answer, by, at}, used?, ended?: {at, by, why}}}: what the box draws (never from a message's text); GET /api/v2/overrides lists the person's, newest first; another person's, or none, E_NOT_FOUNDPOST /api/v2/overrides/<id> chats E_BAD_JSON E_NOT_FOUND E_OFF E_STATUSthe person's answer in the box (^dd-26), from their own session on a page d2 draws, on any d2 host whose session shows the override's chat (its own project's, or another of the person's, as the chat list does, or the one its chat is on, chatOn): once signs a grant for that agent, action, target and arguments, used once within 10 min; chat (Allow for this session: the session is the chat) one for that agent, action and target with any arguments, until the chat ends; no ends it → {override}; the answer goes into the override's chat as the person's own line, so the agent's next hands it as any message of theirs. The agent repeats its call with override: <id> in its body or D2-Override: <id>, and the permission row lets exactly that call through (G_GATE pass, check override, who said yes); a once grant is then spent. An AI token, an app, an embed or a frame (any page but d2's own) E_SCOPE; answered already or lapsed unanswered (10 min) E_STATUS; another person's E_NOT_FOUND; intentOverride: off E_OFF; chat on a god-only one E_ARG (Allow once or No)POST /api/v2/overrides/<id>/end chats E_NOT_FOUNDEnd on the box's line: the person's own session ends a session grant (or an open ask, as a No) → {override}; an AI token, an app, an embed or a frame E_SCOPE; one already over answers as it isGET /api/v2/cdn cdn E_NOT_FOUND E_PAGE_MISSINGthis project's files, a page at a time: {total, host, types, usage, folders, data, limit, capped?, next?}; ?tag= ?prefix=; ?q= a part of the path or a tag (case ignored), applied before the page is cut, so total counts the matches; next carries the q (sent back with no q it pages the same matches, with another q it is E_ARG); 100 unless ?limit= says, at most 100 (api.list.max; more is 100, capped: true); next, sent back as ?cursor= with the same query, the next page; folders: [{prefix, about, cleanable}], the special folders and what each isGET /api/v2/cdn/<path> cdn E_NOT_FOUNDone file's record, with its urlPUT /api/v2/cdn/<path> cdn E_CONSENT E_FORBIDDEN E_NOT_FOUND E_TOO_BIGupload (members); ?tags= ?access=public|members; under input/ (the import, P-2666; also as /api/v2/cdn/<this project>/input/<file>) only after the person accepted the rights notice (403 E_CONSENT, details.consent its route), always members only, docx and pptx too; an AI token's new file goes under <role>/ and its folders, else 403 E_SCOPE reason folder (a tester token alone: a new file under tester/<its run>/ only, no query); ?askedBy=<its person> to write elsewherePATCH /api/v2/cdn/<path> cdn E_FORBIDDEN E_NOT_FOUND{tags, access}; its uploader or a mod; a file under input/ stays members only (access public is E_ARG)DELETE /api/v2/cdn/<path> cdn E_FORBIDDEN E_NOT_FOUNDits uploader or a modPOST /api/v2/cdn/cleanup/check cdnadmins: the files under the cleanable folders, {used: [{path, size, refs}], unused: [{path, size, updated}], checked}POST /api/v2/cdn/cleanup/delete cdnadmins: {paths}; deletes only those the same request finds unused: {deleted, kept: [{path, refs}]}POST /api/v2/cdn/cleanup/send cdnadmins: {paths}; files one designer item listing each still-linked file and where: {item}POST /api/v2/cdn/sweep cdn E_NOT_FOUND E_PARSEthe Architect or his ops agents (^sys-14): {dryRun?, project?}; every project's GET /api/v2/razadmin/cdn cdnRaz Admin's CDN status (^raz-9, ^raz-10), the Architect or his ops agents (anyone else E_SCOPE), from the file index: {at, projects: [{project, files, bytes, quotaBytes|null, archive: {files, bytes}, cleanable: {mockups, tmp}}], totals: {projects, files, bytes, archiveBytes, cleanableBytes}, biggest: [{project, path, bytes, updated}] (20), lastSweep: {at, by, dryRun, removed: {files, bytes}, perProject, errors} | null}; cleanable is what the sweep would take nowPOST /api/v2/diesel/parse eng E_NOT_FOUND{source} → {ok, errors, decls}POST /api/v2/diesel/expr eng E_LIMIT E_SECRET E_SITE{source, lenient?, ast?, vars?} → {ok, results[]}; one statement per line, open (no token): evaluation is pure, but a web call (http.*) needs a signed-in caller (E_AUTH in its result), and http.post/http.patch a member of the project (E_SCOPE) and a host in its apps.sites (E_SITE), within the outbound caps (E_RATE); secret() is refused here (E_SECRET: only a run masks it)POST /api/v2/diesel/reset engback to the demo: topics, domain and objects reload from the seed topics; needs the tokenPOST /api/v2/diesel/run eng E_MAILER_OFF E_NATIVE E_NOT_FOUND E_SECRET E_SITE{spec, story, scratch?} → {ok, phase, errors, payload, flow, tests, trace}; spec/story are text or {topic}; open, but its web calls (http.*) need a signed-in caller (E_AUTH in errors), and http.post/http.patch a member of the project (E_SCOPE) and a host in its apps.sites (E_SITE): scratch runs send for real; writes are capped a minute per project and per box (E_RATE in errors, ^http-12) and each leaves an outbound log line; secret("name") reads a vault entry allowed to rules here, your own (E_SECRET otherwise), masked in all the run answersGET /api/v2/diesel/engines engthe last 5 flows: {total, keep, data}; HTML twin /flowsGET /api/v2/diesel/engines/<id> eng E_NOT_FOUNDone flow: result, expects, trace, syntax tree; HTML twin /flows/<id>GET /api/v2/openspec designthe OpenSpec topics as an openspec/ tree: {total, changes, files}, each checkedGET /api/v2/openspec/changes?spec= designchanges with status, date, deltas and tasksGET /api/v2/domain/overview domainclasses, their links, enums, and the model checks (errors, warnings); HTML twin /domain/overviewGET /api/v2/domain/cat/* domainevery class in full: fields, kinds, annotations, inverses, messages, and its states when the dictionary has them (P-2248)GET /api/v2/domain/cat/A,B domain E_NOT_FOUNDsome classes in full, each with its states (the dictionary's: states, groups, transitions with source, fixed) when it has them; a class only the dictionary has answers {name, states}; HTML twin /domain/cat/AGET /api/v2/domain/views domainwhere each object opens, for the link resolver (^link-1): {stamp, project, domain, views: {Class: view}}, base d2's link classes (Item, Thought, Story, Page, App) with the project's own on top; ETag the stamp, 304 when unchangedGET /api/v2/domain/refs?r=Spec%5Eaudit-4&r=P-1 domainbare references for a browser (^link-ref-1): each r (the first 100) that links for this reader → {refs: {: href}}; a topic that isn't there and one the reader may not open are both left outGET /api/v2/dom/val/Company data E_NOT_FOUND E_NO_CLASS E_PAGE_MISSING E_PARSE E_PRIVATE E_QUERY E_STORE E_TYPElist, a page sorted by key: {total, class, data, limit, capped?, next?}; limit, cursor; capped, next: 100 unless ?limit= says (api.list.default), at most 100 (api.list.max; more is 100, capped: true); next is the last key, in clear, sent back as ?after=GET /api/v2/dom/val/Company?query=stage%20is%20%22Producer%22&limit=100 data E_NO_CLASS E_PARSE E_QUERY E_STORE E_TYPEthe query in action, one database filter with the project and the viewer's rules addedGET /api/v2/dom/val/Company:FLR data E_NOT_FOUND E_NO_CLASSa widget's call (X-D2-Embed) on a class that is own for it reads only its own objects, another's is 403 E_SCOPE {class, verb, scope: own} (^embed-3); one object by WPATH ([d2.]Class:key); another member's object of a class @viewBy=owner is E_NOT_FOUND; HTML twin /dom/val/Company:FLRPUT|DELETE /api/v2/dom/val/Class:key data E_ACCESS E_NOT_FOUND E_NO_CLASS E_ORIGIN E_QUOTA E_STOREupsert (the URL key fills the key field) or delete; checked against the class; another member's object of a class @viewBy=owner or @editBy=owner is E_ACCESS, a mod's and an admin's change too; a failed store write is E_STORE, nothing kept to resend; a runaway project gets 429 E_RATE with Retry-After on every nth write (^quo-5)POST /api/v2/dom/val/Class data E_ACCESS E_EXISTS E_NO_CLASS E_QUOTA E_STOREcreate; 409 if the key exists; 403 E_QUOTA past the plan's objects (counted from the usage record, ^quo-1)POST /api/v2/admin/usage//lift data E_METHOD E_NOT_FOUNDthe Architect, on his own session: lifts a runaway project's brake at once (^quo-5): {ok, project, lifted}POST /api/v2/dom/batch data E_BAD_JSON E_METHOD E_QUOTA E_VALIDATIONmany objects across classes in one request: {mode: all | each, writes: [{op: put | create | delete, cls, key, data?}]} → {ok, written, results: [{i, cls, key, result}]}; mode all (the default) writes nothing if one is bad (422 E_VALIDATION); up to 200 writes and 2 MB (E_QUOTA); an AI token pays one write per 50 objectsGET|PUT|DELETE /api/v2/dom/<Class>/<key> data E_METHOD E_NOT_FOUND E_NO_CLASSthe v0.1 form, kept as an aliasGET /api/v2/domain/overview?of=d2|base domain E_DEMIGOD E_FORBIDDENthe Architect only: the platform's own model or the base project'sGET /api/v2/d2dom/<class>?cursor=&limit=&q=&<field>= data E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_CLASS E_QUERYd2's own objects (^dd-18, ^dd-30): on a project its members', on base d2 every signed-in account's own (^obj-10, their session); a d2 class's objects (D2Object and what extends it: PinPad), sorted by id: {total, class, data, limit, capped?, next?}; each {id, project, user, at, by, ver, …its fields}; yours in this project for a person class, the project's for @scope("project"), d2's for @scope("d2") (read by its @viewBy); limit 100, at most 100 (more is 100, capped: true); next is the last id, in clear: send it as ?after=GET /api/v2/d2dom/<class>/<id> data E_FORBIDDEN E_NOT_FOUND E_NO_CLASSone d2 object, or 404; its version in ver and the ETag (v<n>)PUT /api/v2/d2dom/<class>/<id> data E_BAD_JSON E_CHANGED E_FORBIDDEN E_NO_CLASS E_TOO_BIG E_VALIDATION E_VERSION_REQUIREDwrite a whole d2 object → {ok, entity} at its next ver: id from the address, project, user, at, by and ver set by d2 (the same in the body are dropped); If-Match: v<n> (none: make it); a stale one 409 E_CHANGED {current}, needed for a shared class (428 E_VERSION_REQUIRED), left out for a person class's last write wins; checked against its class (E_VALIDATION); at most 16 KB (413 E_TOO_BIG); a shared class's @editBy (else 403 E_SCOPE); an AI token as d2object.write allows (person 403 E_SCOPE, asks 202 {approval}, tells written and its person told, free written); raises diesel.d2dom.<Class>.on.put; ApiBudget (the daily allowance, ^apiag-6): the person's session only (any token 403 E_SCOPE), costDay null for no limit, the Architect's session sets another's with ?user= or the body's handle (P-2253)PATCH /api/v2/d2dom/<class>/<id> data E_BAD_JSON E_CHANGED E_FORBIDDEN E_NOT_FOUND E_NO_CLASS E_TOO_BIG E_VALIDATION E_VERSION_REQUIREDchange some fields of a d2 object → {ok, entity} at its next ver, the fields not sent kept (^dd-38); If-Match: v<n> needed (428 E_VERSION_REQUIRED), a stale one 409 E_CHANGED {current}; not there, 404; who may, as PUTDELETE /api/v2/d2dom/<class>/<id> data E_FORBIDDEN E_NOT_FOUND E_NO_CLASSdelete a d2 object → {ok}; a person's session only (an AI token 403 E_SCOPE), a shared class's @editBy; raises diesel.d2dom.<Class>.on.deleteGET /api/v2/d2 datad2's built-in entities on /api/v2/d2 (^dd-42): {ok, entities: [{entity, action, key, editBy, fields: [{name, type, editable?, editBy?}], actions: [{action, args}], point}]}GET /api/v2/d2/Agreement?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYAgreements you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Agreement/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Agreement (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/me/consents is its aliasGET /api/v2/d2/Dismissal?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYDismissals you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Dismissal/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Dismissal (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/me/dismissed is its aliasGET /api/v2/d2/Notice?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYNotices you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Notice/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Notice (its ver in the ETag, v<n>); who may, as its point route saysGET /api/v2/d2/SupportAccess?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYSupportAccesss you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/SupportAccess/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone SupportAccess (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/support/accesses is its aliasGET /api/v2/d2/Token?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYTokens you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Token/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Token (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/tokens is its aliasGET /api/v2/d2/Agent?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYAgents you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Agent/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Agent (its ver in the ETag, v<n>); who may, as its point route saysGET /api/v2/d2/AgentStart?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYAgentStarts you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/AgentStart/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone AgentStart (its ver in the ETag, v<n>); who may, as its point route saysGET /api/v2/d2/AgentStatus?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYAgentStatuss you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/AgentStatus/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone AgentStatus (its ver in the ETag, v<n>); who may, as its point route saysGET /api/v2/d2/AuditRecord?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYAuditRecords you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/AuditRecord/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone AuditRecord (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/audit is its aliasGET /api/v2/d2/Chat?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYChats you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Chat/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Chat (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/chats/<id> is its aliasGET /api/v2/d2/Guardian?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYGuardians you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Guardian/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Guardian (its ver in the ETag, v<n>); who may, as its point route saysGET /api/v2/d2/Message?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYMessages you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Message/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Message (its ver in the ETag, v<n>); who may, as its point route saysGET /api/v2/d2/Comment?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYComments you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET|PATCH /api/v2/d2/Comment/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Comment (its ver in the ETag, v<n>); PATCH changes text, tags, state (any other field E_ARG {field}) with If-Match: v<n> (428 E_VERSION_REQUIRED without, a stale one 409 E_CHANGED {current}) → {ok, entity}; who may, as its point route says; the point route GET /api/v2/comments/<id> is its aliasPOST /api/v2/d2/Comment data E_FORBIDDEN E_NOT_FOUND E_NO_ENTITY E_PARSE E_QUOTAa new Comment → 201 {ok, entity}; the point route POST /api/v2/topics/<topic>/comments is its aliasPOST /api/v2/d2/Comment/<id>/resolve data E_CHANGED E_FORBIDDEN E_NOT_FOUND E_NO_ENTITY E_PARSEcomment.resolve on one Comment → {ok, entity}; If-Match: v<n> checked when sent (409 E_CHANGED); POST /api/v2/cmd/comment/resolve {key: <id>, …} is the same; the point route PATCH /api/v2/comments/<id> is its aliasPOST /api/v2/d2/Comment/<id>/answer data E_CHANGED E_FORBIDDEN E_NOT_FOUND E_NO_ENTITY E_PARSEcomment.answer on one Comment → {ok, entity}; If-Match: v<n> checked when sent (409 E_CHANGED); POST /api/v2/cmd/comment/answer {key: <id>, …} is the same; the point route PATCH /api/v2/comments/<id> is its aliasGET /api/v2/d2/Cron?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYCrons you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Cron/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Cron (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/crons is its aliasGET /api/v2/d2/Flow?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYFlows you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Flow/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Flow (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/diesel/engines/<id> is its aliasGET /api/v2/d2/Run?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYRuns you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Run/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Run (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/runs is its aliasGET /api/v2/d2/StepMessage?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYStepMessages you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/StepMessage/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone StepMessage (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/steps is its aliasGET /api/v2/d2/HistoryEntry?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYHistoryEntrys you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/HistoryEntry/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone HistoryEntry (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/history/<id> is its aliasGET /api/v2/d2/Approval?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYApprovals you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Approval/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Approval (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/approvals/<id> is its aliasGET /api/v2/d2/Item?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYItems you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Item/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Item (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/pipeline/<id> is its aliasGET /api/v2/d2/Project?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYProjects you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Project/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Project (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/me/projects is its aliasGET /api/v2/d2/Change?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYChanges you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Change/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Change (its ver in the ETag, v<n>); who may, as its point route saysGET /api/v2/d2/Code?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYCodes you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Code/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Code (its ver in the ETag, v<n>); who may, as its point route saysGET /api/v2/d2/File?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYFiles you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/File/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone File (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/cdn/<path> is its aliasGET /api/v2/d2/Focus?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYFocuss you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Focus/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Focus (its ver in the ETag, v<n>); who may, as its point route saysGET /api/v2/d2/Thought?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYThoughts you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Thought/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Thought (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/thoughts/<id> is its aliasGET /api/v2/d2/Draft?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYDrafts you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Draft/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Draft (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/drafts/<id> is its aliasGET /api/v2/d2/ToDo?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYToDos you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/ToDo/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone ToDo (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/backlog is its aliasGET /api/v2/d2/Topic?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYTopics you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Topic/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Topic (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/topics/<id> is its aliasGET /api/v2/d2/Version?cursor=&limit=&q= data E_FORBIDDEN E_NO_ENTITY E_QUERYVersions you see, a page at a time: {ok, entity, total, data, limit, capped?, next?}; limit 100, at most 100 (api.list.max; more is 100, capped: true); next is the last id, in clear: send it back as ?after=GET /api/v2/d2/Version/<id> data E_CHANGED E_FORBIDDEN E_METHOD E_NOT_FOUND E_NO_ENTITY E_PARSE E_VERSION_REQUIREDone Version (its ver in the ETag, v<n>); who may, as its point route says; the point route GET /api/v2/topics/<topic>/history/<ver> is its aliasGET /api/v2/me accwho is asking, here: {project, known, email, role, god, addons (in a person's session: the add-on ids they hold, every one for the Architect), confirmed, join, tz, vimChecked}; confirmed: the person's email is confirmed (the Architect always), so a New project page can offer every kind (^d2land-5); vimChecked: true once the person passed the editor's Vim check (absent before); an AI token answers for its person, with via: token. tz is the person's own time zone, IANA (null when unset): tell them times in it, never in UTCPUT /api/v2/me {chat} chats E_BAD_JSON E_EXISTS E_FORBIDDEN E_NOT_FOUND E_PLAN E_QUOTA E_TOO_BIG{chat: true | false}: chats on or off, in your own session (any token E_SCOPE); any other field E_ARG. On makes your chat launcher's tool token, in your vault as d2-chat-launcher and allowed to your monitor tokens → {chat, launcher, secret, allowedTo}; off revokes it and deletes the secret → {chat, revoked}PUT /api/v2/me {chatPanel} chats E_BAD_JSON{chatPanel: {pinned?: true | false, open?: <chat id> | null}} (P-2103): the chat panel's state, kept on the account so a pinned chat reopens docked and open on the same chat on every d2 host and in a new tab; the panel writes it when the person pins, unpins, opens or folds it, and every page's panel state carries it (pin); unchanged saves nothing → {ok, chatPanel}. The person's own session only (a token E_SCOPE); anything else in chatPanel E_ARGPOST /api/v2/me/vim-check acc E_BAD_JSON E_METHOD E_TOO_BIG{answer}: the editor's Vim check, from your session: dw (or diw, daw) answers {ok, vimChecked: true} and the editor never asks you again, anywhere; anything else is 422 E_ARG and nothing is keptGET /api/v2/me/consents acc E_METHOD{data}: your agreements, newest first; your session, or an AI token for its personGET /api/v2/me/connect acc E_METHOD{invite: {expires} | null, agent: <name> | null, loop: {agent, at} | null}: Connect your AI's state for you here, read by the block while it waits, and the chat loop listening (your newest agent here whose latest status is Loop on, not ended); your session only (an AI token is E_SCOPE); an embedded widget may read itGET /api/v2/me/dismissed acc E_METHOD{dismissed}: the first-visit intro boxes you pressed Got it on; your own sessionGET|PUT /api/v2/me/onboarding acc E_BAD_JSON E_FORBIDDEN E_METHODYour onboarding run (Story:onboard, P-2664), on the account: GET → {way: new|builder|appuser, level, steps: [{id, title, done, at?, href, more?, once?}], next: {id, title, href} | null, hidden}; each step is ticked by what d2 keeps when it happens (the account, the first project, an agent token or credits, a chat line, an App: page, a Focus, a thought, an invite), never by a click, and a step done before is ticked from the start; a once step passed (the account, the project) is never offered again, nor anything before it; your session, or an AI token for its person. PUT {hidden: true|false}: the strip's ✕ and the user menu's Your next steps, your session or an AI token for its person (a read-only token E_FORBIDDEN)PUT /api/v2/me/dismissed acc E_BAD_JSON E_METHOD E_TOO_BIG{id}: Got it on a first-visit intro box, from your session; the view stays dismissed on every device (GET /api/v2/me has dismissed); id is a view, or getstarted:GET /api/v2/me/projects acc E_LEVEL E_NOT_FOUNDYour projects, as the account home lists them: {projects: [{name, role, level, tags, owner, pinned, lastUsed}], tags, filter: {selected, hidden}, prefix, domain}; prefix: what a new project's name starts with for you (else ""), domain: the root domain of this host, pinned first, then by last used. tags are the project's (its owner's, the same for every member); owner: true on a project you own; pins and the filter are yours aloneGET /myprojects acc E_PAGE_MISSINGMy projects, the account home, on whatever project you are on (^d2land-4): base d2's inherited Page:MyProjects (your projects, New project, your notices), as a signed page (base d2 without it: Page:Projects; with neither, 503 E_PAGE_MISSING: there is no built-in account home, P-1970); /projects and base d2's /home are Page:Projects too; the user menu's My projects and /my-projects land here; a visitor goes to /login?next=/myprojectsPOST /api/v2/account/sidebar acc E_METHOD{sidebar: on|off}, the person's own session: the side panel open or closed on every topic of every project, kept on the account (every device) and written into each page before paint; an AI token E_SCOPE; a visitor E_AUTH (their pick is the d2sb cookie for the domain)PUT /api/v2/projects/<project>/tags acc E_BAD_JSON E_NOT_FOUND E_TOO_BIG{tags: [...]}: replace a project's tags, which every member sees; its owner's (or the Architect's), anyone else E_SCOPE. A tag is 1 to 30 lower-case letters, digits or dashes; at most 20 a project (E_ARG)PUT /api/v2/me/projects/<project>/pin acc E_BAD_JSON E_NOT_FOUND E_NOT_MEMBER E_TOO_BIG{pinned: true|false}: pin it to the top of Your projects; a project you aren't in is E_NOT_MEMBERPUT /api/v2/me/projects/filter acc E_BAD_JSON E_NOT_FOUND E_TOO_BIG{selected: [...], hidden: [...]}: the tag block on Your projects — the projects having all the selected tags show, those with a hidden (red) tag are left out — kept so the page reopens as leftGET /api/v2/notices/stamp acc{stamp, n, prio, flag, island} of what's waiting for you (notices, things to act on; n: how many; flag: the top bar's blocked flag as HTML, empty with nothing waiting; island: {act, news, newsOn}, the top bar island's approvals and news, P-2695): the Notices page syncs on it, every page's navbar polls it for its count and its flagGET /api/v2/push/subscriptions acc E_METHODyour devices that get notifications, in your own session (any token E_SCOPE): {on, subscriptions: [{id, device, at, service}]}; on: false when this box has no push key (push is off, nothing is sent)GET /api/v2/notices accwhat /notices shows, as Page:Notices draws it (P-1915); limit, cursor; capped, next: the notices a page at a time, 100 unless ?limit= says, at most 100 (api.list.max; more is 100, capped: true), total all of them, next sent back as ?cursor= the next page; the cards above them whole: {total, limit, capped?, next?, prio: the broadcast cards [{id, title, text, level, at, bcast?, seen}], invites [{code, by, project, role, as, url}], requests [{project, n, url}], approvals on every project of theirs [{project, id, title, verb?, targets?, go?, url}], notices grouped by code [{id, code?, count, kind, text, link?, at, act?, group?, reply?: {name, asked}, support?: {id, thread, question}}], broadcasts [{id, notice, seen}], urls: {project: address}}; ?code=[&group=] (P-2713): the notices one row rolls up, each {id, code, group?, text, at, kind, link?, act?}, newest first, paged as the notices (a code not N_X-shaped: E_ARG); the person's session or one of their agent tokens; read-only (the actions keep their routes) POST /api/v2/notices/<id>/askops ops E_METHOD E_NOT_FOUND E_PERMAsk ops on a system alert or warning notice (an S_* or N_*_WARN code, ^ops-6), the Architect's own session only (any token or agent 403 E_PERM): files one investigate item for the ops agents (askedBy him, links.notice the notice, following the code's last item), carrying the notice's code, text, count and latest time, and the notices of the code show it (GET /api/v2/notices: askedOps, else lastAsked, each {id, url}) → 201 {item}; an investigate item already open for the code in this project → 200 {item, existing: true}, nothing filed. The item finishes with conclusion (at most 600 characters, else E_ARG), which d2 puts in his Inbox as a thought; its notices clearPOST /api/v2/push/subscriptions acc E_BAD_JSON E_LIMIT E_PUSH_OFF{endpoint, keys: {p256dh, auth}, device}: the browser's push subscription (Prefs → Notifications, Turn on for this device), in your own session → 201 {subscription: {id, device, at}}; the same endpoint again replaces it; endpoint only a browser push service over https (else E_ARG); at most 10 devices (E_LIMIT); a box with no push key E_PUSH_OFFPOST /api/v2/broadcasts/<id>/seen acc E_NOT_FOUNDa broadcast card on your notices counts you once (BCAST-7), as /notices does when it shows it (P-1915); <id> is the broadcast's id or the card's notice id; your own session, on any projectDELETE /api/v2/push/subscriptions/<id> acc E_NOT_FOUNDremoves one of your devices, in your own session; another person's id is E_NOT_FOUND → {subscriptions}GET /api/v2/join/request acc E_NOT_FOUNDthe Join page's state, the person's (session only): {project, member, join, requested}: whether their request to join waitsPOST /api/v2/support/messages support E_FORBIDDEN E_METHOD E_NOT_FOUND E_TOO_BIG{about: <project> | "account", text (1–2000)}: Ask for help, in your own session (any token: E_SCOPE), at most 5 an hour (E_RATE): about a project you own, or your account, to the Architect; about one you're in, to its owner and admins → N_SUPPORT to them, N_SUPPORT_SENT to you; with your email confirmed, also one mail to d2's support box, [d2-support] in its subject, Reply-To your email (^mail-8; answer {…, mailed, hint?}; {page?} names the page you asked from). POST …/messages/<id>/reply {text}: someone the message reached replies (else E_SCOPE) → N_SUPPORT_REPLY on the other sidePOST /api/v2/support/messages/<id>/reply {text} support E_METHOD E_TOO_BIGsomeone the message reached replies, in their own session (any token, or anyone else, E_SCOPE; at most 5 an hour, E_RATE) → {id, replies}; N_SUPPORT_REPLY on the other sideGET /api/v2/support/accesses supportthe Support page's open accesses, the person's (session only): {data: [{id, project, name, expires, support, suspended, claimed}], junk (the junk-folder line)}POST /api/v2/razadmin/users/suspend|resume|delete acc E_CONFIRM E_METHOD E_NOT_FOUND E_ORIGIN E_PROTECTED E_SUSPENDED E_TOO_BIGthe Architect only, in his own session or with an agent of his demigod token (the demigod token itself, and anyone else, E_SCOPE): suspend {email, reason (1–300, shown to them)} → their sessions end and every token of theirs answers E_SUSPENDED; resume {email} → back, with the tokens paused with them; delete {email, typed: the email} → only a suspended account (else E_ARG; a wrong email E_CONFIRM), the same delete as Delete my account. Audited U_SUSPEND, U_RESUME, U_DELETE_USERPOST /api/v2/razadmin/users/reset acc E_METHOD E_NOT_FOUND E_ORIGIN E_SUSPENDED E_TOO_BIG{email, mail: true|false}: the Architect only, in his own session (any token, a demigod's agent too, E_SCOPE; no-store) → mail: true {email, mailed, why?}: the Forgot password? mail, unthrottled, confirmed or not; mail: false {email, link}: a one-time reset link, good for 24 h, shown once. A suspended account 409 E_SUSPENDED. Setting the password on the link confirms the email and ends every session. Audited U_RESET_LINK (never the link); the person gets N_RESET_LINK. The form twin is 🔑 Reset password on All users (POST /god/users/reset)POST /api/v2/razadmin/users/su acc E_FORBIDDEN E_METHOD E_NOT_FOUND E_ORIGIN E_TOO_BIG{email, why (1-200)}: su, the Architect only, in his own session (any token, an AI's or a demigod agent's, E_SCOPE; no-store): his session ends and the answer sets an su session for that person on this host → 200 {email, until, go}; every page and API then answers as for them, marked by a red bar, their writes via: su:support, until Back to him (POST /su/back), log-out or razadmin.suMinutes (30). Not to a god account, a suspended one (E_FORBIDDEN) or none (E_NOT_FOUND). Audited A_SU and A_SU_END (warning); no notice to the person. The form twin is POST /razadmin/members/<id>/su {why}POST /su/back accBack to the Architect (P-2269): an su session only, same origin → the su's sessions destroyed on every host it reached (A_SU_END how: back), a new session for him, 303 to All members; with no su session, 303 to the log-inPOST /api/v2/razadmin/users/confirm acc E_METHOD E_NOT_FOUND E_ORIGINthe Architect only, in his own session or with an agent of his demigod token (anyone else E_SCOPE; no-store): {email, mail: true} → {email, confirmed, mailed, hint?}: their confirm link again, as their own Send the link again (three an hour); {email, note (1–300)} → {email, confirmed: true, already?}: the email confirmed by him, kept with his note (All members says by <him>: note), audited U_EMAIL_CONFIRM. An unknown email 404 E_NOT_FOUND (P-2265)POST /api/v2/razadmin/mail/probe acc E_METHOD E_ORIGINthe Architect only, in his own session or with an agent of his demigod token (anyone else E_SCOPE; no-store): the live mail probe once, now (P-2266): the box mails its own support address [d2-probe] <id>, reads it back over IMAP within 5 minutes and deletes it → {ok, probed: true, at, why?}; a failure is also diesel.mail.on.probeFailed (S_MAIL_PROBE, N_MAIL_PROBE, the monitor told) and the Mailer row red. A box not mailing through gmail (a test box) → {ok: true, probed: false, why}. The release's basic checks want ok: true (mail probe green)POST /api/v2/razadmin/members/<id>/message acc E_DEMIGOD E_METHOD E_NOT_FOUND E_ORIGIN E_TOO_BIG{kind: notice | warning | moderation, subject (one line, ≤200), text (≤5000)}: the Architect only, in his own session or with an agent of his demigod token (anyone else, an admin or an AI token, E_SCOPE) → 201 {to, kind, subject, at, mailed}: the person gets a notice N_ARCHITECT_MESSAGE with no Reply and a mail from Template:mail-direct with no Reply-To (not counted in mail.perDay); kept for their member page; audited A_ARCHITECT_MESSAGE without the text (^mail-10). The form twin is POST /razadmin/members/<id>/message, his session onlyPOST /razadmin/members/<id>/su acc E_ORIGINthe form {why (1-200)}: su… on All members (P-2269), the form twin of POST /api/v2/razadmin/users/su, the Architect's own session only (any token E_SCOPE) → 303 to the person's /home in an su session; a refusal (a god account, a suspended one, a why too long) back to All members with itPOST /razadmin/members/<id>/addons acc E_EXISTS E_NOT_FOUND E_ORIGIN E_PROVIDER E_TOO_BIGthe form {op: award | remove, addon, note}: the Architect only, in his own session (any token E_SCOPE): awards any add-on of Settings:Quotas to the account, or removes the newest it holds of that id, the note saying why (one the person bought ends at the provider at once, ^addon-11; the provider refusing is E_PROVIDER and nothing is removed); audited A_ADDON (^addon-6)POST /api/v2/razadmin/members/<who>/addons/award acc E_EXISTS E_INTENT E_METHOD E_NOT_FOUND E_ORIGIN{addon, note?}: award an add-on of Settings:Quotas to the account (a handle, an email or an account id; ^dd-36): the Architect's (add-ons, plans) or a project admin's (its level, its settings), in their own session or by their agent on their clear intent (D2-Intent) or their Allow once (D2-Override); without it an agent is refused (admin.*: person); audited A_ADMIN, the account's owner told (N_ADMIN)POST /api/v2/razadmin/members/<who>/addons/remove acc E_INTENT E_METHOD E_NOT_FOUND E_ORIGIN E_PROVIDER{addon, note?}: remove the newest of that add-on from the account (one bought ends at the provider too): the Architect's (add-ons, plans) or a project admin's (its level, its settings), in their own session or by their agent on their clear intent (D2-Intent) or their Allow once (D2-Override); without it an agent is refused (admin.*: person); audited A_ADMIN, the account's owner told (N_ADMIN)POST /api/v2/razadmin/members/<who>/plan acc E_INTENT E_METHOD E_NOT_FOUND E_ORIGIN{plan, until?, note?}: a plan's bundle (Settings:Quotas plans, yearly or monthly) on the account, a grant, never a payment; with until (2027-03-01) it goes then: the Architect's (add-ons, plans) or a project admin's (its level, its settings), in their own session or by their agent on their clear intent (D2-Intent) or their Allow once (D2-Override); without it an agent is refused (admin.*: person); audited A_ADMIN, the account's owner told (N_ADMIN)GET /razadmin/page/<name>[/…] acc E_FORBIDDEN E_NOT_FOUND E_PAGE_MISSINGevery Raz Admin page at one address (^blocks-11): base d2's god-only Page:Razadmin<Name> when it is published, else the built-in page of that name (members, quotas, status, logs, audit, monitor, deploy, changes: the seed's changes; users, projects, revenue, inv and data have no built-in page: 503 E_PAGE_MISSING), and its deeper pages (members/<id>[/activity], quotas/<project>); the Architect's session only (a visitor logs in first, anyone else or any token E_FORBIDDEN); anything else 404. The old page addresses (/razadmin/<name>[/…]) are 404, no redirect; their JSON (?format=json, a token's) and form posts stay; /razadmin opens the Toolbox page's Raz Admin tab, /page/Toolbox?tab=razadmin, for the Architect (anyone else: the same log-in or E_FORBIDDEN)GET /api/v2/razadmin/members acc E_FORBIDDENRaz Admin's All members as JSON, a page at a time (limit, cursor; capped, next: 100 unless limit= says, at most 100, api.list.max, more is 100 with capped: true; total all that match; next, sent back as cursor= with the same filters, the next page): {total, limit, capped?, next?, data: [{id, handle, email, plan, addons, status, until, joined, lastActive, projects, members, share, tokens, agents, provider, paid, lastFailed, signedUpBy, move (an old plan's move, P-2226: moving to GET /api/v2/razadmin/audit acc E_FORBIDDENRaz Admin's audit log as JSON, the Architect's session, or one of his agents below (any other token E_SCOPE): archive=YYYY-MM-DD reads that archived day (Archive.readDay) instead of the live records, with the same filters (severity, code, project, text, critical, tests, since, until) and paging (limit, from, next as cursor=; an offset there), the answer carrying archive: GET /api/v2/razadmin/status acc E_FORBIDDEN E_NOT_FOUNDSystem Status as JSON, the Architect's session only: the box's status (samples: the 24-hour series, thresholds, info, heap, service, mongo, mailer, showcase, bus), stats, peaks (the 24-hour peak of each series), copy {of, at, primary, from, active, isPrimary} and routes {hour, day, waits}: the top 20 routes by p95 in the last hour and day, each {action, calls, p50, p95, max, parts: {GET /api/v2/razadmin/inv acc E_FORBIDDEN E_NOT_FOUNDRaz Admin's Data as JSON, the Architect's session only: every inventory {name, icon, note, active, tables, records}, at once: active, tables and records null until a ?name= read them in the last 60 s; ?name=: its tables and records; &table=: {fields, where, total, data}, Find with f.<field>= (contains), the first 50GET /api/v2/razadmin/quotas acc E_FORBIDDEN E_NOT_FOUNDProject Quotas as JSON, the Architect's session only: every project {project, owner, plan, overrides, last?}; ?project=: {project, owner, plan, rows (each with its source and override), overrides, note, problems, runaway}GET /api/v2/razadmin/users acc E_FORBIDDENAll users as JSON, the Architect's session only, a page at a time (limit, cursor; capped, next: 100 unless limit= says, at most 100, api.list.max, more is 100 with capped: true; total all of them; next, sent back as cursor=, the next page): {total, limit, capped?, next?, data: [{id, email, handle, god, confirmed, confirmedBy?: {by, note, at} (P-2265), plan, created, projects: [{name, role}], suspended: {reason, at, by} | null}], plans}; testers' accounts left outGET /api/v2/razadmin/projects acc E_FORBIDDENAll projects as JSON, the Architect's session only, a page at a time (limit, cursor; capped, next: 100 unless limit= says, at most 100, api.list.max, more is 100 with capped: true; total all of them; next, sent back as cursor=, the next page): {newAddresses: {used, cap, freesAt} (this week's), total, limit, capped?, next?, data: [{name, url, join, plan, owner, members, requests, created, tester?, demo?}]}GET /api/v2/razadmin/monitor acc E_FORBIDDENthe Monitor page's data, the Architect's session only (a token E_SCOPE): {monitor: {name, project} | null (no monitor token yet), problem?: {code, message} (the monitor's key isn't set), map: the monitor project's live agents map (what the built-in page draws from: nodes, agents, waits) | null, setup: {project, role, items: [{kind: harness-solo|harness-full|app-loop, setup: {id, kind, expires, roles} | null}]} | null (null unless he is an admin of the project the call is on)}GET /api/v2/razadmin/revenue acc E_FORBIDDENRevenue as JSON: {currency, perPlan, plans: [{plan, label, people, moving?}] (those for sale and those still held: one no longer sold drops out when nobody is on it; moving while someone is, P-2226), moves: {scheduled: [{email, from, to, at, told, failed?}], moved: [{email, from, to, at}], left: {GET /api/v2/razadmin/earnings acc E_FORBIDDENthe Architect's session (P-2259, ^sell-14, ^sell-16): {month, projects: [{project, owner, feePct, month: {sold, tax, card, fee, net}}], owners: [{owner, balance}], taxOwed: {month, year: [{place (country, -state where Stripe gives one), tax}]} (for filing), books: {ok, nets, balances, spent, gaps}} (the books agree when every owner's lines add up to their balance plus what it paid d2). US$ numbersPOST /api/v2/razadmin/stripe/sync acc E_DEMIGOD E_FORBIDDEN E_ORIGIN E_PROVIDERSync to Stripe: one Stripe product (d2_<id>) and price (lookup key d2:<id>, tax exclusive) for each plan and each add-on with a price in Settings:Quotas; a changed price gets a new price with the lookup key moved and the old one archived, an unchanged one is left alone, a product gone from Settings:Quotas is archived. → {dry, made, moved, archived, unchanged}; ?dry=1 changes nothing. The Architect's session or his demigod agent; E_PROVIDER 503 until the Stripe settings are on the boxPOST /api/v2/razadmin/credits acc E_NOT_FOUND E_ORIGIN{handle, cents, note}: credits to a person, kept as a `grant` line. The Architect's own session only: E_SCOPE for any tokenPOST /api/v2/credits/test-grant acc E_NOT_FOUND E_ORIGIN E_QUOTA{handle, cents, note}: test credits to a test account only (or {handle, plan}, P-2497: the plan and its bundle as a grant, never paid, gone with the account at its run's cleanup; a plan not sold or free E_ARG) (a tester's, or aiputty.com+d2test-…), kept as a `test-grant` line: at most credits.testGrant.cents (50) a grant and credits.testGrant.max (300) cents in all per account. A tester agent's token (the tester hold's own route: only to an account its person made, else E_SCOPE) or the Architect's own session; any other token E_SCOPE, a real account E_ARG, past a cap E_QUOTAGET /api/v2/razadmin/spend acc E_DEMIGODd2's spend on API agents (P-2072), the Architect's session or his own agent: {period: today | month (default), providers: [{provider, d2: {cost, charged, runs}, ownKeys: {cost, runs}}], credits: {held, accounts}, breaker: {today, max, paused, lifted} (d2's key today against spend.d2DayMax, P-2253), gauges: {todayVsAvg: {today, avg7, pct} (d2's key so far today against the same hours of the last 7 days' average, the Architect's runs apart; pct null with no average), topToday: [{handle, cost}] (the top 5 on d2's key today), shortfalls: [{kind, at, handle, amount, detail}] (the last 10 reserves that fell short, as told), books: {ok (null before the first check), lastCheck, gaps: [{sum: 1 | 2 | 3, handle?, amount, detail}], openrouter? (why OpenRouter's total wasn't read)}}, alerts: the last 7 days' [{kind: shortfall | surge | books, at, handle?, amount?, detail}], newest first (P-2079, ^credits-15..17), openrouter: {remaining, at, testing, card} | {error, testing, card}; card (P-2496): d2's key at OpenRouter from one fetch of its /key and /credits kept 5 minutes, {at, key: {name (its label, null when it looks like a key), usage: {day, week, month, all}, freeTier, limit, budget: {limit, left, used, pct, period, resets, red (80% used)} | null}, credits: {bought, used, left, red (under US$ 10)} | {error}, red, hits: {day, count, model, limits: [{limit (the name a 429 gave, null for none), count, last, model}]} (today's 429s), perModel (OpenRouter publishes no per-model caps)} | {error, last? (the last good card), hits, perModel}; at 80% and 95% of the key's limit N_KEY_BUDGET to the Architect, once a period; never the key, never its rate_limit; Testing apart (P-2493): columns: {real, testing}: each {today, week (the last 7 UTC days), month, all}: {loaded, granted, cost, charged, net (loaded − cost), runs}, never a test row in real; testing: {accounts: [{handle, cost, runs, run?, item?}] (the top 10 test accounts on d2's key, with their tester's run and the item its status names), items: [{item, cost, runs}] (the top 10), d2Key (Testing's cost on d2's key in all, openrouter.testing)}; each provider's testing: {cost, charged, runs} beside d2 (real only); topToday real only}. d2 is every real run on d2's key, the Architect's with everyone's (he is charged like anyone, P-2219): cost what d2 paid, charged what credits paid; ownKeys is people's runs on their own keys, information only, never in d2's spend; held is the sum of every balance; openrouter.remaining is d2's own balance there (cached 5 minutes; a failure says why and the rest still answers). US$ with two decimals. A member's or another token's call: E_SCOPEPOST /api/v2/razadmin/spend/lift acc E_ORIGINlifts the breaker on d2's key (^credits-12, P-2253) for the rest of the UTC day: every run on d2 credits pauses once today's spend on d2's key reaches spend.d2DayMax (Settings:Quotas, US$ 50), the Architect told by a notice and a mail → {breaker: {today, max, paused, lifted}} (also on GET /api/v2/razadmin/spend). The Architect's own session alone: any token (his own agent's too), another person's session or another origin 403 E_ORIGINGET /api/v2/razadmin/openrouter/activity accd2's spend at OpenRouter by model and day (P-2498), from base d2's management key (the Architect's vault entry openrouter-mgmt, read by the server alone): {on: true, at, days: [{day, cost, models: {GET /api/v2/razadmin/openrouter/meta accthe analytics' metrics, dimensions and granularities OpenRouter lists (P-2498), kept an hour: {on: true, metrics, dimensions, granularities} | {on: true, error} | {on: false, line}; the Architect's own session onlyPOST /api/v2/razadmin/openrouter/query acc E_ORIGIN E_PROVIDERone analytics query at OpenRouter (P-2498): {metrics (1-5 of meta's), dimensions? (up to 3), granularity?, from?, to? (YYYY-MM-DD, at most 90 days)} → {on: true, query, rows (at most 500)} | {on: false, line}; a name not in meta E_ARG {field}; more than one query in 10 seconds E_RATE with Retry-After; OpenRouter failing E_PROVIDER; the Architect's own session, same originPOST /reinstate acc E_LOCKED E_LOGIN E_ORIGIN E_TOO_BIG{email, password, text (1–2000)}: from the hold page a suspended person asks to be reinstated (the password again, else E_LOGIN; one a day, E_RATE) → a support message to the Architect marked reinstate; his reply shows on the hold page at their next sign-in. Audited U_REINSTATE_ASKGET /api/v2/agents/stamp agents{stamp} of the project's agent board and your messages: the Agents page and the pipe map sync on itGET /api/v2/projects/available?name= acc E_CERT_LIMIT E_QUOTA{available} or {available: false, code, reason}GET /api/v2/projects acc{scope, total, data: [{project, url, level, plan, role, tags}]}: tags are the project's own (^myp-1); an all-projects AI token lists its person's own projects; a project token, just its ownGET /api/v2/project acc E_FORBIDDEN E_NOT_FOUNDthe project's facts, its admins' (session only): {name, owner, plan, created, version, level, levels: [{id, title, needsPlan}], join, joinKind, access?, deletable, consent}GET /api/v2/project/members acc E_FORBIDDEN E_NOT_FOUNDthe Members page as JSON, its admins' (session only): {members: [{email, handle, role, kind, fixed, plan?: {name, since, until?, status, by?, note?, free?}}], plans (the project's, top the highest), requests: [{email, handle}], invites: [{email, role, kind, url, expires}], roles, kinds, appUsers}POST /api/v2/mails/test acc E_BAD_JSON E_FORBIDDEN E_METHOD E_ORIGIN{template: mail-welcome | mail-plan-bought | mail-plan-ending | mail-plan-ended | mail-support-reply}: a project's admin, in their own session (a token: E_SCOPE) → {to, mailed, why?, template}: the project's own template (else base AIPutty's) rendered with sample values and mailed to themselves, from GET /api/v2/project/quotas acc E_FORBIDDEN E_NOT_FOUNDthe Quotas page as JSON, its admins' (session only): {project, plan, rows: [{key, label, used, usedText, limit, limitText, source, override?}], problems}POST /projects acc E_LEVEL E_ORIGIN E_QUOTA E_TOO_BIG E_UNCONFIRMEDthe New project form {name, level, view?}: makes the project and hands the person off to its home, signed in (^sso-7); a person whose email isn't confirmed makes one Hero project and nothing more, else the page again with confirm your email first, 403 E_UNCONFIRMED (^d2land-5); master needs the Monthly plan (E_LEVEL)POST /api/v2/projects {name, level?, view?, tags?} acc E_QUOTA E_UNCONFIRMEDmake a project, as the form does: a person's session (→ 201 {project, url, tags, go}: the one-time hop to its home), or an agent of an all-projects maker token for its person (→ 201 {project, url, tags}; any other token E_SCOPE). tags: the owner's tags on it, normalised as PUT /api/v2/projects/<p>/tags does (lower-case, no #, no repeats, at most 20; else E_ARG and nothing is made). level hero, creator, pro or master (master needs the Monthly plan, E_QUOTA); the week's addresses and the owned-projects quota are E_QUOTA too; a person whose email isn't confirmed makes one Hero project and nothing more, else 403 E_UNCONFIRMED (^d2land-5)POST /d2admin/level acc E_LEVEL E_LEVEL_ROLE E_ORIGIN E_TOO_BIG E_UNCONFIRMED{level}: the project's level, from Settings › Project's Upgrade and Level (an admin; a session's form); moving it up needs the admin's email confirmed, else the page again with confirm your email to upgrade, 403 E_UNCONFIRMED (^d2land-6); lowering is not gated; master needs its owner on the Monthly plan (E_LEVEL). On Creator a maker token's every call answers 403 E_LEVEL_ROLE {details: {level, role: maker, needs: designer}}, naming the doors (P-1656)POST /api/v2/razadmin/projects/<name>/level acc E_INTENT E_LEVEL E_METHOD E_NOT_FOUND E_ORIGIN E_UNCONFIRMED{level}: the project's level, as /d2admin/level (moving up needs the caller's email confirmed, E_UNCONFIRMED; the owner holding the level's add-on, else E_LEVEL): the Architect's (add-ons, plans) or a project admin's (its level, its settings), in their own session or by their agent on their clear intent (D2-Intent) or their Allow once (D2-Override); without it an agent is refused (admin.*: person); audited A_ADMIN, the account's owner told (N_ADMIN)PUT /api/v2/razadmin/projects/<name>/settings acc E_AI_ACCESS E_FORBIDDEN E_INTENT E_LOCKED E_METHOD E_NOT_FOUND E_ORIGIN E_QUOTA E_SECRET E_SETTING E_UNCONFIRMED{text}: the project's Settings:Project, published (its whole text; never one of base d2's Settings:System, Quotas or Permissions): the Architect's (add-ons, plans) or a project admin's (its level, its settings), in their own session or by their agent on their clear intent (D2-Intent) or their Allow once (D2-Override); without it an agent is refused (admin.*: person); audited A_ADMIN, the account's owner told (N_ADMIN)POST /api/v2/projects/<handle>-test/reset {copy?, level?, next?} acc E_CERT_LIMIT E_FORBIDDEN E_NOT_YOURS E_QUOTA E_UNCONFIRMEDthe person's own test project (P-2111), in their own session only (an AI token E_SCOPE): back to its seed, its level's templates as a new project starts (topics, objects, drafts, runs, files gone), made the first time (level: the one asked, else this project's; the plan's projects and the week's addresses count, E_QUOTA); then copy: at most 50 topics of the project it's called on, written into it (a member's; Settings: ones are skipped) for its suite page to play. Any other name E_SCOPE, a <handle>-test someone else made E_NOT_YOURS: no other project is ever touched → {project, made, level, url, topics, copied, skipped: [{topic, code, message}], go} (go: the one-time hop to next there, signed in)POST /api/v2/projects/<handle>-test/report {suite?, journeys: [{topic, story, title?, ok, skipped?, failed?: {i, step, expected, seen}, ms?, run?}], ms?} acc E_FORBIDDEN E_NOT_FOUND E_NOT_YOURSthe report of a suite run on the person's own test project (P-2111), their own session only (an AI token, another name E_SCOPE; not made yet E_NOT_FOUND): Report:suite-<handle>-<stamp>, tagged ephemeral, each journey ✓, ✗ (the step, what was expected, what was seen) or ⏭ skipped, with its time, written to the project whose suite page last reset the test project, else to the one it's called on; the writer a member there (E_FORBIDDEN) → 201 {project, topic, url, passed, failed, skipped}GET /api/v2/projects/<p>/input/consent acc E_FORBIDDEN E_NOT_FOUNDthe import's rights notice (P-2666), on the project's own address: whether the caller accepted it here → {project, accepted, at, version, asked?}POST /api/v2/projects/<p>/input/consent {version?} acc E_FORBIDDEN E_NOT_FOUNDaccept the import's rights notice once for this project (the person owns the material or may use it, and answers for it), kept on the project: who, when, the notice's version (default 1); a file into input/ needs it first (E_CONSENT). Their session, or their AI with askedBy naming them (else E_SCOPE) → 201 {project, accepted, at, version, asked?}POST /api/v2/project/release ops E_FORBIDDEN{version}: a new version of this project (admins)POST /api/v2/project/onboarding-end accthe onboarding's end, by the project's admin or their AI: a Pro project's home becomes its dashboard, unless it has one of its own → {home, set}POST /api/v2/project/share {email} acc E_EMAIL E_EXISTS E_LEVEL E_NO_APPShare (Hero and Creator): the owner or an admin, in their session, invites a person to the whole project as an app user, with the invite mail → 201 {email, mailed, why?, message?}; not mailed to an account whose email isn't confirmed (^mail-12); a member already E_EXISTS 409; a project with no app yet E_NO_APPPATCH /api/v2/project {join, joinKind?} accwho may join: request, open (with a plan on sale: free, then upgrade), invite (closed, a new project's default) or paid (paid only: joining is buying a plan, ^sell-3), and joinKind, the kind open join gives (builder or app-user, app-user unless set); the owner or an admin, in their session or through their AI token → {join, joinKind}POST /api/v2/onboard/roles {roles, days?} accStart your agents: roles of designer, builder, dispatcher, tester → 201 {links: [{role, id, link, expires}], vault}: a one-time link per role (an hour), serving its start prompt; each role token kept in the vault as d2-<role>, the dispatcher allowed d2-designer and d2-builder. The person's session or a signed d2 page; never an AI tokenPOST /api/v2/onboard/setup {kind} acc E_FORBIDDENa setup link: kind harness-solo, harness-full or app-loop → 201 {line, url, expires, roles, kind}: one link (an hour) bundling a role link per role (harness-full: monitor, dispatcher, builder, designer; else the level's designer or maker), served by GET /setup/<id>; the owner or an admin in their own session or a signed d2 page (a builder: harness-solo and app-loop, for their own AI), never a token. Kind connect → 201 {line, url, expires, kind}: Connect your AI's paste line, the invite Done / New AI invite makes (10 minutes, ending their unused one), for any member's own AIDELETE /api/v2/project acc E_CONFIRM E_FORBIDDEN E_NOT_FOUND E_PROTECTED E_SUPPORT_LINE{name}: delete this project and all its data, its name typed to confirm (its owner or an admin, in their session; never a token, never a built-in project)GET /api/v2/auth acc{loggedIn, tokenNeeded}: the write token's cookiePOST /signup acc E_BOTthe sign-up form {email, password, password2, next, cf-turnstile-response}: with the box's Turnstile keys set, the bot check runs first — no token, a refusal or Cloudflare unreachable is the page again, 400 E_BOT, no account; a user-role token in Authorization skips it (A_BOT_SKIP)POST /login/forgot acc E_ORIGIN E_TOO_BIGForgot password? {email} (^mail-6): the same page whatever is typed — If there's an account with that email, a link is on its way (good for an hour); a link is mailed to the account's email, confirmed or not, never to a suspended account, at most 3 an hour (the Architect's own unthrottled); a link not mailed is logged (info, kind mail) with whyGET|POST /login/confirm-again acc E_METHOD E_ORIGIN E_TOO_BIGDidn't get the confirm mail? {email} (^mail-5; form or JSON): the same answer whatever is typed — If there's an account with that email waiting to be confirmed, a new confirm link is on its way (JSON: {ok, said}); a fresh confirm link is mailed only to an account that exists, isn't confirmed and isn't suspended, at most 3 an hour; a link not mailed is logged with whyGET|POST /login/reset/<link> acc E_GONE E_METHOD E_NOT_FOUND E_PASSWORDSet a new password (^mail-6): GET the form; POST {password, password2} sets it once, within mail.resetTtl [1 h] (24 h for one the Architect hands over), confirms the email if it wasn't, ends every session of the person and logs them in here (303 /home); a used or expired link 410 E_GONE, an unknown one 404 E_NOT_FOUNDGET|POST /account/confirm/<link> acc E_GONE E_METHOD E_NOT_FOUNDa confirm-your-email link (^mail-5, ^mail-16): GET shows the address and Confirm my email, and confirms nothing; the POST (same origin) confirms the email it was mailed to, once, within mail.confirmTtl [7 days]; a used link is 200, confirmed; expired 410 E_GONE, unknown 404, both with Send me a new link. Until it's confirmed d2 mails the account nothing but its account mailsPOST /api/v2/me/confirm acc E_METHODSend the link again (^mail-5, ^ui-mail-2): in your own session (any token E_SCOPE) → {confirmed, mailed, hint?}: a new confirm-your-email mail, replacing the unused link; at most 3 an hourPOST /api/v2/auth/login acc E_CONSENT E_MOVED E_PASSWORD E_SUSPENDEDretired (P-434): 410 E_MOVED — a browser logs in at /login, a token goes in each call's Authorization headerPOST /api/v2/auth/logout accclears itGET /api/v2/tls-ask?domain= opsfor the TLS proxy: 200 only for our namesGET /api/v2/apps/<App>/publish pages E_FORBIDDEN E_METHOD E_NOT_FOUNDwhat Publish app takes from App:<App> (^pubapp-3), for the project's admins in their own session (a token E_SCOPE, anyone else E_FORBIDDEN, no such App E_NOT_FOUND) → {app, ver, classes (those its d2.api calls name, by /api/v2/dom/… paths and batch cls, and those they reference), rules (the Settings:Init $when rules naming one of them), files (its /cdn/<path> files), vault (the {vault:<name>} entries it and those rules name: listed, never copied), objects: {Class: count} (copied only with copy data), live: [{project, ver, at, state: live | ahead}] (each project published from it, ^pubapp-5), quota: {held, used} (the caller's site add-ons and the sites using them, P-2262; null for the Architect)}POST /api/v2/apps/<App>/publish pages E_BAD_JSON E_EXISTS E_FORBIDDEN E_NOT_FOUND E_PLAN E_QUOTA E_RESERVED E_UNCONFIRMEDPublish app (^pubapp-1, ^pubapp-2): a project admin's session makes the project <name> (^acc-19's name rules, the publisher its owner, hero level, no pipeline) with kind: app, app: <App>, from: {project, app, ver, at} in its Settings:Project, membership open (view public, anyone joins as an app user) or invite, and only what the plan lists: App:<App>, Spec:<App> with its classes' $class lines, Settings:Init with its rules, its files, and the classes' objects when copyData is true; none of the template's dashboards, Help, logs or pipeline. It takes one of the publisher's site add-ons (P-2262: addon-site, bought on a paid plan; the Architect's take none), never counts in projects per user; on Free 403 E_PLAN, past the add-ons 403 E_QUOTA {reason: sites, held, used}, two at once never both taking the last one → 201 {project, url, go, copied: {classes, rules, files, objects}, vault: [names to allow on the new project]}POST /api/v2/apps/<App>/publish/<project> pages E_BAD_JSON E_CONFIRM E_FORBIDDEN E_NOT_FOUNDPublish update (^pubapp-6): to a project published from this App, by an admin of both in their own session: the App, Spec:<App> and Settings:Init again, from: ver now the dev App's, never its objects → {project, ver, from}; a field the dev classes no longer have that holds data there is 409 E_CONFIRM {drops: [{class, field, count}]} and nothing changes, until it is sent again with confirm: true; a project not published from it E_NOT_FOUNDPOST /api/v2/tester/grant {account, plan?, addons?} acc E_METHOD E_TOO_BIGa tester sets the plan (free, yearly, monthly) and add-ons (addon-masterprojects: Master projects without the Monthly plan) of a test account its person made, no payment and no billing record → {account, plan, addons}; anyone else's account, or any other token, E_SCOPE (^tst-13)POST /api/v2/tester/cleanup {run?, all?, account?, project?, keep?, askedBy?} acc E_TOO_BIGdeletes the test accounts (with their tokens and the projects they own), the d2test- projects and the mail in the box's mailbox that its person's tester made: this run's by default; another run's or all of them (all: true) only with askedBy naming its person; account / project: one of them; keep: true marks the run's kept, so the daily housekeeping deletes them a day after the keep; either way the run ends and its tester grants go (free plan) → {deleted: {accounts, projects, mails, failed, mailError?}} or {kept} (^tst-7, ^tst-13)GET /api/v2/mail?box=inbox|sent&to= acc E_MAILthe box's mail to this run's test addresses: inbox read from the box's own mailbox over IMAP (its accounts' addresses, plus to= one of the run's) → {box, total, data: [{id, from, to, subject, at}]}; sent, the mail log's rows → [{to, kind, subject, at, ok, messageId?, error?}] (messageId: the Message-ID d2 wrote); Mailer off or the mailbox refusing: 503 E_MAIL with why (^tst-12)GET /api/v2/mail/<id> acc E_MAIL E_NOT_FOUNDone mail of the inbox: {id, from, to, subject, at, text, links, messageId, auth}, auth the receiving server's Authentication-Results as {spf, dkim, dmarc} each pass | fail | none (the raw line when it names none of them); a mail to another run's address E_SCOPEPOST /api/v2/mail {to, subject, text} acc E_MAIL E_TOO_BIGsends one mail through the Mailer, from the box, to one of this run's test addresses only (else E_SCOPE) → 201 {queued} (or queued: false with why)POST /api/v2/broadcasts support{title (≤80), body (markdown subset ≤2000: paragraphs, **bold**, *italic*, links to https:// or a path), level?: info|warning, until?}: a draft → 201 {id, preview}; nobody is told yetGET /api/v2/broadcasts support E_NOT_FOUND E_STATUSevery broadcast, newest first; /<id> one; PATCH /<id> changes a draft (E_STATUS once sent); DELETE /<id> drops a draftPATCH /api/v2/broadcasts/<id> {…} support E_NOT_FOUND E_STATUS{title?, body?, level?, until?}: changes a draft → {id, preview}; E_STATUS once sentDELETE /api/v2/broadcasts/<id> support E_NOT_FOUND E_STATUSdrops a draft → {deleted}; E_STATUS once sentPOST /api/v2/broadcasts/<id>/send support E_NOT_FOUND E_STATUSone priority N_BROADCAST per account, behind notice.broadcast (his AI: asks); /withdraw takes it off everyone's Noticescurl -s https://dieselwebos.com/api/v2/diesel/run \\
-H 'content-type: application/json' -H 'authorization: Bearer $D2_TOKEN' \\
-d '{"spec":"$when hello (who) => (payload = \\"hi \\" + who)","story":"$send hello (who = \\"d2\\")\\n$expect payload == \\"hi d2\\""}'
Reads and diesel/expr are open; other writes (PUT, POST, DELETE) need a bearer token. Log in, sign up, members, tokens and the account pages are HTML forms (the pages; see Help). Admin API (/admin/*: status, logs, deploy, restart, rollback) is separate and token-only.