API reference (BFF /api/*)¶
Generated by
docs/script/gen_api_reference.pyfrom the committedopenapi.json— do not hand-edit. Drift-gated (--check) in CI; the schema itself is gated bygenerate_reference_docs.py, so parameters + responses here can't go stale vs. the running BFF.
The Kneo Agent Dashboard BFF contract every /api/* call speaks (distinct from the kneo-serv /v1 contract, which kneo-client owns). Response bodies are the BFF view-models — shapes + semantics are in view-models; the error envelope + code table is api_contract. All errors share the {code, message, request_id} envelope (§12).
Example bodies are illustrative shapes
The Example blocks show a response's shape (field names + value types), generated from the schema — not real data. Authorize + auth headers are omitted; see security hardening.
annotations¶
DELETE /api/annotations/{annotation_id}¶
Delete Annotation
Delete an annotation. version (query) guards against clobbering a concurrent
edit — a stale value is 409, a missing row 404.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
annotation_id |
path | string | ✓ | — |
version |
query | integer | ✓ | — |
Responses:
| Code | Description |
|---|---|
204 |
Successful Response |
422 |
Validation Error |
PUT /api/annotations/{annotation_id}¶
Update Annotation
Edit an annotation. body.version must match the current row or the write is
rejected 409 dashboard_state_conflict (optimistic concurrency); a missing row is
404.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
annotation_id |
path | string | ✓ | — |
Request body: AnnotationUpdate (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"body": "string",
"created_at": "string",
"created_by": "string",
"id": "string",
"run_id": "string",
"tags": [],
"updated_at": "string",
"version": 0
}
GET /api/runs/{run_id}/annotations¶
List Annotations
All annotations on a run, oldest first. Read is open to any resolved operator; oidc
requires a session (no session → 401).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
[
{
"body": "string",
"created_at": "string",
"created_by": "string",
"id": "string",
"run_id": "string",
"tags": [],
"updated_at": "string",
"version": 0
}
]
POST /api/runs/{run_id}/annotations¶
Create Annotation
Attach a note + tags to a run — server-side authz (ANNOTATE), stamped with the
operator identity (created_by).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
Request body: AnnotationCreate (application/json)
Responses:
| Code | Description |
|---|---|
201 |
Successful Response |
422 |
Validation Error |
Example 201 (shape):
{
"body": "string",
"created_at": "string",
"created_by": "string",
"id": "string",
"run_id": "string",
"tags": [],
"updated_at": "string",
"version": 0
}
audit¶
GET /api/audit¶
List Events
Paged, filterable audit events (platform.audit.list).
Defaults to all events, newest first (the server sorts created_at desc). Optional
event_type (e.g. run.created) and run_id narrow the timeline; empty strings
("" → None) mean "no filter". Offset pagination to the window cap, like Runs.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
event_type |
query | string | – | — |
run_id |
query | string | – | — |
limit |
query | integer | – | — |
offset |
query | integer | – | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"count": 0,
"limit": 0,
"offset": 0,
"rows": [
{
"actor": "string",
"continuation_id": "string",
"created_at": "",
"event_type": "string",
"id": "string",
"metadata": {},
"run_id": "string"
}
],
"total": 0,
"window": 0
}
audit-log¶
GET /api/audit-log¶
Read Audit Log
Recent dashboard-local audit entries, newest first. Requires the AUDIT_READ capability (admin by default).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | – | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"entries": [
{
"action": "string",
"created_at": "string",
"environment": "string",
"id": "string",
"operator": "string",
"outcome": "string",
"target": "string"
}
]
}
auth¶
GET /api/callback¶
Callback
OIDC callback — Authlib validates state/nonce + the ID token (JWKS), we map the
claims → role (default-deny), mint a server-side session, set the signed
cookie, and redirect the SPA. An identity with no mapped role gets 403.
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
GET /api/login¶
Login
Begin OIDC login — redirect to the provider (Authorization Code + PKCE; state + nonce are stashed in the transient Starlette session). oidc mode only.
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
POST /api/logout¶
Logout
Revoke the current session (delete the server-side row) and clear the cookie. Idempotent — safe with no/expired session.
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
GET /api/me¶
Me
The current operator + the BFF's effective authorization — the single source of
truth for SPA nav/action gating (not a client-side role map, which can drift from the
stored Access map). 401 if no valid session (oidc mode); always resolves in static mode.
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
{
"capabilities": [
"string"
],
"environments": [
"string"
],
"identity": "string",
"role": "string"
}
credentials¶
GET /api/credentials¶
List Credentials
The credential-reference inventory (platform.credentials.list).
Sections (providers / extra / service_tokens) of references with a
present/missing status + a presence rollup. No secrets: the view carries no
value, only presence + reserved metadata (expires_at/last_checked, null for
env-var references today).
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
{
"missing_count": 0,
"present_count": 0,
"sections": [
{
"entries": [
{
"env_var": "\u2026",
"expires_at": "\u2026",
"last_checked": "\u2026",
"name": "\u2026",
"present": "\u2026",
"status": "\u2026"
}
],
"section": "string"
}
]
}
health¶
GET /api/health¶
Health
Aggregate livez/readyz/healthz of the target platform.
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
{
"probes": {
"healthz": false,
"livez": false,
"readyz": false
},
"service": "string",
"subsystems": [
{
"checked_age": "string",
"degraded": false,
"detail": "string",
"name": "string",
"ok": false
}
],
"version": "string"
}
human-tasks¶
GET /api/human-tasks¶
List Tasks
List human tasks (platform.human_tasks.list), deadline-first.
Defaults to all actionable tasks — status="" (→ None) returns both
pending and escalated (the serv status filter accepts only those two open
states; resolved tasks aren't listed), so escalated (timed-out, manually
resumable) tasks stay visible in the queue instead of being dropped by a
pending-only filter (pre-rc1 audit). Pass status=pending/escalated to
narrow. Sorted by expires_at asc so the most urgent (and overdue/escalated) is
on top; the row projection derives the escalated label and degrades gracefully
when a field is absent.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
status |
query | string | – | — |
limit |
query | integer | – | — |
offset |
query | integer | – | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"count": 0,
"limit": 0,
"offset": 0,
"rows": [
{
"continuation_id": "string",
"created_age": "string",
"deadline_age": "string",
"near_deadline": false,
"request_id": "string",
"request_summary": "string",
"run_id": "string",
"status": "string",
"workflow_kind": "string",
"workflow_name": "string"
}
],
"total": 0,
"window": 0
}
GET /api/human-tasks/{continuation_id}¶
Get Task
Human-task detail + message thread (platform.human_tasks.get).
Backs the task-detail view: the request prompt, any options (for a selected
decision), and the redacted messages[] thread — so a reviewer can resume with a
real decision. messages is None/absent on older servers → empty.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
continuation_id |
path | string | ✓ | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"continuation_id": "string",
"messages": [],
"options": [],
"request_id": "string",
"request_summary": "\u2014",
"run_id": "string",
"status": "",
"workflow_name": ""
}
POST /api/human-tasks/{continuation_id}/resume¶
Resume Task
Resume a paused run with a decision (platform.human_tasks.resume).
The SPA sends the decision (+ optional content/selected_option) and echoes
the task's request_id; the BFF mints one if absent. The Idempotency-Key
(per attempt) makes a retry safe. Resuming past the deadline raises
KneoHumanTaskExpiredError → 409 human_task_expired via the envelope (§12);
the SPA surfaces "task expired" and refreshes the queue.
On request_id: the platform's HumanResumeRequest.request_id is a required
field, but the queue row's request_id is best-effort (str | None) — so when the
row carried none, the BFF mints a dash- correlation tag to satisfy the contract.
The continuation_id path param is the real task selector; request_id is a
correlation/audit tag, so a minted value is a safe fallback (deep-audit CONTRACT-3:
a local 400 was rejected — it would wrongly block resuming a legitimately
request_id-less task).
This privileged platform mutation attempts a best-effort audit append (persistence not guaranteed) of the operator + outcome.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
continuation_id |
path | string | ✓ | — |
Idempotency-Key |
header | string/null | – | — |
Request body: HumanResumeInput (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"continuation_id": "string",
"idempotency_key": "string",
"run_id": "string",
"status": "string"
}
launch¶
POST /api/launch/deploy¶
Deploy
Deploy — validate + compile(strict) + policy-report against the target env,
combined into one "ready to run?" verdict. Still non-mutating: it verifies, it
does not start a run. An invalid spec comes back as valid/compiled/policy_ok
false with blocking[] reasons (not an error); a platform failure surfaces via
the §12 envelope.
Request body: LaunchSpecInput (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"blocking": [],
"compiled": false,
"diagnostics": [],
"policy_ok": false,
"ready": false,
"valid": false
}
GET /api/launch/history¶
History
Recent operational launches (MRU) — the dashboard-local launch history, newest
first, for one-click re-launch. Dashboard-local state (no platform call); oidc requires
a session (no session → 401).
Env-scoped (deep-audit / external review): rows are filtered to the environments the
operator's role may use (:func:access.allowed_envs), so an env-restricted role can't
enumerate launch provenance (spec_path/digest/run_id/created_by) for environments it is
denied elsewhere. An unrestricted role (admin / no grants) sees all rows.
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
{
"entries": [
{
"created_at": "string",
"created_by": "",
"digest": "string",
"environment": "string",
"id": "string",
"label": "string",
"mode": "string",
"run_id": "string",
"spec_path": "string"
}
]
}
POST /api/launch/load¶
Load
Load — a static, env-independent preview of what the agent is
(agent.specs.explain). No mutation; the preview is env-independent, but the effective
env is still forwarded to explain, so it is reconciled with the active env and
checked against the operator's environment grants (per-role, resolved through the
Access map) like Deploy/Run (sibling parity).
Request body: LaunchSpecInput (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
POST /api/launch/run¶
Run
Run — start an operational run of the spec (platform.runs.create).
The mutating phase. The SPA gates it behind a typed-confirm (re-type the env)
and only enables it after a green Deploy; RBAC / the platform scope is the real
authority. Idempotency: the SPA mints one Idempotency-Key per launch attempt
and reuses it across retries (LaunchPage runKey; cleared only on success or an
explicit re-edit), so a resend after an ambiguous failure carries the same key and
platform.runs.create won't double-launch — the guarantee holds across the retry, not
just within one attempt. Validation/compile/policy failures should have blocked Run
at Deploy; if the platform still rejects create, it surfaces via the §12
envelope. The SPA routes to the new run's detail. This privileged platform mutation
attempts a best-effort audit append (persistence not guaranteed) of the operator +
outcome — success carries the new run_id; a failed launch records an error outcome.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | string/null | – | — |
Request body: LaunchRunInput (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
ops¶
GET /api/healthz¶
Healthz
Full BFF status — always 200; degradation is reported in the body (never a
503). Reports liveness, readiness (state store + recovery mode), and dependency
status (OIDC).
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
GET /api/livez¶
Livez
Liveness — the process is up. No dependency checks (safe as a restart probe).
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
GET /api/readyz¶
Readyz
Readiness — the BFF can serve: the dashboard state store answers a trivial
query and the instance is not in recovery mode. 200 when ready, 503
otherwise (traffic/rollout gate). Recovery (ADR-012 §7) keeps this red so the
proxy/orchestrator de-routes the instance until an operator completes reconciliation.
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
overview¶
GET /api/overview¶
Get Overview
BFF-derived Overview tiles (status counts, pending HITL, readiness) + a Grafana
deep-link. Counts are authoritative totals from filtered runs.list calls, run
concurrently; readiness comes from health.readyz.
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
{
"error_rate": 0.0,
"grafana_url": "string",
"ready": false,
"tiles": [
{
"key": "string",
"label": "string",
"link": "string",
"tone": "neutral",
"value": 0
}
]
}
policies¶
GET /api/policies¶
List Policies
List environment policies (policies.environment_list), by environment.
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
GET /api/policies/{environment}¶
Get Policy
One environment's policy + the before/after vs its previous_policy.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
environment |
path | string | ✓ | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
PUT /api/policies/{environment}¶
Put Policy
Replace an environment's policy (environment_put). Server-side authz
(POLICY_WRITE) + the platform 403 as a second layer; the response's
previous_policy drives the before/after the SPA shows. This privileged platform
mutation attempts a best-effort audit append (persistence not guaranteed).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
environment |
path | string | ✓ | — |
Request body: PolicyUpdateInput (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
POST /api/policies/{environment}/preview¶
Preview Policy
Dry-run a policy change before the mutating PUT — the field diff vs the active
policy + the runs it would affect. Persists nothing (environment_preview). Gated by
POLICY_WRITE (admin) like the PUT it previews — it's the policy-authoring flow, not
a general read (Phase-5 audit: was reachable by any operator).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
environment |
path | string | ✓ | — |
Request body: PolicyUpdateInput (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
runs¶
GET /api/runs¶
List Runs
List runs (platform.runs.list) with the server-side filter facets.
status is the base facet; has_error/workflow_kind/created_after|before
need a live kneo_serv >= 1.1.0, and q (content search over output) needs
>= 1.2.0. An older server rejects an unknown facet with 422
unknown_query_parameters — so rather than error the page, the BFF drops the
below-floor facets tier-by-tier and retries, reporting each dropped one in
unsupported_filters so the SPA disables that chip (audit-0002 M-9). No env
facet (the global switcher scopes the session).
stuck is a BFF heuristic with no server facet. When stuck_only is set the BFF
scans up to _STUCK_SCAN_MAX_PAGES × _STUCK_SCAN_PAGE runs and returns every
stuck one found (not just page 1) — so the honest answer to "what's stuck" isn't
limited to the first page (audit-0002 M-3 real fix).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
status |
query | string/null | – | — |
has_error |
query | boolean/null | – | — |
workflow_kind |
query | string/null | – | — |
created_after |
query | string/null | – | — |
created_before |
query | string/null | – | — |
q |
query | string/null | – | — |
stuck_only |
query | boolean | – | — |
limit |
query | integer | – | — |
offset |
query | integer | – | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"count": 0,
"limit": 0,
"offset": 0,
"rows": [
{
"agent_name": "string",
"blocked": false,
"current_node_id": "string",
"current_step_index": 0,
"has_error": false,
"run_id": "string",
"status": "string",
"stuck": false,
"stuck_reason": "string",
"updated_age": "string",
"usage": {
"cost_approximate": false,
"cost_usd": 0.0,
"currency": "USD",
"input_tokens": 0,
"output_tokens": 0,
"total_tokens": 0
},
"workflow_kind": "string",
"workflow_name": "string"
}
],
"total": 0,
"unsupported_filters": [],
"window": 0
}
GET /api/runs/error-summary¶
Get Error Summary
Cluster recent failed runs by workflow (cross-run triage: what's failing?).
BFF-aggregates one bounded window of runs.list(status=failed) into per-workflow
groups (count + sample run ids), most-failing first. Declared before /{run_id} so
the literal path isn't captured as a run id.
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
{
"groups": [
{
"count": 0,
"sample_run_ids": [],
"workflow_kind": "string",
"workflow_name": "string"
}
],
"scanned": 0,
"total_failed": 0
}
GET /api/runs/{run_id}¶
Get Run
Run detail view-model: status + trace + checkpoints, composed.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"agent_name": "string",
"checkpoints": [
{
"age": "string",
"node": "string",
"sequence": 0,
"type": "string"
}
],
"continuation_id": "string",
"current_node_id": "string",
"current_step_index": 0,
"error": "string",
"input_redacted": false,
"is_terminal": false,
"output_text": "string",
"path": [
"string"
],
"run_id": "string",
"session_id": "string",
"status": "string",
"trace": [
{
"duration_ms": 0,
"kind": "string",
"node": "string",
"ts": "string",
"type": "string"
}
],
"trace_complete": false,
"trace_dropped": false,
"trace_event_count": 0,
"usage": {
"cost_approximate": false,
"cost_usd": 0.0,
"currency": "USD",
"input_tokens": 0,
"output_tokens": 0,
"total_tokens": 0
},
"workflow_kind": "string",
"workflow_name": "string"
}
POST /api/runs/{run_id}/cancel¶
Cancel Run
Stop — cooperatively cancel a run (platform.runs.cancel).
Visibility is UX-gated in the SPA (only when the run is non-terminal); the
platform is the authority. Cancelling an already-terminal run is a 200 no-op
that returns the unchanged terminal state (check status to tell whether it did
anything), not a 409; 503/429 surface via the standard envelope (§12). The
idempotency key makes a retry safe. (A 409 run_state_conflict is real but fires on
other lifecycle fences — e.g. continuing a non-blocked run — not on this route;
kneo-client 1.2.0.) This privileged platform mutation attempts a
best-effort audit append (persistence not guaranteed) of the operator + outcome.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
Idempotency-Key |
header | string/null | – | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"action": "string",
"continuation_id": "string",
"idempotency_key": "string",
"new_run_id": "string",
"run_id": "string",
"status": "string"
}
GET /api/runs/{run_id}/chain¶
Get Run Chain
Continuation chain — the runs sharing this run's session_id (walk
paused→resumed across run boundaries).
Fetches the run to read its session_id, then lists that session oldest-first.
Degrades to available=False when the run has no session_id, or when the
server can't filter by it (session_id needs kneo_serv >= 1.1.0 → 422
unknown_query_parameters). Any other 400/422 (a real bad-request, a genuine
validation error, contract drift) is re-raised to the §12 envelope rather than
masked as an empty chain — mirrors _list_page's code check (pre-rc1 audit).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
GET /api/runs/{run_id}/checkpoints/diff¶
Get Checkpoint Diff
Time-travel diff between two checkpoint sequences (runs.checkpoints_diff).
The SPA passes two sequences picked from the run-detail checkpoint list;
returns added/changed/removed state with truncated honored. A 404 (older
server without the diff endpoint) propagates via the §12 envelope so the SPA can
note it's unavailable.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
from_sequence |
query | integer/null | – | — |
to_sequence |
query | integer/null | – | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"added": [],
"change_count": 0,
"changed": [],
"from_sequence": 0,
"removed": [],
"to_sequence": 0,
"truncated": false
}
POST /api/runs/{run_id}/continue¶
Continue Run
Resume — continue a paused/blocked run (platform.runs.continue_).
The SPA shows this only when the run is blocked. This resumes the run at the
platform level; a HITL task that needs an explicit decision is resumed from
the Human-tasks queue (human_tasks.resume). Continuing a non-blocked/terminal
run surfaces as a 409 run_state_conflict via the envelope (§12); human_task_expired
is the resume path's code, not this one. This privileged platform mutation attempts
a best-effort audit append (persistence not guaranteed) of the operator + outcome.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
Idempotency-Key |
header | string/null | – | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"action": "string",
"continuation_id": "string",
"idempotency_key": "string",
"new_run_id": "string",
"run_id": "string",
"status": "string"
}
GET /api/runs/{run_id}/graph¶
Get Run Graph
Run workflow DAG (platform.runs.graph, kneo_serv >= 1.1.0).
Returns nodes+edges with visited/current highlighting. A 404 (older server /
no graph for this run) degrades to available=False so the SPA falls back to
the path breadcrumb rather than erroring the tab.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
GET /api/runs/{run_id}/policy-report¶
Get Run Policy Report
Per-run policy report (platform.runs.policy_report) — the compiled spec's
policy outcomes (human-review requirements, tool permissions, guardrails,
diagnostics) for this run. A 404 (older server / no report) degrades to
available=False so the tab shows "no report" rather than erroring.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"available": false,
"diagnostics": [],
"human_review_missing": false,
"human_review_required": false,
"summary": {},
"valid": true,
"version": ""
}
GET /api/runs/{run_id}/recovery¶
Get Run Recovery
Recovery panel (platform.runs.recovery) — where a failed/interrupted run
stopped and whether it can be resumed (a continuation_id/checkpoints mean yes).
Pairs with Resume (runs.continue_) when recoverable.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"checkpoint_count": 0,
"continuation_id": "string",
"current_node_id": "string",
"current_step_index": 0,
"error": "string",
"recoverable": false,
"run_id": "string",
"status": "string",
"visited_nodes": []
}
GET /api/runs/{run_id}/replay¶
Get Run Replay
Replay reconstruction (platform.runs.replay) — the run's timeline for
step-through. A 404/platform error surfaces via the §12 envelope.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
saved-filters¶
GET /api/saved-filters¶
List Saved Filters
All saved filters, by name. Read is open to any resolved operator; oidc requires a
session (no session → 401).
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
[
{
"created_at": "string",
"created_by": "string",
"filters": {},
"id": "string",
"name": "string",
"updated_at": "string",
"version": 0
}
]
POST /api/saved-filters¶
Create Saved Filter
Save the current Runs filter under a name — server-side authz (FILTER_WRITE),
stamped with the operator identity (created_by).
Request body: SavedFilterCreate (application/json)
Responses:
| Code | Description |
|---|---|
201 |
Successful Response |
422 |
Validation Error |
Example 201 (shape):
{
"created_at": "string",
"created_by": "string",
"filters": {},
"id": "string",
"name": "string",
"updated_at": "string",
"version": 0
}
DELETE /api/saved-filters/{filter_id}¶
Delete Saved Filter
Delete a saved filter. version (query) guards a concurrent edit — a stale
value is 409, a missing row 404.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
filter_id |
path | string | ✓ | — |
version |
query | integer | ✓ | — |
Responses:
| Code | Description |
|---|---|
204 |
Successful Response |
422 |
Validation Error |
PUT /api/saved-filters/{filter_id}¶
Update Saved Filter
Rename/redefine a saved filter. body.version must match or the write is
rejected 409 dashboard_state_conflict; a missing row is 404.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
filter_id |
path | string | ✓ | — |
Request body: SavedFilterUpdate (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"created_at": "string",
"created_by": "string",
"filters": {},
"id": "string",
"name": "string",
"updated_at": "string",
"version": 0
}
sessions¶
GET /api/sessions/{session_id}¶
Get Session
The runs in a session, oldest-first (runs.list(session_id=…)).
Degrades to available=False when the server can't filter by session_id
(needs kneo_serv >= 1.1.0 → 422 unknown_query_parameters); any other 400/422
is re-raised to the §12 envelope rather than masked as an empty timeline
(mirrors the Chain tab, pre-rc1 audit).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
session_id |
path | string | ✓ | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
settings¶
GET /api/settings/access¶
Get Access Map
The effective role → capabilities map — the stored override, else the built-in
default (is_default=true, version=0). Read is open to any resolved operator; in
oidc mode a valid session is required (no session → 401).
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
PUT /api/settings/access¶
Put Access Map
Override the capability map. Requires SETTINGS_WRITE (admin in the built-in default map). Unknown roles/capabilities are 400; a
stale version (or a create over an existing map) is 409. The write bumps the map
version (existing sessions must re-authenticate); the write attempts a best-effort audit append (persistence not guaranteed).
Request body: AccessMapUpdate (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
GET /api/settings/access/environments¶
Get Env Grants
The role → allowed-environments grants. Unconfigured → is_default=true,
version=0 (unrestricted: every role may use every env; ADR-009 §2).
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
PUT /api/settings/access/environments¶
Put Env Grants
Set the role → allowed-environments grants. Requires SETTINGS_WRITE (admin in the built-in default map). Unknown roles are 400;
a stale version (or create-over-existing) is 409; attempts a best-effort audit append
(persistence not guaranteed). (Admin always retains access to every env, so this can't lock admins out.)
Request body: EnvGrantsUpdate (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
GET /api/settings/connections¶
List Connections
The operator's usable environment connections (env-ordered) — drives the env
switcher. Read is open to any resolved operator; oidc requires a session (no session
→ 401). Filtered by the operator's env grants (0.8.0 A8): a restricted role
sees only its allowed_envs, so the switcher can't offer an env whose use would
403 (authorize_env stays the enforcement layer — filtering is UX truthfulness,
not the security boundary). Unrestricted roles (admin · no grants · wildcard) see
every connection, including in Settings › Connections management.
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
[
{
"created_at": "string",
"created_by": "string",
"env": "string",
"label": "",
"profile": "string",
"updated_at": "string",
"version": 0
}
]
DELETE /api/settings/connections/{env}¶
Delete Connection
Remove an env connection. Requires SETTINGS_WRITE (admin in the built-in default map). version (query) guards a concurrent edit
(stale → 409, missing → 404); the write attempts a best-effort audit append (persistence not guaranteed).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
env |
path | string | ✓ | — |
version |
query | integer | ✓ | — |
Responses:
| Code | Description |
|---|---|
204 |
Successful Response |
422 |
Validation Error |
PUT /api/settings/connections/{env}¶
Upsert Connection
Create (version=0) or update an env → profile mapping. Requires SETTINGS_WRITE (admin in the built-in default map). A create over
an existing env, or a stale version, is 409; the write attempts a best-effort audit append (persistence not guaranteed).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
env |
path | string | ✓ | — |
Request body: ConnectionUpsert (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"created_at": "string",
"created_by": "string",
"env": "string",
"label": "",
"profile": "string",
"updated_at": "string",
"version": 0
}
GET /api/settings/preferences¶
Get Preferences
The current operator's stored preferences, or an empty default (is_default=true,
version=0).
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
PUT /api/settings/preferences¶
Put Preferences
Upsert the current operator's preferences (keyed by their identity). version is
0 to create and the current version to update; a stale version (or create-over-
existing) is 409.
Request body: PreferencesUpdate (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
GET /api/settings/pricing¶
Get Pricing
The blended price book — unconfigured → is_default=true, version=0,
blended_per_1k=null (pricing disabled). Read is open to any resolved operator; oidc
requires a session (no session → 401).
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
Example 200 (shape):
PUT /api/settings/pricing¶
Put Pricing
Set the blended price book. Requires SETTINGS_WRITE (admin in the built-in default map). A negative rate is 400; a stale version
(or create-over-existing) is 409. Attempts a best-effort audit append (persistence not guaranteed).
Request body: PriceBookUpdate (application/json)
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
spend¶
GET /api/spend¶
Get Spend
Blended, approximate spend for the active env over window (24h|7d|
30d). Unknown window → 400. truncated marks a window larger than the scan
budget (the total is then a lower bound).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
window |
query | string | – | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"approximate": false,
"counted": 0,
"currency": "USD",
"priced": false,
"scanned": 0,
"total": 0,
"total_cost_usd": 0.0,
"truncated": false,
"window": "string"
}
traces¶
GET /api/runs/{run_id}/trace¶
Get Trace
Paged trace events for a run (platform.runs.trace).
Returns the mandated paged envelope ({rows,count,total,limit,offset,window}
+ collector complete/dropped), not a bare array (audit-0002 L-4).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
event_type |
query | string/null | – | — |
limit |
query | integer | – | — |
offset |
query | integer | – | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Example 200 (shape):
{
"complete": false,
"count": 0,
"dropped": false,
"limit": 0,
"offset": 0,
"rows": [
{
"duration_ms": 0,
"kind": "string",
"node": "string",
"ts": "string",
"type": "string"
}
],
"total": 0,
"window": 0
}
GET /api/runs/{run_id}/trace/stream¶
Stream Trace
SSE live trace tail — platform.runs.tail_trace framed as Server-Sent Events.
Streams trace frames as events arrive, an end frame at terminal status, or a
stream_error frame on a platform failure; the SPA consumes it with EventSource.
The per-request client (get_client) stays open until the stream finishes.
SEC-1 hardening: (a) an explicit send_timeout tears the stream down if the consumer
stalls (freeing the writer + client) — not a max duration (a blocked run's tail is
intended); (b) a per-operator concurrent-stream cap via the _stream_slot
dependency — a stream over the cap is refused 503 + Retry-After before the response
starts, and the slot release rides the request exit stack (leak-free even on a
disconnect before the first frame; Phase-5 audit).
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
run_id |
path | string | ✓ | — |
event_type |
query | string/null | – | — |
Responses:
| Code | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |