AuditTrail API Reference
Base URL: /api/v1
All request and response bodies are JSON.
Authentication: All trace, analytics, evaluation, ablation, and span endpoints require a valid session cookie (set by POST /api/v1/auth/login). Unauthenticated requests return 401 Unauthorized. Ingest endpoints (POST /ingest/spans, POST /ingest/traces) use optional auth — authenticated users get traces assigned to their account; unauthenticated agents create unowned traces.
User isolation: Each user only sees traces where user_id matches their account. Traces created without authentication have user_id=NULL and are not visible to any user.
Response envelope: Most GET endpoints wrap their response in a {"data": ...} envelope. Always read resp.json()["data"] rather than resp.json() directly when consuming trace, span, DAG, or evaluation responses.
Health
GET /api/v1/health
Check service health, database connectivity, and uptime.
Response:
{
"status": "ok",
"version": "1.0.0",
"db_connected": true,
"rules_loaded": 6,
"uptime_seconds": 3600.5
}
| Field | Type | Description |
|---|---|---|
status | string | "ok" or "degraded" |
version | string | API version |
db_connected | boolean | Whether the database is reachable |
rules_loaded | integer | Number of enabled constitutional rules |
uptime_seconds | float | Seconds since server start |
Auth
POST /api/v1/auth/register
Create a new user account. The first registered user is assigned the admin role; all subsequent users receive viewer.
Request body:
{
"email": "user@example.com",
"password": "secure-password",
"display_name": "Alice"
}
| Field | Type | Required | Description |
|---|---|---|---|
email | string (email) | yes | User email address |
password | string | yes | Password (hashed with Argon2) |
display_name | string | no | Display name |
Response (201):
{
"user_id": "uuid",
"email": "user@example.com",
"role": "admin"
}
POST /api/v1/auth/login
Authenticate and receive a session cookie (HttpOnly JWT, 24h expiry).
Request body:
{
"email": "user@example.com",
"password": "secure-password"
}
Response (200):
{
"user_id": "uuid",
"email": "user@example.com",
"role": "admin",
"display_name": "Alice"
}
Sets cookie: audittrail_session (HttpOnly, SameSite=Lax, max-age=86400).
POST /api/v1/auth/logout
Clear the session cookie. No request body required.
Response: 204 No Content
GET /api/v1/auth/me
Get the authenticated user's profile. Requires session cookie.
Response (200):
{
"user_id": "uuid",
"email": "user@example.com",
"role": "admin",
"display_name": "Alice",
"created_at": "2026-03-01T12:00:00Z",
"last_login_at": "2026-03-30T08:15:00Z"
}
PATCH /api/v1/auth/profile
Update the current user's display name and/or password.
Request body:
{
"display_name": "Alice B.",
"password": "new-password"
}
Both fields are optional; at least one must be provided.
Response (200):
{
"updated": true,
"fields": ["display_name", "password"]
}
Traces
GET /api/v1/traces
List traces with filtering, sorting, and cursor-based pagination.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | -- | Filter by status (complete, error, running) |
agent_name | string | -- | Filter by agent name |
environment | string | -- | Filter by environment |
model | string | -- | Filter traces containing spans with this model |
created_after | datetime | -- | ISO 8601 lower bound |
created_before | datetime | -- | ISO 8601 upper bound |
search | string | -- | Search agent name or trace ID (case-insensitive) |
sort_by | string | created_at | Column to sort by |
sort_order | string | desc | asc or desc |
limit | integer | 50 | Page size (1-200) |
cursor | string | -- | Pagination cursor from previous response |
Response (200):
{
"data": [
{
"id": "trace-uuid",
"session_id": "session-uuid",
"root_span_id": "span-uuid",
"agent_name": "researcher",
"environment": "production",
"status": "complete",
"created_at": "2026-03-30T10:00:00Z",
"completed_at": "2026-03-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,
"tools_used": null
}
],
"meta": {
"total": 150,
"cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wMy0zMFQxMDowMDowMFoiLCJpZCI6InRyYWNlLXV1aWQifQ==",
"has_more": true
}
}
GET /api/v1/traces/{trace_id}
Get a single trace with summary statistics including tools used.
Response (200):
{
"data": {
"id": "trace-uuid",
"session_id": "session-uuid",
"root_span_id": "span-uuid",
"agent_name": "researcher",
"environment": "production",
"status": "complete",
"created_at": "2026-03-30T10:00:00Z",
"completed_at": "2026-03-30T10:00:05Z",
"total_duration_ms": 5200,
"total_tokens_in": 1200,
"total_tokens_out": 450,
"total_cost": 0.0285,
"metadata_": {},
"span_count": 6,
"tools_used": ["web_search", "calculator"]
}
}
DELETE /api/v1/traces/{trace_id}
Delete a trace and all associated data (spans, tool calls, evaluations).
Response (200):
{
"success": true
}
DELETE /api/v1/traces
Bulk-delete traces by ID list.
Request body:
{
"ids": ["trace-uuid-1", "trace-uuid-2"]
}
Response (200):
{
"data": {
"deleted": 2
}
}
DELETE /api/v1/traces/flush-all
Delete ALL traces in the database. Admin role required. For clearing development/seed data.
Response (200):
{
"data": {
"deleted": 26
}
}
| Status | Description |
|---|---|
| 200 | All traces deleted |
| 401 | Not authenticated |
| 403 | Admin role required |
GET /api/v1/traces/{trace_id}/dag
Get the reconstructed decision DAG for a trace, formatted for React Flow.
Response (200):
{
"data": {
"nodes": [
{
"id": "span-uuid",
"type": "llmNode",
"position": {"x": 0, "y": 0},
"data": {
"span_id": "span-uuid",
"name": "gpt-4o-step-0",
"span_type": "llm",
"status": "ok",
"duration_ms": 1200,
"tokens_in": 350,
"tokens_out": 120,
"cost": 0.0071,
"model": "gpt-4o",
"tool_name": null,
"selected_from": null,
"has_children": true,
"child_count": 2,
"children_count": 2,
"is_critical_path": true,
"tool_calls": null,
"depth": 0
}
}
],
"edges": [
{
"id": "edge-uuid",
"source": "span-uuid-1",
"target": "span-uuid-2",
"type": "default",
"animated": false,
"style": null
}
],
"total_nodes": 6
}
}
GET /api/v1/traces/{trace_id}/spans
Get all spans belonging to a trace, ordered by start time.
Response (200):
{
"data": [
{
"id": "span-uuid",
"trace_id": "trace-uuid",
"parent_span_id": null,
"span_type": "agent",
"name": "researcher-orchestrator",
"start_time": "2026-03-30T10:00:00Z",
"end_time": "2026-03-30T10:00:05Z",
"duration_ms": 5200,
"status": "ok",
"tokens_in": 0,
"tokens_out": 0,
"estimated_cost": 0.0,
"model": null,
"error_message": null,
"error_type": null
}
]
}
GET /api/v1/traces/{trace_id}/timeline
Get spans with computed depth for timeline/waterfall rendering.
Response (200):
{
"data": [
{
"id": "span-uuid",
"trace_id": "trace-uuid",
"parent_span_id": null,
"span_type": "agent",
"name": "researcher-orchestrator",
"start_time": "2026-03-30T10:00:00Z",
"end_time": "2026-03-30T10:00:05Z",
"duration_ms": 5200,
"status": "ok",
"tokens_in": 0,
"tokens_out": 0,
"estimated_cost": 0.0,
"model": null,
"error_message": null,
"error_type": null,
"depth": 0
}
]
}
GET /api/v1/traces/facets
Get distinct values for filter dropdowns (environments, models, agents, statuses).
Response (200):
{
"environment": ["development", "staging", "production"],
"model": ["gpt-4o", "gpt-4o-mini", "claude-3-5-sonnet"],
"agent_name": ["researcher", "qa-bot", "data-analyst"],
"status": ["complete", "error", "running"]
}
GET /api/v1/traces/export
Export filtered traces as a CSV file. Accepts the same filter query parameters as GET /traces (except limit, cursor, sort_by, sort_order).
Query parameters: status, agent_name, environment, model, search, created_after, created_before
Response: text/csv file download with headers:
id, agent_name, environment, status, created_at, completed_at, total_duration_ms, total_tokens_in, total_tokens_out, total_cost
Spans
GET /api/v1/spans/{span_id}
Get a single span with full metadata including input, output, and attributes.
Response (200):
{
"data": {
"id": "span-uuid",
"trace_id": "trace-uuid",
"parent_span_id": "parent-span-uuid",
"span_type": "llm",
"name": "gpt-4o-step-1",
"start_time": "2026-03-30T10:00:01Z",
"end_time": "2026-03-30T10:00:02Z",
"duration_ms": 1200,
"status": "ok",
"tokens_in": 350,
"tokens_out": 120,
"estimated_cost": 0.0071,
"model": "gpt-4o",
"error_message": null,
"error_type": null,
"input": {"prompt": "Research the latest AI safety papers"},
"output": {"response": "Here are the top papers..."},
"attributes": {"temperature": 0.7}
}
}
GET /api/v1/spans/{span_id}/tool-calls
Get all tool calls associated with a span.
Response (200):
{
"data": [
{
"id": "toolcall-uuid",
"span_id": "span-uuid",
"tool_name": "web_search",
"arguments": {"query": "AI safety papers 2026"},
"result": {"data": "..."},
"duration_ms": 800,
"selected_from": ["web_search", "calculator", "read_file"],
"selection_confidence": 0.92
}
]
}
Ingest
These endpoints are the data-plane entry point for instrumented agents. Spans are upserted (idempotent, at-least-once delivery). Traces and agents are auto-created as needed.
POST /api/v1/ingest/spans
Batch-ingest spans. Maximum batch size: 500. Also runs constitutional rule evaluation on completed spans and broadcasts violations via WebSocket.
Request body:
{
"batch": [
{
"span_id": "span-uuid",
"trace_id": "trace-uuid",
"parent_span_id": "parent-uuid",
"name": "gpt-4o-step-0",
"span_type": "llm",
"start_time": "2026-03-30T10:00:00Z",
"end_time": "2026-03-30T10:00:01Z",
"status": "ok",
"model": "gpt-4o",
"input": {"prompt": "..."},
"output": {"response": "..."},
"error_message": null,
"tokens_in": 350,
"tokens_out": 120,
"cost": 0.0071,
"tool_calls": [
{
"tool_name": "web_search",
"arguments": {"query": "..."},
"result": {"data": "..."},
"selected_from": ["web_search", "calculator"]
}
],
"attributes": {"temperature": 0.7}
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
span_id | string | yes | Unique span identifier |
trace_id | string | yes | Parent trace identifier |
parent_span_id | string | no | Parent span (for tree structure) |
name | string | yes | Span name |
span_type | string | yes | llm, tool, agent, chain, retriever, embedding, custom |
start_time | datetime | yes | ISO 8601 timestamp |
end_time | datetime | no | ISO 8601 timestamp (null if still running) |
status | string | no | running, ok, complete, error (default: running) |
model | string | no | LLM model name |
input | object | no | Span input data |
output | object | no | Span output data |
error_message | string | no | Error description |
tokens_in | integer | no | Input token count |
tokens_out | integer | no | Output token count |
cost | float | no | Estimated cost in USD |
tool_calls | array | no | Embedded tool call records (see schema below) |
attributes | object | no | Arbitrary key-value metadata |
ToolCallInSpan schema:
Each entry in tool_calls must match:
| Field | Type | Required | Description |
|---|---|---|---|
tool_name | string | yes | Name of the tool invoked |
arguments | object | yes | Arguments passed to the tool (must be a JSON object, not a string) |
result | object / null | no | Tool result (must be a JSON object like {"output": "..."}, not a plain string — strings will cause a 422 validation error) |
selected_from | array / null | no | List of candidate tool names the agent chose from |
Common mistake: Passing a string for result instead of a dict. Always wrap string results:
{
"tool_name": "calculator",
"arguments": {"expression": "2 + 2"},
"result": {"output": "4"},
"selected_from": ["calculator", "web_search"]
}
Response (200):
{
"accepted": 5,
"errors": null
}
If some spans fail, errors contains descriptions:
{
"accepted": 3,
"errors": ["span abc123: duplicate key violation"]
}
POST /api/v1/ingest/traces
Create or update a trace record and auto-register the agent.
Request body:
{
"trace": {
"trace_id": "trace-uuid",
"session_id": "session-uuid",
"agent_name": "researcher",
"agent_version": "1.2.0",
"environment": "production",
"tags": {"team": "ml-ops"},
"metadata": {"experiment_id": "exp-42"}
}
}
Response (200):
{
"trace_id": "trace-uuid"
}
Ablation (Causal Attribution)
GET /api/v1/ablation/by-trace/{trace_id}
Get the most recent ablation result for a given trace. Used by the frontend to check if ablation results already exist.
Response (200):
{
"data": {
"job_id": "uuid",
"span_id": "uuid",
"trace_id": "uuid",
"status": "completed",
"num_segments": 5,
"actual_cost": 0.015,
"ablation_model": "demo",
"created_at": "2026-03-30T12:00:00",
"completed_at": "2026-03-30T12:00:05",
"has_sankey": true,
"has_shap": true
}
}
Returns { "data": null } if no ablation exists for this trace.
POST /api/v1/ablation/estimate
Estimate the cost and segment count for an ablation analysis without running it.
Request body:
{
"span_id": "span-uuid",
"trace_id": "trace-uuid"
}
Response (200):
{
"data": {
"span_id": "span-uuid",
"trace_id": "trace-uuid",
"prompt_preview": "Research the latest AI safety papers and summarize...",
"tool_selected": "web_search",
"candidates": ["web_search", "calculator", "read_file"],
"num_segments": 5,
"segments_preview": [
{"id": "seg-0", "text": "Research the latest"},
{"id": "seg-1", "text": "AI safety papers"}
],
"estimated_cost": 0.0,
"estimated_runs": 15,
"estimated_tokens": 7500
}
}
POST /api/v1/ablation/run
Start an ablation analysis job. Requires confirmed: true (use /estimate first to preview cost).
Request body:
{
"span_id": "span-uuid",
"trace_id": "trace-uuid",
"confirmed": true
}
Response (200):
{
"data": {
"job_id": "ablation-uuid",
"status": "complete",
"cached": false
}
}
If a cached result exists for the same prompt+model combination:
{
"data": {
"job_id": "existing-uuid",
"status": "complete",
"cached": true,
"message": "Returning cached result for identical prompt+model combination."
}
}
GET /api/v1/ablation/{job_id}
Get the status and results of an ablation job.
Response (200):
{
"data": {
"job_id": "ablation-uuid",
"span_id": "span-uuid",
"trace_id": "trace-uuid",
"status": "complete",
"num_segments": 5,
"estimated_cost": 0.0,
"actual_cost": 0.0,
"ablation_model": "demo",
"prompt_hash": "abc123...",
"surrogate_f1": 0.85,
"created_at": "2026-03-30T10:00:00Z",
"completed_at": "2026-03-30T10:00:03Z",
"error_message": null,
"segments": [...],
"segment_results": [
{
"segment_id": "seg-0",
"segment_text": "Research the latest",
"attribution_score": 0.45,
"original_tool": "web_search",
"ablated_tool": "web_search",
"tool_changed": false,
"original_scores": {"web_search": 0.8, "calculator": 0.1, "read_file": 0.1},
"ablated_scores": {"web_search": 0.5, "calculator": 0.3, "read_file": 0.2}
}
],
"tool_selected": "web_search",
"candidates": ["web_search", "calculator", "read_file"]
}
}
GET /api/v1/ablation/{job_id}/sankey
Get Sankey diagram data for a completed ablation job. Returns d3-sankey compatible format. Only available when job status is complete.
Response (200):
{
"data": {
"nodes": [
{"id": "phrase-0", "name": "Research the latest", "category": "phrase"},
{"id": "reasoning-0", "name": "information retrieval", "category": "reasoning"},
{"id": "tool-web_search", "name": "web_search", "category": "tool"}
],
"links": [
{"source": "phrase-0", "target": "reasoning-0", "value": 0.45},
{"source": "reasoning-0", "target": "tool-web_search", "value": 0.45}
]
}
}
Error (409): Returned if the job is not yet complete.
GET /api/v1/ablation/{job_id}/shap
Get SHAP feature importance values from the surrogate model. Only available when job status is complete.
Response (200):
{
"data": {
"features": [
{"name": "prompt_length", "importance": 0.32},
{"name": "has_question_mark", "importance": 0.18}
],
"surrogate_f1": 0.85,
"tool_predicted": "web_search",
"tool_probabilities": {
"web_search": 0.78,
"calculator": 0.15,
"read_file": 0.07
},
"is_mock": true
}
}
Rules (Constitutional)
GET /api/v1/rules
List all loaded constitutional rules.
Response (200):
{
"data": [
{
"id": "no-bulk-delete-001",
"name": "Bulk delete prevention",
"description": "Flag tool invocations that perform destructive file-system operations",
"version": "1.0",
"target_span_type": "tool",
"condition_field": "tool_name",
"condition_operator": "in",
"thresholds": {
"amber": {
"value": ["delete", "rm", "rmdir", "remove", "unlink"],
"message": "Tool invocation involves a destructive operation"
},
"red": {
"value": ["delete", "rm", "rmdir", "remove", "unlink"],
"message": "VIOLATION: Destructive file-system operation detected"
}
},
"enabled": true,
"policy_group": "safety"
}
],
"total": 6
}
GET /api/v1/rules/{rule_id}
Get a single rule by ID.
Response (200):
{
"data": {
"id": "no-bulk-delete-001",
"name": "Bulk delete prevention",
"description": "...",
"version": "1.0",
"target_span_type": "tool",
"condition_field": "tool_name",
"condition_operator": "in",
"thresholds": {...},
"enabled": true,
"policy_group": "safety"
}
}
POST /api/v1/rules/validate
Parse and validate YAML rule content without loading it into the running system.
Request body:
{
"yaml_content": "rule:\n id: test-rule\n name: Test\n ..."
}
Response (200):
{
"valid": true,
"rule": {
"id": "test-rule",
"name": "Test",
...
},
"error": null
}
If invalid:
{
"valid": false,
"rule": null,
"error": "Missing required field: thresholds"
}
POST /api/v1/rules
Create a new constitutional rule from YAML content.
Request body:
{
"yaml_content": "rule:\n id: my-rule-001\n name: My Rule\n ..."
}
Response (201):
{
"data": {
"id": "my-rule-001",
"name": "My Rule",
"policy_group": "safety",
"enabled": true,
"conditions": [...]
}
}
Errors: 422 if YAML is invalid, 409 if rule ID already exists.
PATCH /api/v1/rules/{rule_id}
Update mutable fields of a loaded rule (enabled, name, policy_group).
Request body (all optional):
{
"enabled": false,
"name": "Updated Name",
"policy_group": "operational"
}
Response (200): Updated rule object in { "data": { ... } } wrapper.
Errors: 404 if rule not found.
DELETE /api/v1/rules/{rule_id}
Remove a rule from the loaded rules list.
Response: 204 No Content
Errors: 404 if rule not found.
POST /api/v1/rules/reload
Reload all rules from the configured rules directory (AUDITTRAIL_RULES_DIR).
Response (200):
{
"reloaded": 6,
"rule_ids": ["no-bulk-delete-001", "cost-guard-001", "latency-slo-001", ...]
}
Evaluations (Constitutional)
GET /api/v1/evaluations
List constitutional evaluation results with filtering and offset-based pagination.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
trace_id | string | -- | Filter by trace |
rule_id | string | -- | Filter by rule |
severity | string | -- | Filter by severity (info, amber, red) |
passed | boolean | -- | Filter by pass/fail |
limit | integer | 50 | Page size (1-500) |
offset | integer | 0 | Offset for pagination |
Response (200):
{
"data": [
{
"id": "eval-uuid",
"span_id": "span-uuid",
"trace_id": "trace-uuid",
"rule_id": "no-bulk-delete-001",
"rule_name": "Bulk delete prevention",
"rule_version": "1.0",
"severity": "amber",
"passed": true,
"score": 0.8,
"threshold_amber": 0.8,
"threshold_red": 1.0,
"actual_value": "delete_file",
"explanation": "Tool name matches destructive operation pattern",
"evaluated_at": "2026-03-30T10:00:02Z",
"reviewed": false
}
],
"total": 42
}
GET /api/v1/evaluations/summary
Get compliance summary statistics. Optionally scoped to a single trace.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
trace_id | string | -- | Scope summary to a single trace |
Response (200):
{
"total": 120,
"passed": 110,
"amber": 8,
"red": 2,
"compliance_percentage": 91.67,
"by_rule": [
{
"rule_id": "no-bulk-delete-001",
"rule_name": "Bulk delete prevention",
"total": 15,
"amber": 3,
"red": 1
}
]
}
PATCH /api/v1/evaluations/{eval_id}
Mark a constitutional evaluation as reviewed. Appends a review marker to the explanation field.
Request body:
{
"reviewed": true,
"notes": "Reviewed by compliance team, false positive"
}
Response (200):
{
"id": "eval-uuid",
"reviewed": true,
"notes": "Reviewed by compliance team, false positive"
}
Analytics
All analytics endpoints cache results for 60 seconds. Repeated requests within the cache window are served from memory.
GET /api/v1/analytics/overview
Dashboard metric cards with high-level statistics.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
created_after | datetime | -- | ISO 8601 lower bound |
created_before | datetime | -- | ISO 8601 upper bound |
Response (200):
{
"total_runs": 150,
"error_rate": 4.67,
"avg_latency_ms": 2340.5,
"token_usage": 45000,
"compliance_score": 95.0
}
GET /api/v1/analytics/cost
Cost breakdown grouped by agent, model, or tool.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
group_by | string | agent | agent, model, or tool |
created_after | datetime | -- | ISO 8601 lower bound |
created_before | datetime | -- | ISO 8601 upper bound |
Response (200):
{
"data": [
{
"name": "researcher",
"total_cost": 1.245,
"call_count": 45,
"avg_cost": 0.027667
}
]
}
GET /api/v1/analytics/latency
Latency percentiles (p50, p95, p99) grouped by day.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
created_after | datetime | -- | ISO 8601 lower bound |
created_before | datetime | -- | ISO 8601 upper bound |
Response (200):
{
"timestamps": ["2026-03-28", "2026-03-29", "2026-03-30"],
"p50": [1200.0, 1350.5, 1100.0],
"p95": [3200.0, 3500.0, 2900.0],
"p99": [5100.0, 5800.0, 4200.0]
}
GET /api/v1/analytics/tools
Tool usage statistics including call counts, success/failure, and average duration.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
created_after | datetime | -- | ISO 8601 lower bound |
created_before | datetime | -- | ISO 8601 upper bound |
Response (200):
{
"data": [
{
"tool_name": "web_search",
"call_count": 85,
"success_count": 82,
"failure_count": 3,
"avg_duration_ms": 450.25
}
]
}
GET /api/v1/analytics/cost-timeseries
Return daily cost totals for the cost-over-time line chart.
Query params: created_after (ISO datetime, optional), created_before (ISO datetime, optional)
Response (200):
{
"data": [
{ "date": "Mar 28", "value": 0.0342 },
{ "date": "Mar 29", "value": 0.0518 }
]
}
GET /api/v1/analytics/tools-timeseries
Return daily tool call counts for the tool-calls-over-time line chart.
Query params: created_after (ISO datetime, optional), created_before (ISO datetime, optional)
Response (200):
{
"data": [
{ "date": "Mar 28", "value": 12 },
{ "date": "Mar 29", "value": 8 }
]
}
GET /api/v1/analytics/slowest-spans
Return spans with the highest latency, grouped by name with p95/p99 percentiles.
Query params: limit (int, 1-20, default 5)
Response (200):
{
"data": [
{ "name": "web_search", "p95": 1200.5, "p99": 1850.2, "count": 45 },
{ "name": "calculator", "p95": 80.1, "p99": 120.3, "count": 22 }
]
}
Reports
POST /api/v1/reports/generate
Generate a PDF audit report for a specified date range. Runs synchronously in v1.0.
Request body:
{
"period_start": "2026-03-01",
"period_end": "2026-03-30",
"include_violations": true,
"include_analytics": true
}
| Field | Type | Default | Description |
|---|---|---|---|
period_start | date | required | Report period start (YYYY-MM-DD) |
period_end | date | required | Report period end (YYYY-MM-DD) |
include_violations | boolean | true | Include constitutional violation details |
include_analytics | boolean | true | Include analytics charts and tables |
Response (201):
{
"report_id": "report-uuid",
"status": "completed"
}
GET /api/v1/reports
List generated reports, newest first.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Page size (1-200) |
offset | integer | 0 | Offset for pagination |
Response (200):
{
"data": [
{
"id": "report-uuid",
"status": "completed",
"period_start": "2026-03-01",
"period_end": "2026-03-30",
"include_violations": true,
"include_analytics": true,
"file_path": "data/reports/audit_report_uuid.pdf",
"file_size_bytes": 52400,
"generation_time_ms": 1200,
"summary": {"pdf_size_bytes": 52400, "generation_time_ms": 1200},
"created_at": "2026-03-30T12:00:00Z",
"completed_at": "2026-03-30T12:00:01Z",
"error_message": null
}
],
"total": 5
}
GET /api/v1/reports/{report_id}
Get the status and metadata of a specific report.
Response (200): Same shape as a single item from the list endpoint.
GET /api/v1/reports/{report_id}/download
Download the generated PDF. Only available when report status is completed.
Response: application/pdf file stream.
Error (409): Returned if the report is not yet ready.
Settings
GET /api/v1/settings
Get all settings as a flat key-value dictionary.
Response (200):
{
"data": {
"pii_redaction_enabled": true,
"retention_days": 90,
"theme": "dark"
}
}
GET /api/v1/settings/metrics
Return performance metrics for the settings dashboard (middleware overhead, API latency, WebSocket latency, rule counts).
Response (200):
{
"middleware_overhead_ms": 2.1,
"api_response_p95_ms": 52.3,
"websocket_latency_ms": 10.5,
"loaded_rules": 6,
"active_rules": 5,
"disabled_rules": 1
}
PATCH /api/v1/settings
Update settings. Body is a dictionary of key-value pairs to upsert.
Request body:
{
"retention_days": 30,
"theme": "light"
}
Response (200): Returns the full updated settings dictionary (same shape as GET).
Instance
GET /api/v1/instance
Get instance metadata: version, connected agents, rule count, and uptime.
Response (200):
{
"version": "1.0.0",
"connected_agents": 3,
"rules_count": 6,
"uptime_seconds": 86400.0
}
Agents
GET /api/v1/agents
List registered agents with status and trace count.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Page size (1-200) |
offset | integer | 0 | Offset for pagination |
Response (200):
{
"data": [
{
"id": "agent-uuid",
"name": "researcher",
"framework": "langgraph",
"description": "Researches topics via web",
"first_seen_at": "2026-03-01T00:00:00Z",
"last_seen_at": "2026-03-30T10:00:00Z",
"trace_count": 45,
"status": "online",
"registered_via": "auto",
"metadata_": null
}
],
"total": 3
}
POST /api/v1/agents
Manually register an agent. Agent names must be unique.
Request body:
{
"name": "my-agent",
"framework": "langgraph",
"description": "Custom agent for data processing"
}
Response (201): Same shape as a single item from the list endpoint.
Error (409): Returned if the agent name is already registered.
GET /api/v1/agents/{agent_id}
Get a single agent by ID.
Response (200): Same shape as a single item from the list endpoint.
DELETE /api/v1/agents/{agent_id}
Remove an agent registration.
Response: 204 No Content
API Keys
GET /api/v1/api-keys
List all API keys with secrets masked (only prefix shown).
Response (200):
{
"data": [
{
"id": "key-uuid",
"name": "production-ingest",
"public_key": "pk-at-abc123...",
"secret_key_prefix": "sk-at-def456",
"scope": "ingest",
"created_at": "2026-03-01T00:00:00Z",
"last_used_at": "2026-03-30T10:00:00Z",
"revoked_at": null
}
],
"total": 2
}
POST /api/v1/api-keys
Generate a new API key pair. The plaintext secret key is returned only once in this response.
Request body:
{
"name": "production-ingest",
"scope": "ingest"
}
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Human-readable key name |
scope | string | ingest | Permission scope |
Response (201):
{
"id": "key-uuid",
"name": "production-ingest",
"public_key": "pk-at-abc123def456...",
"secret_key": "sk-at-789xyz...",
"scope": "ingest",
"created_at": "2026-03-30T12:00:00Z"
}
Save the secret_key immediately -- it is not retrievable after this response.
DELETE /api/v1/api-keys/{key_id}
Revoke an API key. The key record is not deleted -- it remains visible with a revoked_at timestamp for audit purposes.
Response: 204 No Content
Error (409): Returned if the key is already revoked.
WebSocket
WS /ws/traces/{trace_id}
Subscribe to real-time events for a specific trace. On connect, the client receives a replay of the last 100 events from the ring buffer.
WS /ws/live
Subscribe to ALL trace events across all traces. Used by the dashboard overview for a live activity stream.
Client Messages
Send JSON messages to the WebSocket:
{"type": "ping"}
{"type": "subscribe", "trace_id": "trace-uuid"}
{"type": "unsubscribe", "trace_id": "trace-uuid"}
Server Events
Events are JSON objects with a type field:
Span event:
{
"type": "span_started",
"trace_id": "trace-uuid",
"span_id": "span-uuid",
"parent_span_id": "parent-uuid",
"name": "gpt-4o-step-0",
"span_type": "llm",
"timestamp": "2026-03-30T10:00:00Z",
"status": "running",
"duration_ms": null,
"error_message": null,
"error_type": null,
"model": "gpt-4o",
"tokens_in": null,
"tokens_out": null,
"estimated_cost": null
}
Constitutional violation event:
{
"type": "constitutional_violation",
"trace_id": "trace-uuid",
"span_id": "span-uuid",
"rule_id": "no-bulk-delete-001",
"rule_name": "Bulk delete prevention",
"severity": "amber",
"passed": true,
"explanation": "Tool name matches destructive operation pattern",
"actual_value": "delete_file",
"evaluated_at": "2026-03-30T10:00:02Z"
}
Error Responses
All error responses follow a consistent format:
{
"detail": "Resource not found",
"code": "not_found"
}
| Status | Code | Description |
|---|---|---|
| 400 | varies | Bad request (validation error, invalid cursor, etc.) |
| 401 | -- | Unauthorized (missing or invalid session) |
| 403 | -- | Forbidden (deactivated account) |
| 404 | not_found | Resource not found |
| 409 | -- | Conflict (duplicate email, already revoked, not ready) |
| 422 | -- | Unprocessable entity (cannot extract prompt for ablation) |
| 500 | internal_error | Internal server error |