Skip to content

API reference (BFF /api/*)

Generated by docs/script/gen_api_reference.py from the committed openapi.json — do not hand-edit. Drift-gated (--check) in CI; the schema itself is gated by generate_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 KneoHumanTaskExpiredError409 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):

{
  "summary": {}
}

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):

{
  "idempotency_key": "string",
  "run_id": "string",
  "status": "string"
}

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):

{
  "rows": [
    {
      "enabled": false,
      "environment": "string"
    }
  ]
}

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):

{
  "diff": [],
  "environment": "string",
  "policy": {},
  "previous_policy": {}
}

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):

{
  "diff": [],
  "environment": "string",
  "policy": {},
  "previous_policy": {}
}

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):

{
  "affected_count": 0,
  "affected_run_ids": [],
  "diff": []
}

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):

{
  "available": false,
  "runs": [],
  "session_id": "string",
  "total": 0,
  "truncated": false
}

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):

{
  "available": false,
  "current": "string",
  "edges": [],
  "nodes": []
}

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):

{
  "checkpoint_count": 0,
  "run_id": "string",
  "status": "string",
  "timeline": []
}

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):

{
  "available": false,
  "runs": [],
  "session_id": "string",
  "total": 0,
  "truncated": false
}

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):

{
  "capabilities": {},
  "is_default": false,
  "version": 0
}

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):

{
  "capabilities": {},
  "is_default": false,
  "version": 0
}

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):

{
  "environments": {},
  "is_default": false,
  "version": 0
}

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):

{
  "environments": {},
  "is_default": false,
  "version": 0
}

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):

{
  "is_default": false,
  "preferences": {},
  "version": 0
}

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):

{
  "is_default": false,
  "preferences": {},
  "version": 0
}

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):

{
  "blended_per_1k": 0.0,
  "currency": "USD",
  "is_default": false,
  "version": 0
}

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):

{
  "blended_per_1k": 0.0,
  "currency": "USD",
  "is_default": false,
  "version": 0
}

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