API Overview
Interactive documentation is available at /api/v1/docs (Swagger UI) and /api/v1/redoc once the server is running.
Base URL
Section titled “Base URL”http://<host>:<port>/Default: http://localhost:8000/
Access modes
Section titled “Access modes”The server exposes the same functionality through two protocols on the same port:
| Protocol | Base path | Use case |
|---|---|---|
| REST (HTTP/JSON) | / | Automation, CI, custom scripts |
| MCP (SSE / Streamable HTTP) | /mcp | Any MCP-compatible client or agent |
LLM workflow
Section titled “LLM workflow”flowchart TD
A["1. GET /{analyzer}/rules\nFetch the security ruleset once\nReuse for every resource in the loop"] --> B
B["2. POST /{analyzer}/{namespace}/ingest\nSend the raw plan\nReceive resource units[] with stable hashes"] --> C
C{{"3. Loop until HTTP 208"}}
C --> D["a. GET /{namespace}/resource/next\n→ one resource"]
D --> E["b. Analyse using the ruleset\nfetched in step 1"]
E --> F["c. PUT /resource/{hash}/analysis\n→ store findings"]
F --> C
C -->|HTTP 208 — all done| G["4. GET /{namespace}/report\nRetrieve consolidated\nMarkdown or HTML report"]
Server-side alternative: POST /{analyzer}/{namespace}/analyze runs the whole workflow above on the server itself — the caller supplies an LLM provider/model (and an API key, or relies on one stored for the org) and gets the report back directly (sync for small inputs) or a job to poll (async for large inputs). See POST /{analyzer}/{namespace}/analyze.
Router groups
Section titled “Router groups”| Tag | Prefix | Purpose |
|---|---|---|
health | /health | Liveness probe (no auth) |
session | /auth/register, /auth/session…, /auth/me… | Human user registration/login/logout, session introspection for dashboard bootstrap (no API-key auth) |
auth | /auth/keys | Service account (named, IP-filterable API key) management (org-admin only) |
sso | /orgs/{org_id}/sso, /auth/sso/… | Per-org OIDC connection CRUD (org-admin) + the public discover/start/callback login flow — see Single sign-on |
orgs | /orgs/… | Org info, members, invitation links, analyzer load/unload, LLM credentials, billing quota/usage, report-link TTL |
teams | /orgs/{org_id}/teams/… | Teams CRUD + member add/remove — pure organizational grouping, no access-control effect (org-scoped; create/rename/delete/add/remove-member are org-admin only) |
resources | /resource/…, /{namespace}/… | CRUD + namespace ops (org-scoped) |
reports | /reports/{token} | Public, time-limited HTML report access (no auth — see Report links) |
invitations | /invite/{token}, /invite/{token}/accept | Preview is public; accept requires a session — see Invitations |
analyzers | /analyzers, /{analyzer}/… | Discovery, rules, ingest, server-side analyze (org-scoped) |
rules | /orgs/{org_id}/rules/… | Custom rules CRUD — create, list/get, partial update (including the enabled/requires_review toggles), delete (org-scoped; create/update/delete are org-admin only) |
report-templates | /orgs/{org_id}/report-templates/… | Custom report templates CRUD — create, list/get, partial update (including the is_default toggle), delete, seed-builtin, preview (org-scoped; create/update/delete are org-admin only) |
jobs | /jobs/… | Async /analyze job status polling (org-scoped) |
findings | /findings/… | Finding triage — list, summary, PATCH status incl. needs_review (org-scoped) |
integrations | /orgs/{org_id}/integrations/github[/manifest|/app], /orgs/{org_id}/integrations/review-webhook, /integrations/github/manifest/callback, /integrations/github/webhook | GitHub App connect/disconnect (org-admin), the manifest flow for a custom per-org App, the public webhook receiver, + the needs-review outbound webhook (org-admin) |
metrics | /metrics/… | Telemetry — spans, summary, key usage (org-admin only, org-scoped) |
admin | /admin/… | Platform-wide, cross-tenant operator views — requires is_superadmin, not any org role. See Platform Admin. |
Authentication
Section titled “Authentication”Every request resolves to a principal bound to exactly one organization (org_id) — there is no cross-org access without switching credentials. Two kinds of principal exist, both authenticate to the same role model:
- API key (
X-API-Keyheader or?api_key=) — machine principal for CI/MCP clients, created viaPOST /auth/keys. - Session cookie (
mcp_session, set byPOST /auth/session) — human principal for interactive/dashboard use, obtained viaPOST /auth/register,POST /auth/session, or a completed SSO login (GET /auth/sso/callback) — all three set the same cookie, so nothing downstream distinguishes how a session was obtained.
Authentication is optional and controlled by auth.api_key in the config file — when null (the default), the server runs as an anonymous owner of a default org with no authentication, suitable for local dev or private networks only.
A single ordering applies to both API keys and human memberships: viewer < member < admin < owner.
| Role | Can access |
|---|---|
owner / admin | Everything member can, plus key management, org member management, team management, analyzer load/unload, creating/editing/deleting custom rules, configuring the needs-review webhook, and metrics |
member | Analyzer discovery/rules/ingest, resource CRUD, reports, reading (but not writing) custom rules |
viewer | Reserved for future read-only use; not yet distinguished from member in Phase 0+1 |
is_superadmin is a separate axis, not a fifth role above owner. It answers “can this principal see across every org,” which owning even every org you belong to doesn’t imply — a user can be owner of three orgs and a superadmin of none. Gates only /admin/…. See Platform Admin.
Registering and logging in (human users)
Section titled “Registering and logging in (human users)”org_name and invite_token are both optional and mutually exclusive — send one, the
other, or neither:
# Create a new org, owned by you (the original, still-default behavior)curl -X POST http://localhost:8000/auth/register \ -H "Content-Type: application/json" \# → sets the mcp_session cookie; you now own a brand-new organization# Join an org via an invitation link instead — see Invitations belowcurl -X POST http://localhost:8000/auth/register \ -H "Content-Type: application/json" \# Neither — an org-less account. Every org-scoped endpoint 403s until you# create an org (POST /orgs) or accept an invite.curl -X POST http://localhost:8000/auth/register \ -H "Content-Type: application/json" \curl -X POST http://localhost:8000/auth/session \ -H "Content-Type: application/json" \ -c cookies.txt# → 200 even for an org-less account (org_id/role null in the response) —# this used to 401 with "no organization membership"; org-less is now a# real, intended account state, not an error.An already-registered, org-less account “graduates” by calling POST /orgs (create a
new org, same endpoint an existing user calls to add a second one) or by opening an
invitation link — see Invitations.
Bootstrap admin key
Section titled “Bootstrap admin key”auth.api_key in the config is the bootstrap admin key. It is permanent, never stored in the database, cannot be revoked, and always resolves to the default org as owner — treat it like a root password and keep it secret.
Creating service accounts
Section titled “Creating service accounts”Use an admin/owner credential (bootstrap key or a logged-in session) to create runtime keys — “service accounts” in dashboard terms — scoped to that credential’s org, optionally restricted to a set of source IPs:
curl -X POST http://localhost:8000/auth/keys \ -H "X-API-Key: <admin-key>" \ -H "Content-Type: application/json" \ -d '{"role": "member", "name": "ci-pipeline", "ip_allowlist": ["203.0.113.0/24"]}'{ "key_id": "3f4a...", "key": "xK9mZ...", "role": "member", "name": "ci-pipeline", "ip_allowlist": ["203.0.113.0/24"], "created_at": "2026-01-01T00:00:00Z"}The raw key is returned once — store it securely. name and ip_allowlist are both
optional; a request using this key from outside ip_allowlist gets 403 (the key is valid, just
not usable from there) rather than the 401 an unrecognised key gets.
Revoking a key
Section titled “Revoking a key”curl -X DELETE http://localhost:8000/auth/keys/<key_id> \ -H "X-API-Key: <admin-key>"Only a key belonging to the caller’s own org can be revoked — attempting to revoke another org’s key returns 404, not 403 (it gives no signal that the key exists at all).
See Auth endpoints for the full API reference.
Single sign-on (SSO)
Section titled “Single sign-on (SSO)”Each org can connect its own OIDC identity provider instead of relying on passwords — POST/GET/PATCH/DELETE /orgs/{org_id}/sso (org-admin) manages the connection; POST /auth/sso/discover, GET /auth/sso/{org_id}/start, and GET /auth/sso/callback (all public — the caller isn’t authenticated yet) implement the login itself. A completed login resolves the asserted email against existing users (linking a local-password account rather than duplicating it) and JIT-provisions a membership at the connection’s default_role if one doesn’t already exist, without ever downgrading an existing one. See Quick Start: SSO & SCIM — Single sign-on for the setup walkthrough and API Endpoints — SSO for the full request/response reference.
Request ID
Section titled “Request ID”Every response includes an X-Request-ID header. Supply your own value in the request to correlate log entries:
X-Request-ID: my-run-42Error responses
Section titled “Error responses”All errors return JSON:
{"detail": "human-readable message"}| Status | Meaning |
|---|---|
| 400 | Bad request (e.g. unknown filter field) |
| 401 | Missing or invalid credentials (API key or session), or an invalid GitHub webhook signature |
| 402 | Org’s monthly LLM spend cap already met/exceeded (/analyze pre-flight check — see Billing / quota) |
| 403 | Authenticated but insufficient role (admin required); or an SSO login whose asserted email’s domain isn’t in the connection’s allowed_domains |
| 404 | Resource / analyzer not found, or a cross-org access attempt |
| 208 | All resources in namespace already analysed — exit the loop |
| 409 | Key already exists (safe to ignore on ingest) |
| 413 | Input too large (exceeds server.max_input_size_mb) |
| 422 | Schema validation error or invalid analysis payload |
| 500 | Unhandled server error |
| 501 | Analyzer not yet implemented; a webhook whose installation has neither its own App nor the platform’s config.github configured; or the manifest-flow trigger without server.public_base_url set |
| 502 | LLM provider returned output that couldn’t be parsed (/analyze sync path only); an SSO identity provider was unreachable during login (GET /auth/sso/{org_id}/start); or POST /admin/storage/prepare-mongodb/POST /admin/storage/switch couldn’t reach the target MongoDB |