API Usage
Spectral exposes a REST API via FastAPI plus an MCP-equivalent surface, served from the dedicated
api.runspectral.com host (ADR-095). Paths carry no /api/ prefix (the host already scopes it,
ADR-077 D7) and no version segment — the API is versioned by date: each key is pinned to the
version current at its first use, with an optional Spectral-Version: YYYY-MM-DD request header to
override, per ADR-091. This guide covers
authentication, the decision API and its MCP equivalent, dashboard read routes, required headers,
and error handling. For the full endpoint reference, start the API server and visit /docs
(Swagger UI).
The decision API itself is specified in ADR-077; the MCP equivalent in ADR-088; action discoverability in ADR-089. URL path conventions align with the tenancy hierarchy per ADR-086 D7.
Interactive API docs
Section titled “Interactive API docs”uv run --package spectral-api python -m spectral_apiopen http://localhost:8000/docs # Swagger UIopen http://localhost:8000/redoc # ReDoc (alternative)All request/response schemas are auto-generated from Pydantic models. The action discoverability
endpoint at /orgs/{org_id}/domains/{domain_id}/actions per ADR-089 returns an OpenAPI 3.x document scoped to
(org_id, domain_id) describing the deployed actions for that domain. Each action’s input schema
is a typed, documented contract (input names with their value types and descriptions) sourced from
the action’s canonical input ontology per ADR-107 —
one named, typed input per real quantity, reconciled at publish so two rules that name the same
quantity differently surface as a single input, not two. It is the authority, not a schema scraped
from the predicate’s read keys.
API surface overview
Section titled “API surface overview”/decide # POST decision API/orgs/{org_id}/domains/{domain_id}/actions # GET action discoverability/auth/* # Auth (non-domain)/orgs/{org_id}/... # Org-scoped management/orgs/{org_id}/domains/{domain_id}/... # Domain-scoped management/operator/* # Operator-only (operations role)MCP equivalent # per ADR-088The MCP surface exposes the decision API as an equivalent first-class path per ADR-088 — same authentication shape, same routing rules, same response shape. The Codex pages document the HTTP form; the MCP form follows the same contract.
Authentication
Section titled “Authentication”Every route requires authentication except:
POST /auth/registerPOST /auth/loginPOST /auth/callbackPOST /auth/reset-passwordGET /auth/oauth/{provider}GET /health
JWT (browser / human users)
Section titled “JWT (browser / human users)”# Registercurl -X POST http://localhost:8000/auth/register \ -H "Content-Type: application/json" \ -d '{"email": "dev@test.com", "password": "devpass", "org_name": "Dev"}'
# Logincurl -X POST http://localhost:8000/auth/login \ -H "Content-Type: application/json" \ -d '{"email": "dev@test.com", "password": "devpass"}'# -> { "access_token": "eyJ...", "user_id": "...", ... }The JWT shape (subject identity, RFC 8693 act claim for delegation, scope set) is specified in
ADR-087 D1 + D2. Pass the token as
Authorization: Bearer <token> on subsequent requests.
API keys (programmatic access)
Section titled “API keys (programmatic access)”API keys are domain-bound: minted per (org_id, domain_id) per ADR-086
D3, so the key carries both tenancy coordinates. A decision key grants read:actions (fetch action
discoverability) + a decision scope (write:domain) to invoke /decide (ADR-077 D7). Because the
key resolves (org, domain) server-side, the /decide body needs no tenancy — just the action +
context.
# Create a key (requires admin:domain scope)curl -X POST http://localhost:8000/orgs/<org_id>/domains/<domain_id>/keys \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"name": "Production agent"}'# -> { "key": "sp_live_...", "key_id": "..." } (plaintext shown once)
# Use the key — (org, domain) come from the key, not the bodycurl -X POST http://localhost:8000/decide \ -H "Authorization: Bearer sp_live_..." \ -H "Content-Type: application/json" \ -d '{ "action": "wire_transfer.release", "context": { "amount": 50000, "payment_method": "wire", "vendor_id": "v_123" } }'Scopes
Section titled “Scopes”Authorization uses action:resource scopes per the role + scope model in
Access Control:
| Scope | Description |
|---|---|
read:domain | Read domain data (decisions, audit chain, System Card, World Model) |
write:domain | Create / update domain data (rule candidates, source materials) |
approve:modules | Operator-only — approve action modules at the enshrinement gate |
admin:domain | Domain settings, API keys, member management |
admin:org | Org-level management |
read:actions | Fetch action discoverability (API key scope) |
read:operations / write:operations / admin:operations / delete:operations | Spectral staff (operations org role) |
The decision API
Section titled “The decision API”The primary surface. A caller’s agent invokes /decide before acting; Spectral
returns a binding { status, work_frame, decision_metadata } per ADR-077 D3.
Request
Section titled “Request”POST /decideAuthorization: Bearer sp_live_...Content-Type: application/json
{ "action": "wire_transfer.release", "context": { "amount": 50000, "payment_method": "wire", "vendor_id": "v_123" }, "world_model_version": 7, // optional; omit to use the active version "correlation_id": "wf_482" // optional passthrough; echoed via X-Correlation-Id}The (org, domain) tenancy is not in the body — the domain-bound API key resolves it
server-side (ADR-077 D7). correlation_id may also be sent as the X-Correlation-Id header.
context carries values for supplied attributes only — system_generated attributes
(request_time, request_id, authenticated_caller) are captured by Spectral at request
entry and cannot be supplied by the caller.
Response
Section titled “Response”{ "status": "YELLOW", "work_frame": { "mode": "review_requested", "next_action": "route_to_human_owner", "allowed_actions": ["gather_evidence", "request_approval"], "forbidden_actions": ["execute_wire_transfer", "reinterpret_policy", "override_spectral"], "required_output": "review_packet", "missing_evidence": ["vendor_payment_history"], "next_human_owner": "ap_supervisor" }, "decision_metadata": { "matched_rules": [...], "suppression_chain": [...], "aggregation_outcome": "winner_takes_all_T2", "predicate_traces": {...}, "errored_predicates": [], "request_id": "req_a1b2c3", "request_time": "2026-05-19T14:32:11Z", "world_model_version": 7 }}The status field is one of GREEN / GREEN-SKIP / YELLOW / RED.
The full five-phase decomposition of every invocation is documented in
Decision Execution.
Action discoverability
Section titled “Action discoverability”To learn what actions a domain exposes:
GET /orgs/{org_id}/domains/{domain_id}/actionsAuthorization: Bearer <token>Returns an OpenAPI 3.x document scoped to (org_id, domain_id) per
ADR-089 with per-action input requirements,
optional version-pin support, and customer-visible aggregation modes. Each action’s input schema
is a typed, documented contract — input names with their value types and descriptions, sourced from
the action’s canonical input ontology per
ADR-107 — so a caller can build
against it without reading the rules.
Required headers
Section titled “Required headers”| Header | When | Example |
|---|---|---|
Authorization | Always (except public paths) | Bearer eyJ... or Bearer sp_live_... |
Content-Type | POST/PUT with JSON body | application/json |
For /decide invocations the (org, domain) tenancy is not in the body — it is resolved
server-side from the domain-bound key; the body carries only action + context (plus optional
world_model_version and correlation_id). Management routes carry tenancy in the URL path
(/orgs/{org_id}/domains/{domain_id}/...). API keys are bound to a specific
(org_id, domain_id) at mint time and cannot be reused across domains.
Common workflows
Section titled “Common workflows”1. Onboarding
Section titled “1. Onboarding”POST /auth/register -> org + first memberGET /auth/me -> user profile + org/domain listPOST /orgs/{org_id}/domains -> create a domain (operator-assisted)POST /orgs/{org_id}/domains/{domain_id}/keys -> API key for programmatic access2. Discover deployed actions
Section titled “2. Discover deployed actions”GET /orgs/{org_id}/domains/{domain_id}/actions -> OpenAPI for deployed actionsGET /orgs/{org_id}/domains/{domain_id}/world-model -> current world-model version + context schema + configuration3. Invoke a decision
Section titled “3. Invoke a decision”POST /decide -> binding { status, work_frame, decision_metadata }The agent acts on the work frame. Decisions are binding (per ADR-077); the caller cannot override.
4. Inspect decisions through the dashboard
Section titled “4. Inspect decisions through the dashboard”GET /orgs/{org_id}/domains/{domain_id}/decisions -> recent-decisions feedGET /orgs/{org_id}/domains/{domain_id}/decisions/{decision_id} -> detail with audit-chain traceGET /orgs/{org_id}/domains/{domain_id}/system-card -> deployment-scoped operational recordPOST /orgs/{org_id}/domains/{domain_id}/decisions/{decision_id}/review-request -> flag decision for operator reviewPOST /orgs/{org_id}/domains/{domain_id}/decisions/{decision_id}/noteworthy -> mark decision noteworthyThe review-request and noteworthy primitives route to the operator side as override-pattern signals. See Customer Dashboard for the v0 inspection surface.
5. Operator workflows
Section titled “5. Operator workflows”Operator routes under /operator/* are scoped to the operations org role. Drive
authoring, distillation, publication, and override-pattern triage through the
Operations app.
Error responses
Section titled “Error responses”All errors follow RFC 9457 Problem Details per ADR-006 D3:
{ "type": "https://spectral.dev/errors/not-found", "title": "Resource Not Found", "status": 404, "detail": "Decision dec_abc123 not found"}| Status | Meaning |
|---|---|
| 400 | Bad request — invalid parameters or business rule violation |
| 401 | Not authenticated — missing or invalid token |
| 403 | Forbidden — authenticated but lacks required scope |
| 404 | Not found — resource doesn’t exist or not in tenant |
| 422 | Validation error — request body failed Pydantic validation |
| 429 | Rate limited (per ADR-084 D1 multi-dimensional rate limits) |
| 500 | Server error |
Validation failures on the supplied context of a /decide request return
status: "YELLOW" with next_action: "gather_evidence_and_retry" rather than HTTP 422 —
schema validation is part of the decision contract, not a transport-level error.
Response envelope
Section titled “Response envelope”Collections return a paginated wrapper per ADR-006 D3:
{"results": [...], "pagination": {"total": 42, "limit": 20, "offset": 0}}Single items return the object directly (flat, no wrapper).
Decision responses (/decide) are the documented { status, work_frame, decision_metadata }
shape per ADR-077 D3 — not the generic single-item shape.
Errors use RFC 9457 Problem Details.
Endpoint groups
Section titled “Endpoint groups”The API is organized into the following resource-based router modules:
| Group | Prefix | Description |
|---|---|---|
| Auth | /auth/* | Registration, login, logout, OAuth, password reset, profile |
| Orgs | /orgs/{org_id}/* | Org management, members, invites |
| Domains | /orgs/{org_id}/domains/{domain_id}/* | Domain settings, members, action registry view, World Model view, System Card |
| Keys | .../keys | API key lifecycle (create, list, revoke) per (org_id, domain_id) |
| Actions | .../actions | Action discoverability per ADR-089 |
| Decisions | .../decisions/* | Recent-decisions feed, per-decision audit-chain detail, review-request + noteworthy primitives |
| Decision API | /decide | The decision API per ADR-077; MCP equivalent per ADR-088 |
| Operator | /operator/* | Operator workflows — authoring, distillation, publication, override-pattern triage (requires operations org role) |
For the complete list of endpoints with request/response schemas, see the Swagger UI at /docs.