Documentation

AuditTrail API Reference

Complete REST + WebSocket surface for AuditTrail v3.45.0.

Base URL: /api/v1 (all REST routes; the SCIM provisioning endpoints are the only exception — they live under /scim/v2 per the SCIM spec). Request and response bodies are JSON unless noted (CSV / PDF exports stream files).

Authentication & authorization

AuditTrail accepts several credential types depending on the route group:

CredentialHeader / mechanismUsed by
Session cookieaudittrail_session (HttpOnly JWT, set by POST /auth/login)Dashboard / browser
Ingest API keyAuthorization: Bearer sk-at-…Agent SDKs, OTLP exporters
Gateway virtual keyAuthorization: Bearer at-gw-…Gateway proxy only
Runner tokenAuthorization: Bearer sk-atd-… (or ?token=)Local runner daemon (WS + GET /runner/status)
SCIM tokenAuthorization: Bearer scim-…IdP SCIM provisioning under /scim/v2
CSRF double-submitaudittrail_csrf cookie echoed in a headerAll cookie-auth mutations

The dependency names you'll see referenced below map to:

  • get_current_user — session cookie required.
  • require_user_or_apikey — session cookie or an sk-at-… ingest key.
  • require_user_apikey_or_runner_token — adds the daemon's sk-atd-… token.
  • require_admin — tenant admin role.
  • require_superadmin — cross-tenant is_superadmin flag.

User isolation (Hard Rule 8): every owned row is filtered by user_id == current_user.id. Cross-tenant reads return 404 (existence is not leaked) on the newer route groups and 403 on a few older ones — the purpose column notes where this matters.

Response envelope: trace / span / DAG / evaluation / ablation GETs wrap their payload in {"data": …}. Read resp.json()["data"], not resp.json(). Newer route groups (alerts, datasets, evals, agent-registry, runner, organizations, SSO/SCIM, compliance) return bare JSON or a typed object.


Auth & identity

routes/auth.py — prefix /api/v1/auth. Login / register are rate-limited (10/minute); password reset is tighter (5/minute).

MethodPathAuthPurpose
POST/auth/registernoneCreate an account (first user → admin, rest → viewer)
POST/auth/loginnoneAuthenticate; sets session + CSRF + refresh cookies
POST/auth/logoutcookie (best-effort)Clear cookies, revoke refresh tokens, audit-log
GET/auth/mecookieCurrent user profile
PATCH/auth/profilecookieUpdate display name and/or password
POST/auth/forgot-passwordnoneIssue a reset token (logged server-side in v1.x)
POST/auth/reset-passwordnoneConsume a reset token, set new password
POST/auth/verify-emailnoneConsume an email-verification token
POST/auth/resend-verificationcookieRe-issue a verification token
POST/auth/refreshrefresh cookieRotate refresh token + mint a new access JWT (theft-chain detection)
POST/auth/demo-loginnoneOne-click public demo user (seeds sample data on first use)

POST /auth/login request:

json
{ "email": "user@example.com", "password": "secure-password" }

Response (200):

json
{ "user_id": "uuid", "email": "user@example.com", "role": "admin", "display_name": "Alice" }

Sets audittrail_session (HttpOnly, SameSite=Strict, 24h), audittrail_csrf (JS-readable, SameSite=Lax), and audittrail_refresh (HttpOnly, scoped to /api/v1/auth, 30d).

Admin (superadmin, cross-tenant)

routes/admin.py — prefix /api/v1/admin. Every route requires require_superadmin; mutations write audit-log rows.

MethodPathPurpose
GET/admin/usersList every user across tenants
GET/admin/users/{user_id}One user's detail
PATCH/admin/users/{user_id}Promote/demote or (de)activate (self-protection enforced)
DELETE/admin/users/{user_id}Soft-delete (is_active=False)
GET/admin/audit-logPaginated audit-log entries
GET/admin/audit-log/exportStream the full audit log as CSV
GET/admin/tenantsList tenants (role=admin users) + trace counts
GET/admin/instance/infoInstance-wide stats
GET/admin/databaseSQLite health: on-disk db/WAL/-shm sizes, read-only PRAGMA counters (page/freelist/journal-mode), per-table row counts busiest-first (v3.36.0; strictly read-only — degrades honestly on in-memory/non-SQLite backends). UI: /admin/database

Organizations, members, invites

routes/organizations.py — prefix /api/v1/organizations. RBAC ranks owner > admin > annotator > viewer via require_org_role. See SSO & SCIM for the SAML/SCIM surface that sits on top.

MethodPathAuthPurpose
GET/organizationsuser/apikeyList orgs the caller belongs to
POST/organizationsuser/apikeyCreate an org (caller becomes owner)
GET/organizations/{org_id}memberOrg detail + your role
PATCH/organizations/{org_id}admin+Update (plan change is owner-only)
GET/organizations/{org_id}/membersmemberList members
PATCH/organizations/{org_id}/members/{member_id}admin+Change role (can't demote last owner)
DELETE/organizations/{org_id}/members/{member_id}admin+Remove member (can't remove last owner)
POST/organizations/{org_id}/invitesadmin+Issue an invite (one-time token)
GET/organizations/{org_id}/invitesadmin+List invites
DELETE/organizations/{org_id}/invites/{invite_id}admin+Revoke an invite
POST/organizations/accept-inviteuser/apikeyAccept an invite token (matched on caller email)

Projects

routes/projects.py — prefix /api/v1/organizations/{org_id}/projects.

MethodPathPurpose
GET…/projectsList projects in an org
POST…/projectsCreate a project
GET…/projects/{project_id}Project detail
PATCH…/projects/{project_id}Update a project
DELETE…/projects/{project_id}Delete a project

API keys

routes/api_keys.py — prefix /api/v1/api-keys. The plaintext sk-at-… secret is shown once on create.

MethodPathPurpose
GET/api-keysList keys (secret masked to prefix)
POST/api-keysCreate an ingest key pair (returns the one-time secret)
DELETE/api-keys/{key_id}Revoke a key

Traces & spans

routes/traces.py + routes/spans.py — prefix /api/v1. All get_current_user-scoped; GETs use the {"data": …} envelope.

MethodPathPurpose
GET/tracesList with filters + cursor pagination
GET/traces/facetsDistinct values for filter dropdowns
GET/traces/exportExport filtered traces as CSV (≤10k rows)
GET/traces/{trace_id}One trace + summary stats + tools used
DELETE/traces/flush-alladmin — delete all of the caller's traces
DELETE/traces/{trace_id}Delete one trace (cascade)
DELETE/tracesBulk-delete by ID list
GET/traces/{trace_id}/dagReconstructed decision DAG (React Flow shape)
GET/traces/{trace_id}/spansAll spans on a trace
GET/traces/{trace_id}/timelineSpans + computed depth (waterfall)
GET/spans/{span_id}One span with full input/output/attributes
GET/spans/{span_id}/tool-callsTool calls attached to a span

GET /traces query parameters: status, agent_name, environment, model, tool (traces with at least one tool span of that name — v3.26.0), created_after, created_before, search, sort_by (created_at default), sort_order (desc), limit (1–200, default 50), cursor.

Response (200):

json
{
  "data": [
    {
      "id": "trace-uuid", "session_id": "session-uuid", "root_span_id": "span-uuid",
      "agent_name": "researcher", "environment": "production", "status": "complete",
      "created_at": "2026-06-30T10:00:00Z", "completed_at": "2026-06-30T10:00:05Z",
      "total_duration_ms": 5200, "total_tokens_in": 1200, "total_tokens_out": 450,
      "total_cost": 0.0285, "metadata_": {"tags": {"version": "1.0"}}, "span_count": 6
    }
  ],
  "meta": { "total": 150, "cursor": "eyJjcmVhdGVkX2F0Ijoi…", "has_more": true }
}

The cursor is a base64-encoded {"created_at": <iso>, "id": <str>}; pass it back as ?cursor= to fetch the next page.


Ingest

routes/ingest.py — prefix /api/v1/ingest. The data-plane entry point. require_user_or_apikey; spans are upserted on span_id (idempotent, at-least-once). Each route is rate-limited 60/minute. See the quickstart for full agent-integration patterns.

MethodPathPurpose
POST/ingest/spansBatch-ingest spans (≤500), run constitutional eval, broadcast over WS
POST/ingest/tracesCreate/update a trace + auto-register the agent
POST/ingest/otlpOTLP/JSON ingest — maps gen_ai.* attributes (≤2000 spans, zero-SDK)

POST /ingest/spans request:

json
{
  "batch": [
    {
      "span_id": "span-uuid", "trace_id": "trace-uuid", "parent_span_id": null,
      "name": "gpt-4o-step-0", "span_type": "llm",
      "start_time": "2026-06-30T10:00:00Z", "end_time": "2026-06-30T10:00:01Z",
      "status": "ok", "model": "gpt-4o",
      "input": {"prompt": "…"}, "output": {"response": "…"},
      "tokens_in": 350, "tokens_out": 120, "cost": 0.0071,
      "tool_calls": [
        { "tool_name": "web_search", "arguments": {"query": "…"},
          "result": {"output": "…"}, "selected_from": ["web_search", "calculator"] }
      ],
      "attributes": {"temperature": 0.7}
    }
  ]
}

Response (200): { "accepted": 5, "errors": null }. Partial failures land in errors. The OTLP endpoint returns { "accepted", "rejected", "partialSuccess?" }.

Zero-SDK OTLP: point an OpenTelemetry exporter at the OTLP route with an Authorization: Bearer sk-at-… header — gen_ai.* attributes map onto native span fields. JS/Node exporters can target it directly with OTEL_EXPORTER_OTLP_PROTOCOL=http/json; the Python OTel SDK does not implement that protocol option, so bridge through an OpenTelemetry Collector (otlphttp exporter with encoding: json) — full recipes on OpenTelemetry integration.


XAI & causal attribution

Ablation (causal attribution)

routes/ablation.py — prefix /api/v1/ablation. get_current_user. Ablation is opt-in and cached; always preview cost with /estimate first.

MethodPathPurpose
GET/ablation/by-trace/{trace_id}Most-recent completed result for a trace (or null)
POST/ablation/estimateCost + segment-count preview (no run)
POST/ablation/segments/previewAuto-segment an arbitrary prompt
POST/ablation/runStart a job (confirmed: true required)
GET/ablation/{job_id}Job status + full result
GET/ablation/{job_id}/sankeyd3-sankey data (409 until complete)
GET/ablation/{job_id}/shapSHAP feature importance + tool probabilities

Counterfactuals & NL explanations

routes/xai.py — prefix /api/v1/xai. require_user_or_apikey, rate-limited 20/minute. See Counterfactuals and NL Explanations.

MethodPathPurpose
POST/xai/explain-attributionsNatural-language "why this tool?" summary for a span
POST/xai/counterfactuals"What input would have changed the decision?" candidates

SAE mechanistic XAI

routes/v20.py (sae_router) — prefix /api/v1/sae. require_user_or_apikey. See SAE mechanistic XAI.

MethodPathPurpose
GET/sae/supported-modelsCatalog of SAE-supported models for the UI empty state
GET/sae/candidate-traces"Which of my runs support SAE?" — user-scoped traces with ≥1 span on a supported open-weight model (honestly empty on closed-weight demos)
GET/sae/span/{span_id}Per-span SAE state (cached features, config readiness, honest reason)
POST/sae/extractTrigger extraction for a span (gated on saelens+torch+HF key)

Governance & rules

routes/rules.py — prefix /api/v1. Reads use get_current_user; mutations require require_admin. Rules live in YAML (Hard Rule 11); a tenant's edits are stored as overrides. Per-user rule bindings scope which rules fire on which spans (v2.7.0 E5).

MethodPathAuthPurpose
GET/rulescookieEffective rules for the tenant
GET/rules/{rule_id}cookieOne effective rule
PATCH/rules/{rule_id}adminUpdate a tenant override
DELETE/rules/{rule_id}adminRemove the tenant override
POST/rulesadminAdd a tenant-owned override from YAML
POST/rules/validatecookieParse/validate YAML without loading
POST/rules/reloadadminReload instance-default rules from disk
GET/rules/{rule_id}/bindingscookieList the caller's scope bindings on a rule
POST/rules/{rule_id}/bindingscookieAttach a scope binding
DELETE/rules/{rule_id}/bindings/{binding_id}cookieRemove a scope binding

REGO policies (Governance 2.0)

routes/v20.py (policy_router) — prefix /api/v1/policies. require_user_or_apikey.

MethodPathPurpose
GET/policiesList the caller's REGO policies
POST/policiesCreate a policy (inline REGO or external OPA URL)
POST/policies/simulateEvaluate a policy against an input doc
DELETE/policies/{policy_id}Delete a policy

Constitutional evaluations

routes/evaluations.py — prefix /api/v1. get_current_user.

MethodPathPurpose
GET/evaluationsList evaluation results (filter by trace/rule/severity/passed)
GET/evaluations/summaryCompliance summary stats (optionally per-trace)
PATCH/evaluations/{eval_id}Mark an evaluation reviewed
GET/constitutional/rulesRules + evaluation statistics (compliance dashboard)
GET/constitutional/summaryFrontend-shaped compliance summary + trend markers
GET/constitutional/evaluationsRicher evaluation list for the compliance page

Evals & datasets

Two distinct surfaces — see Evaluations.

Datasets

routes/datasets.py — prefix /api/v1/datasets. require_user_or_apikey.

MethodPathPurpose
GET/datasetsList datasets (with item counts)
POST/datasetsCreate a dataset
GET/datasets/{dataset_id}Dataset detail
DELETE/datasets/{dataset_id}Archive a dataset
GET/datasets/{dataset_id}/itemsList items
POST/datasets/{dataset_id}/itemsAdd an item
DELETE/datasets/{dataset_id}/items/{item_id}Delete an item

Eval runs

routes/evals.py — prefix /api/v1/evals. require_user_or_apikey. Runs execute a built-in evaluator over a dataset in a detached task.

MethodPathPurpose
GET/evals/evaluatorsBuilt-in evaluator catalog
GET/evals/runsList the caller's runs
POST/evals/runsStart a run over a dataset
GET/evals/runs/{run_id}Run status + mean score
GET/evals/runs/{run_id}/itemsPer-item scores

Annotation queues

routes/annotations.py — prefix /api/v1/annotations. require_user_or_apikey.

MethodPathPurpose
GET/annotations/queuesList annotation queues
POST/annotations/queuesCreate a queue
POST/annotationsCreate an annotation
GET/annotations/queues/{queue_id}List annotations in a queue

Deployments & runtime controls

routes/deployments.py — prefix /api/v1/deployments/actions. require_user_or_apikey; state-changing routes 30/minute. The 3-tier safety model and the executor capability matrix are documented in Deployment actions and Executable actions; the read-only runtime-control projection powers Run status.

MethodPathPurpose
GET/deployments/actions/capabilitiesPer-action executor capability matrix
GET/deployments/actions/runtime-controlsThe caller's ACTIVE gateway-enforced controls (read-only)
POST/deployments/actions/runtime-controls/{control_id}/deactivateManually deactivate an active runtime control (30/minute)
GET/deployments/actionsList actions (filter by status)
POST/deployments/actionsPropose an action (autonomous Tier-1 auto-executes)
POST/deployments/actions/{action_id}/approveApprove (within TTL)
POST/deployments/actions/{action_id}/rejectReject
POST/deployments/actions/{action_id}/mark-executedRecord external execution

Attribution evidence (ActionOut.evidence, v3.30.0). When a proposal's target_ref resolves to one of the caller's traces or spans, the propose path stores the no-LLM surrogate SHAP attribution for the decision-relevant tool span on the action row: {version, source ("surrogate" | "heuristic"), explanation, top_features[{name, value, shap_value, description}], surrogate_f1, tool, span_id, trace_id, violation{rule_id, rule_name, severity} | null, computed_at}. source is honest provenance — "surrogate" only when the startup-trained model exists; surrogate_f1 is null on the heuristic fallback. Actions without a resolvable trace/span target have evidence: null. Computed best-effort; never blocks or fails the propose.

Runtime-control fields (RuntimeControlOut). Each active control row carries id, control_type (switch_model / throttle / disable_flag), target, params_json, source_action_id (the deployment_actions row that created it), created_at, and expires_at (v3.9.0). expires_at is a nullable TTL — null means the control is permanent until deactivated; a non-null timestamp is when the executor's background reaper will auto-deactivate it (fail-safe: an expired throttle/disable/switch stops enforcing). See Run status for the TTL and deactivate lifecycle.

Deactivate (v3.10.0) — POST /deployments/actions/runtime-controls/{control_id}/deactivate flips the control's active flag to false so the gateway stops enforcing it, then broadcasts runtime_control.expired on the owner's WebSocket. User-scoped (Hard Rule 8): only the caller's own active controls are addressable, so a control that is already inactive or belongs to another tenant returns 404. Returns the updated RuntimeControlOut.


Agent registry & secrets

Agent registry (immutable versioning)

routes/agent_registry.py — prefix /api/v1/agent-registry. require_user_or_apikey; cross-tenant → 404; mutations 20/minute. See Agent registry.

MethodPathPurpose
GET/agent-registry/List the caller's definitions
POST/agent-registry/Create a definition
GET/agent-registry/{definition_id}Definition detail + current version
DELETE/agent-registry/{definition_id}Delete a definition (cascades versions)
POST/agent-registry/{definition_id}/versionsCreate an immutable, validated version
GET/agent-registry/{definition_id}/versionsList versions (newest first)
GET/agent-registry/{definition_id}/versions/{version}One version (full files_json)
POST/agent-registry/{definition_id}/promote/{version}Set the current (default-dispatch) version

Agent secrets (gateway-only BYOK)

routes/agent_secrets.py — prefix /api/v1/agent-secrets. require_user_or_apikey; cross-tenant → 404; mutations 30/minute. The plaintext value is write-only — never returned, never logged. See Gateway secrets.

MethodPathPurpose
GET/agent-secretsList secrets (metadata + last4 only)
POST/agent-secretsStore a provider secret (encrypted on receipt)
PUT/agent-secrets/{secret_id}Rotate (re-encrypt) a secret
DELETE/agent-secrets/{secret_id}Delete a secret

Agent templates & agents

routes/agent_templates.py — prefix /api/v1/agent-templates (require_user_or_apikey). See Running agents from chat.

MethodPathPurpose
GET/agent-templatesTemplate catalog
GET/agent-templates/{template_id}One template
POST/agent-templates/{template_id}/renderRender a runnable snippet
GET/agent-templates/proposals/{correlation_id}Have traces for this correlation id landed yet?

routes/agents.py — prefix /api/v1/agents. Reads get_current_user; create / delete require require_admin.

MethodPathPurpose
GET/agentsList registered agents
POST/agentsRegister an agent (admin)
GET/agents/{agent_id}One agent
DELETE/agents/{agent_id}Remove an agent (admin)

Runner daemon

routes/runner.py — prefix /api/v1/runner. require_user_or_apikey (except /status, which also accepts the daemon's sk-atd-… runner token). Dispatch is gated by a per-user template allowlist. See Local runner and Run status.

MethodPathAuthPurpose
POST/runner/tokensuser/apikeyMint a daemon token (one-time sk-atd-… secret)
GET/runner/tokensuser/apikeyList tokens (no secrets)
DELETE/runner/tokens/{token_id}user/apikeyRevoke a token + force-disconnect the daemon
GET/runner/statususer/apikey/runner-tokenConnection + last-dispatch summary
POST/runner/dispatchuser/apikeySend a run to the connected daemon
GET/runner/dispatches/{dispatch_id}user/apikeyPoll status + stdout tail
GET/runner/dispatches/{dispatch_id}/streamuser/apikeySSE tail of the ring buffer
DELETE/runner/dispatches/{dispatch_id}user/apikeyCancel a dispatch (best-effort)
GET/runner/allowlistuser/apikeyEffective allowlist + addable catalog
POST/runner/allowlistuser/apikeyAllowlist a built-in or agent:<id> reference
DELETE/runner/allowlist/{entry_id}user/apikeyRemove an allowlist entry

POST /runner/dispatch request:

json
{
  "template_id": "quickstart",
  "prompt": "Compute 12 * 8 and explain the result",
  "model": "gpt-4o-mini",
  "env_overrides": {},
  "version": null
}

template_id is a built-in (quickstart / web-search) or a registered agent reference (agent:<definition_id>). A value not in the caller's effective allowlist returns 422 {"error": "template_not_allowed", "allowed": [...]}; no connected daemon returns 404.

Response (201): { "dispatch_id": "…", "correlation_id": "…" }.

Dispatch detail (GET /runner/dispatches/{id}DispatchDetailOut):

json
{
  "id": "…",
  "correlation_id": "…",
  "template_id": "agent:…",
  "prompt": "…",
  "status": "succeeded",
  "stdout_tail": [{ "stream": "stdout", "line": "…", "at": "…" }],
  "trace_id": "…",
  "created_at": "…",
  "started_at": "…",
  "completed_at": "…",
  "error_log": null,
  "agent_definition_id": "…",
  "agent_version_id": "…",
  "agent_version": 3
}

The three version-provenance fields — agent_definition_id, agent_version_id, agent_version (v3.11.0) — record which registered agent version actually ran. They are null for built-in templates (quickstart / web-search) and populated only for agent:<id> dispatches. Note the last-dispatch summary in GET /runner/status (DispatchSummary) carries the lighter pair agent_definition_id + agent_version only; agent_version_id appears solely on the full detail response above. See Local runner.

The daemon connects over WebSocket at /api/v1/runner/ws (below).


Gateway proxy

routes/gateway.py — an OpenAI-compatible reverse proxy that traces every LLM call. Virtual keys (at-gw-…) are managed under /api/v1/gateway (require_user_or_apikey); the proxy itself lives under /api/v1/gateway/proxy/v1 and authenticates with the virtual key as a Bearer token. See Gateway proxy.

MethodPathAuthPurpose
POST/gateway/keysuser/apikeyCreate a virtual key (one-time at-gw-… secret)
GET/gateway/keysuser/apikeyList virtual keys
POST/gateway/keys/{key_id}/revokeuser/apikeyRevoke a virtual key
POST/gateway/proxy/v1/chat/completionsvirtual keyOpenAI-compatible chat completions (streaming + non-streaming)
GET/gateway/proxy/v1/modelsvirtual keyList routed model IDs

Calling the proxy — point any OpenAI SDK at it:

bash
export OPENAI_BASE_URL="https://auditrail.yourco.com/api/v1/gateway/proxy/v1"
export OPENAI_API_KEY="at-gw-YOUR-VIRTUAL-KEY"

Optional headers / body fields the proxy understands (stripped before forwarding upstream): X-AuditTrail-Secret selects a stored gateway secret as the upstream credential; audittrail_prompt_key opts the request into prompt-canary attribution. Active runtime controls (switch_model / throttle / disable_flag) are applied to the owner's gateway-routed traffic before the call forwards. Every completion is logged as an llm span — constitutional rules fire exactly as for SDK-originated spans.

Unsupported model → 400 (v3.7.0). When a provider key is configured and the request names a model the gateway doesn't recognise, the proxy refuses with a 400 rather than fabricating a response:

json
{
  "error": "unsupported_model",
  "model": "totally-made-up-model",
  "supported": ["gpt-*", "o1/o3/o4*", "openai/*", "claude-*", "anthropic/*", "minimax*", "ollama/*", "gemini-*", "qwen*", "mock/* (keyless dev only)"]
}

The check runs before both the streaming and non-streaming branches (a stream can't be un-started once it begins). The keyless dev-mock fallback — no provider key configured at all — is unaffected and still serves a deterministic mock/* completion. See Gateway proxy.


Prompts, canary & optimize

routes/prompts.py, routes/prompt_canary.py, routes/prompt_optimize.py — all under prefix /api/v1/prompts. require_user_or_apikey; cross-tenant → 404. Canary mutations 10/minute. See Prompt canary.

MethodPathPurpose
GET/promptsLatest version per prompt key
POST/promptsCreate a new prompt version
GET/prompts/{prompt_key}/versionsAll versions for a key
GET/prompts/{prompt_key}/productionThe production version
POST/prompts/{prompt_key}/productionPromote a version to production
GET/prompts/canary/activeAll active canary deployments (any status)
GET/prompts/{prompt_key}/canary/activeActive deployment for one key
POST/prompts/{prompt_key}/canary/proposeCreate a proposed deployment
POST/prompts/{prompt_key}/canary/startproposed → ramping
POST/prompts/{prompt_key}/canary/rampSet weight (10/25/50, clamped to max)
POST/prompts/{prompt_key}/canary/pauseramping → analyzing
POST/prompts/{prompt_key}/canary/promoteanalyzing → stable (swap production)
POST/prompts/{prompt_key}/canary/rollbackAny non-terminal → rolled_back
POST/prompts/{prompt_key}/optimize/estimateCost preview for an optimizer run
POST/prompts/{prompt_key}/optimizeRun the prompt optimizer (confirmed gate)

Chat sessions & assistant

routes/chat_sessions.py — prefix /api/v1/chat/sessions (require_user_or_apikey; mutations 20/minute). routes/assistant.py — prefix /api/v1/assistant. See Operations Assistant.

MethodPathPurpose
GET/chat/sessionsList sessions (archived filter, cursor pagination)
POST/chat/sessionsCreate an empty session
GET/chat/sessions/{session_id}Session + a page of messages
PATCH/chat/sessions/{session_id}Rename / archive
DELETE/chat/sessions/{session_id}Delete (cascades messages)
POST/chat/sessions/{session_id}/messagesAppend a message (idempotent on consecutive duplicates)
GET/assistant/keysList BYOK provider keys
POST/assistant/keysAdd a provider key
DELETE/assistant/keys/{key_id}Delete a provider key
POST/assistant/keys/{key_id}/testFire a tiny completion to verify a key
POST/assistant/chatSSE-streamed chat completion (emits generative-UI tiles)

Compliance

routes/compliance_admin.py — prefix /api/v1/compliance. get_current_user; superadmins see all tenants. EU AI Act article posture + Article 73 incident log + regulator-ready export. See Compliance.

MethodPathPurpose
GET/compliance/statusPer-article (12/13/26/50/73) compliance snapshot
POST/compliance/incidentsLog a serious incident (Article 73)
GET/compliance/incidentsList incidents (status filter)
GET/compliance/exportRegulator-ready JSON over a period (default 180d)
GET/compliance/export.pdfSame data as a downloadable PDF

Alerts & webhooks

Alerts

routes/alerts.py — prefix /api/v1/alerts. require_user_or_apikey. See Alerts.

MethodPathPurpose
GET/alerts/rulesList the caller's alert rules
POST/alerts/rulesCreate a rule (validates the metric)
PATCH/alerts/rules/{rule_id}Update a rule
DELETE/alerts/rules/{rule_id}Delete a rule (cascades events)
GET/alerts/eventsRecent fired events
POST/alerts/test/{rule_id}Dry-run a rule (compute metric, don't fire)
GET/alerts/metricsSupported metric identifiers

Webhooks

routes/webhooks.py — prefix /api/v1/webhooks. Reads get_current_user; mutations + test require require_admin.

MethodPathPurpose
GET/webhooksList webhook destinations
POST/webhooksRegister a destination (admin)
PATCH/webhooks/{webhook_id}Update a destination (admin)
DELETE/webhooks/{webhook_id}Remove a destination (admin)
POST/webhooks/{webhook_id}/testFire a synchronous test delivery (admin)

Outbound destinations support Slack, PagerDuty Events v2, and generic HTTPS with HMAC-SHA256 signing. The create/patch/delivery paths enforce an SSRF guard that blocks private/loopback/link-local/cloud-metadata targets.


Identity provider integration (SSO / SCIM)

routes/v21.py — SAML SSO + SCIM provisioning. See SSO & SCIM.

MethodPathAuthPurpose
POST/api/v1/saml/config/{org_id}org ownerCreate/update the org's SAML IdP config
GET/api/v1/saml/config/{org_id}org admin+Read SAML config
POST/api/v1/saml/acs/{org_slug}signed assertionAssertion Consumer Service (IdP POST → session)
POST/api/v1/scim/tokensorg ownerMint a SCIM bearer token (one-time secret)
GET/api/v1/scim/tokens/{org_id}org admin+List SCIM tokens
POST/scim/v2/UsersSCIM tokenProvision a user
GET/scim/v2/Users/{user_id}SCIM tokenRead a provisioned user
DELETE/scim/v2/Users/{user_id}SCIM tokenDeprovision (remove membership)

Pause / resume

routes/pauses.py — prefix /api/v1/pauses. require_user_or_apikey; mutations 12/minute. The Edit + Resume path is gated by AUDITTRAIL_EXPERIMENTAL_EDIT_RESUME. See Pause / resume.

MethodPathPurpose
POST/pausesSDK announces a pause checkpoint
GET/pauses/{trace_id}/activeActive (or awaiting-edit) pause for a trace, or null
GET/pauses/{pause_id}One pause by id — any status, terminal included (the SDK reads its resumed/expired/abandoned state here once /active returns null)
POST/pauses/{pause_id}/heartbeatSDK keepalive (stale → reaper marks abandoned)
POST/pauses/{pause_id}/ackSDK confirms an edit was applied (awaiting_edit → resumed)
POST/pauses/{pause_id}/resumeOperator resume (optionally with an edited state blob)

Fleet

routes/fleet.py — prefix /api/v1/fleet. require_user_or_apikey. See Live Fleet.

MethodPathPurpose
GET/fleet/snapshotPer-agent summary for the caller's recent traces
GET/fleet/topologyAggregated agent → tool topology graph

Reports

routes/reports.py — prefix /api/v1/reports. get_current_user.

MethodPathPurpose
POST/reports/generateGenerate a PDF audit report (synchronous)
GET/reportsList reports (newest first)
GET/reports/{report_id}Report status + metadata
GET/reports/{report_id}/downloadDownload the PDF (409 until ready)

Analytics

routes/analytics.py — prefix /api/v1/analytics. get_current_user; results cached 60s.

MethodPathPurpose
GET/analytics/overviewHigh-level dashboard metric cards
GET/analytics/costCost grouped by agent / model / tool
GET/analytics/latencyp50/p95/p99 latency by day
GET/analytics/toolsTool usage stats
GET/analytics/cost-timeseriesDaily cost totals
GET/analytics/tools-timeseriesDaily tool-call counts
GET/analytics/slowest-spansHighest-p99 spans
GET/analytics/fleet-whyFleet-level surrogate feature importances (v3.31.0)

Fleet WHY (GET /analytics/fleet-why, v3.31.0). Global feature importances of the tool-selection surrogate — what drives tool selection across the whole deployment, computed at train time as mean |SHAP| over the training set (importance_method: "mean_abs_shap"; falls back to "xgb_gain" when the SHAP explainer is unavailable). The surrogate is instance-global: it trains on the most recent 500 tool spans across the deployment, at API startup and automatically in the background once at least 5 tool spans with two distinct tools exist. Response: {trained, n_samples, surrogate_f1, trained_at, min_samples_required, importance_method, importances[{name, importance, description}], tool_distribution, user_tool_span_count, reason} — when untrained, importances is empty and reason states why (no fabricated values).


Settings, health & billing

routes/settings.py + routes/health.py — prefix /api/v1.

MethodPathAuthPurpose
GET/healthnoneService health, DB connectivity, rules loaded, uptime
GET/settingscookieEffective tenant settings
GET/settings/metricscookiePerformance metrics for the settings dashboard
PATCH/settingsadminUpdate tenant settings
GET/settings/pricingcookiePricing config: operator overrides, default rate, community-catalog size/freshness (v3.24.0)
GET/instancecookieInstance metadata (version, agents, rules, uptime)

routes/v20.py (billing_router) — prefix /api/v1/billing.

MethodPathAuthPurpose
GET/billing/subscriptions/{org_id}org viewer+Subscription state (auto-creates a free row)
POST/billing/checkoutorg ownerCreate a Stripe Checkout session (gated on AUDITTRAIL_STRIPE_SECRET_KEY)
POST/billing/webhookStripe signatureStripe webhook (HMAC-SHA256 verified)

WebSocket

routes/ws.py. The dashboard endpoints authenticate with the session JWT cookie; the runner endpoint authenticates with an sk-atd-… token. The canonical paths are under /api/v1/ws/* (the production Caddy only proxies /api/*); legacy /ws/* paths resolve to the same handlers.

PathAuthPurpose
/api/v1/ws/traces/{trace_id}session cookie + trace ownershipSubscribe to one trace (replays last 100 events)
/api/v1/ws/livesession cookieSubscribe to all of the caller's trace events
/api/v1/runner/wsrunner token (sk-atd-…)Local runner daemon link (run / cancel / output / exit)

Client messages: {"type":"ping"}, {"type":"subscribe","trace_id":"…"}, {"type":"unsubscribe","trace_id":"…"}, and {"type":"command", …} — the last proposes a DeploymentAction from the live DAG (same tier rules as the REST path; the WS path only proposes, it never auto-executes Tier 2/3).

Server events use the {"event": <type>, …} envelope (note: event, not type). Examples: span_start / span_end, trace_complete, constitutional_violation, pause_active / pause_resumed / pause.expired / pause.abandoned, canary.ramped / canary.rolled_back, command.proposed / command.ack, runner.connected / runner.disconnected / runner.dispatch.output / runner.dispatch.final.

span_start fires when a running span is ingested and span_end when a span arrives closed (ok / complete / error) — both HTTP and OTLP ingest broadcast them, so a streaming SDK run renders spans in the trace view live, without polling. trace_complete fires once, when the last running span closes and the trace transitions into a terminal status; its data carries {status, span_count, total_cost, total_duration_ms}.


Error responses

json
{ "detail": "Resource not found" }
StatusMeaning
400Bad request (validation, invalid cursor, unknown metric/action; gateway unsupported_model when a provider key is set)
401Unauthenticated (missing/invalid session, key, or token)
403Forbidden (insufficient role; older endpoints use 403 for cross-tenant)
404Not found (newer endpoints return 404 for cross-tenant — existence not leaked)
409Conflict (duplicate name/email, illegal state transition, already revoked)
410Gone (pause TTL expired / abandoned)
412Precondition failed (edit-resume requires the feature flag)
415Unsupported media type (OTLP requires application/json)
422Unprocessable entity (bad request body, bad agent bundle, disallowed runner template)
429Too many requests (per-route rate limit)
500Internal server error