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 | /health | liveness for load balancers |
|---|---|---|
| GET | /health/deep | database, 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
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/stats | registry counters |
| GET | /v1/vendors | all vendors with latest training stance |
| GET | /v1/vendors/:slug | full cited profile: facts, chain, documents, changes, document changes |
| GET | /v1/vendors/lookup?domain=notion.so | the 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=20 | recent fact changes |
| GET | /v1/concentration?vendors=a,b | which hyperscalers / model providers a vendor set terminates in |
| GET | /v1/agents/:agent_id | a 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/keys | public key, key id, hash recipe |
|---|---|---|
| GET | /v1/ledger/head | chain head |
| POST | /v1/receipts/verify | body: 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/:ref | ledger entry for a record (check, fact_set, snapshot, change, document_change) |
Self-serve
| POST | /v1/import | Bring 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/signup | 403 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/corrections | the 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/:token | redeems 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/agents | your registered agents |
| POST | /v1/footprint | a Scout inventory (packages/scout) → vendors resolved against the registry, unmatched hints queued, stored, ledger-entered. See docs/scout.md. |
| GET | /v1/footprint | latest report per device; vendors seen across devices with stance, how they were seen (tool, MCP, key), and whether they are watched |
| GET | /v1/org/landscape | watched ∪ seen vendors and concentration over what is underneath them |
| GET | /v1/checks?limit=100 | your check log |
| GET | /v1/dataflow?days=90 | data classes → counterparties summary (the Article 30 view) |
| GET | /v1/api-keys | list the org's keys: name, prefix, created, last used, revoked |
| POST | /v1/api-keys | mint another key (shown once); owners only when signed in |
| POST | /v1/api-keys/:id/revoke | revoke a key |
| GET | /v1/me | signed-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/members | members, 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/:userId | remove a member; the last owner stays (owner) |
| DELETE | /v1/org/invites/:id | withdraw an invite (owner) |
| GET | /v1/org/sso | the 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/sso | remove 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/events | that agent's events, newest first |
| GET | /v1/org/activity | ?days=90&limit=100 every agent's events for the org |
| GET | /v1/org/activity/stream | server-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/vendors | the vendors the org watches with the latest record's headline facts, notice period, and change counts for 30 days |
| DELETE | /v1/org/vendors/:slug | stop 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/billing | your 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/webhook | Stripe → 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/corrections | open 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=50 | the 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.