REST API

Base URL: http://localhost:8787 (dev). All responses JSON. Public routes need no auth; org routes need Authorization: Bearer <api key> (or, from the web app only, the person's sealed session in x-openwake-session plus x-openwake-org); org creation needs x-openwake-key: <OPENWAKE_ADMIN_KEY>.

Health

GET/healthliveness for load balancers
GET/health/deepdatabase, migrations, keys, ledger head and anchor age, pipeline freshness, integrations; 503 with problems[] when something blocks production. pnpm preflight prints the same from the CLI.

Public: the registry

MethodPathPurpose
GET/v1/statsregistry counters
GET/v1/vendorsall vendors with latest training stance
GET/v1/vendors/:slugfull cited profile: facts, chain, documents, changes, document changes
GET/v1/vendors/lookup?domain=notion.sothe vendor behind a domain (website and document hosts first, then the name): the full profile plus verdict (training stance in one line with its quote), profile_url, watch_url. What the browser badge asks for the tab it is on. 404 when nothing is on record.
GET/v1/changes?limit=20recent fact changes
GET/v1/concentration?vendors=a,bwhich hyperscalers / model providers a vendor set terminates in
GET/v1/agents/:agent_ida registered agent and its operator
POST/v1/check{ counterparty, data_classes[], caller_agent_id? } → advisory decision + signed receipt. Attributed to your org when a key is sent.

Public: proofs

GET/v1/keyspublic key, key id, hash recipe
GET/v1/ledger/headchain head
POST/v1/receipts/verifybody: a receipt from /v1/check{ ok, offline: { statement_hash, entry_hash, signature }, ledger: { present, matches } }. The offline part needs only the public key; pnpm receipt:verify does it with no network.
GET/v1/ledger/:kind/:refledger entry for a record (check, fact_set, snapshot, change, document_change)

Self-serve

POST/v1/importBring your own vendor list. Body: raw text/csv or text/plain (names, domains, URLs, or a CSV export from Okta / Google Workspace / Entra / a SaaS-management tool), or JSON { text, watch?, email?, slack_webhook_url? }. Returns per-vendor matches with training / AI-feature / subprocessor summary, the unmatched rows, and a coverage percentage. With watch + email it also starts a watchlist (creating an org + API key, or adding to yours when a bearer key is sent). Unmatched vendors are counted for coverage: never who asked. The web form at /import calls the same code.
POST/v1/signup403 when sign-in is configured (accounts start at /login). Otherwise { email, vendors[], slack_webhook_url? } → org + API key (shown once) + watchlist. Rate-limited. The web form at /watch calls this.
POST/v1/vendors/:slug/corrections{ field, claim, source_url?, contact_email? } → a public correction request, ledger-entered. field is a fact key, subprocessors, documents or other. Rate-limited.
GET/v1/vendors/:slug/correctionsthe public correction log for a profile (never the contact)
POST/v1/vendors/:slug/claim{ email } at the vendor's domain → emails a 48-hour verification link (dev: returned as dev_link)
GET/v1/claim/:tokenredeems the link; marks the vendor profile verified

Org-scoped

POST/v1/watchlists{ owner_email, vendors[], slack_webhook_url? }: secrets sealed at rest
POST/v1/agents (also parent_agent_id, kind, policy)register an agent: { agent_id, name, operator_vendor?, issuer?, scopes? }
GET/v1/agentsyour registered agents
POST/v1/footprinta Scout inventory (packages/scout) → vendors resolved against the registry, unmatched hints queued, stored, ledger-entered. See docs/scout.md.
GET/v1/footprintlatest report per device; vendors seen across devices with stance, how they were seen (tool, MCP, key), and whether they are watched
GET/v1/org/landscapewatched ∪ seen vendors and concentration over what is underneath them
GET/v1/checks?limit=100your check log
GET/v1/dataflow?days=90data classes → counterparties summary (the Article 30 view)
GET/v1/api-keyslist the org's keys: name, prefix, created, last used, revoked
POST/v1/api-keysmint another key (shown once); owners only when signed in
POST/v1/api-keys/:id/revokerevoke a key
GET/v1/mesigned-in person, their orgs and roles (session only)
POST/v1/me/orgs{ name } → a new org on Free with the caller as owner (session only)
GET/v1/org/membersmembers, open invites, seats used / limit
POST/v1/org/members{ email, role? } → invite; joins at once if they have an account; 402 when every seat is taken (owner)
DELETE/v1/org/members/:userIdremove a member; the last owner stays (owner)
DELETE/v1/org/invites/:idwithdraw an invite (owner)
GET/v1/org/ssothe org's own OpenID Connect issuer (issuer, client id, email domain, verified_at; never the secret) and the redirect URI to register (owner)
PUT/v1/org/sso{ issuer, client_id, client_secret?, email_domain? } set it; Business only; the secret is required the first time and kept when omitted; one sign-in through it verifies (owner)
DELETE/v1/org/ssoremove it (owner)
PATCH/v1/agents/:id{ name?, parent_agent_id?, kind?, policy? } rename, re-parent (submodule), or set the allowed context of an agent this org registered. policy is { data_classes?: [...], counterparties?: [...] }; * allows all, acme/* a subtree; null clears it
POST/v1/agents/:id/events{ kind, counterparty?, data_classes?, outcome?, check_id?, detail?, tool?, action?, acting_for?, delegated_by?, parent_event_id?, task_id?, purpose?, authority? } → the event scored against the agent's policy (within_policy, policy_reasons) and its ledger entry. kind is delegation, tool_call, data_out, data_in, decision or custom; detail is one sealed line, never a payload. 600/min per org. packages/recorder posts one of these per MCP tool call, see docs/recorder.md
GET/v1/agents/:id/eventsthat agent's events, newest first
GET/v1/org/activity?days=90&limit=100 every agent's events for the org
GET/v1/org/activity/streamserver-sent events: every agent event (activity) and every Scout report (footprint) as it lands, ?since=<event id> to resume; ping every 16 s, bye after 30 min (reconnect). What /dashboard/live shows
GET/v1/org/overview?days=30 the org's dashboard: watched vendors, changes by materiality, objection windows closing soonest, alerts sent, agent activity
GET/v1/org/changes?days=90&limit=200&handled=0 fact and document changes on the watched vendors, newest first, with the notice window, the handled state and a drafted objection for fact changes
POST/v1/org/changes/:kind/:id/handled{ handled?: true, note? } mark a change handled (or reopen it) for this org
GET/v1/org/vendorsthe vendors the org watches with the latest record's headline facts, notice period, and change counts for 30 days
DELETE/v1/org/vendors/:slugstop watching a vendor across the org's watchlists (owner)
GET/v1/org/graph?days=90 the org's agents as a tree, the counterparties they reached (checks + events, data classes, decisions, out-of-policy counts), and what sits downstream of those vendors

Billing

GET/v1/billingyour plan, its limits, current usage, and whether billing is enabled on this deployment
POST/v1/billing/checkout`{ plan: "team" \"business", email? }{ url }` Stripe Checkout. 503 when Stripe is not configured.
POST/v1/billing/portal{ url } Stripe Customer Portal (change or cancel)
POST/v1/billing/webhookStripe → Openwake. Signature verified against STRIPE_WEBHOOK_SECRET; each event id applied once.

Plan gates return 402 with an upgrade URL: watched vendors beyond the plan (Free: 5), Slack destinations, CSV exports, the data-flow summary, and agents beyond the plan (Free: 3, Team: 25). Checks, the registry, evidence packs and JSON/HTML reports are never gated. See docs/pricing.md.

Admin

GET/v1/admin/correctionsopen correction requests, oldest first, with the sealed contact opened (x-openwake-key). pnpm corrections prints the same.
POST/v1/admin/corrections/:id`{ status: accepted \rejected \duplicate, resolution }`: resolves once, ledger-entered, shown on the profile
GET/v1/admin/requests?limit=50the demand queue: uncovered vendors by import count (x-openwake-key). pnpm requests prints the same from the CLI.
POST/v1/orgs{ name } → org + first API key (shown once)

data_classes: pii, customer_content, credentials, health, financial, source_code, none. Payloads are never accepted.

Provenance on events (acting_for, delegated_by, parent_event_id, task_id, purpose, authority) is optional and usually empty today: identifiers and short labels that let a delegation storyline be reconstructed later (who the agent acts for, who handed it the work, the event it continues, the task it belongs to, the stated purpose, the policy or approval it rests on). parent_event_id must be an event of the same organisation. All of it is in the ledger statement.