Skip to content

Runtime API contracts

This page explains the runtime backend routes used by the dashboard and other clients. In the standard development environment, the base URL is http://localhost:8010.

  • OpenAPI document: GET /openapi.json
  • Swagger UI: GET /docs
  • ReDoc: GET /redoc

Authentication: When AGENTIC_API_KEY is set, protected endpoints require one of:

  • Authorization: Bearer <key> header
  • X-API-Key: <key> header

The /api/health prefix, /docs, /openapi.json, and /redoc bypass API-key authentication. This includes /api/health/ready. So does every path outside /api/ (the static UI, /metrics and /ws/*); WebSocket connections are authenticated by their own handler. If OIDC is enabled, use the OIDC settings described in Configuration.

Route map

The OpenAPI document is the machine-readable source of truth for request and response schemas. This table gives the complete route inventory.

Health and catalogs

Method Path Purpose
GET /api/health Check whether the process is running
GET /api/health/ready Check whether required runtime dependencies are ready
GET /api/agents List configured agents
GET /api/personas List available personas
GET /api/tools List tools that workflow steps may allow
GET /api/observers List available observers

Models, chat, and settings

Method Path Purpose
GET /api/models/probe Probe configured model providers
POST /api/models/lmstudio/load Ask LM Studio to load a local model
POST /api/chat Stream a model or tier-based chat response
GET, PUT /api/settings/providers Read or replace saved provider settings
POST /api/settings/providers/{provider_id}/probe Test one saved provider
GET, PUT /api/settings/tiers Read or replace tier routing settings
GET, POST /api/settings/model-packs List or create model packs
PUT /api/settings/model-packs/{pack_id} Create a new version of a model pack
POST /api/settings/model-packs/{pack_id}/duplicate Copy a model pack
POST /api/settings/model-packs/{pack_id}/validate Validate a model pack
POST /api/settings/model-packs/{pack_id}/activate Make a model pack active
DELETE /api/settings/model-packs/active Clear the active model pack
PUT /api/settings/model-packs/{pack_id}/bindings/{workflow_name} Bind a model pack to one workflow
DELETE /api/settings/model-packs/bindings/{workflow_name} Clear a workflow-specific model-pack binding
GET /api/settings/model-packs/{pack_id}/dependencies Find runs and bindings that use a model pack
POST /api/settings/model-packs/{pack_id}/archive Archive an unused model pack
GET /api/settings/model-packs/{pack_id}/export Export a model pack
POST /api/settings/model-packs/import Import a model pack

Provider and tier settings are stored in ~/.agentic/ui-settings.json. Secrets remain in environment variables; the settings API does not return secret values.

Model finder

Method Path Purpose
GET /api/model-finder/profile Inspect the detected hardware profile
GET, PUT, DELETE /api/model-finder/profile-override Read, set, or remove a hardware override
GET /api/model-finder/recommendations Rank local models for the current profile and filters

Workflows and execution

Method Path Purpose
GET /api/workflows List discovered workflows
GET /api/adapters List execution adapters
GET /api/workflows/{name}/dag Return the workflow graph
GET /api/workflows/{name}/capabilities Return the workflow's declared input and output capabilities (used for dataset matching, not engine support)
GET /api/workflows/{name}/editor Return an editable workflow document
PUT /api/workflows/{name} Save a workflow document
POST /api/workflows/validate Validate an unsaved workflow document
POST /api/run Start a workflow run
GET /api/workflows/{workflow_name}/preview-dataset-inputs Preview how dataset samples map to workflow inputs
WS /ws/execution/{run_id} Observe a run started with POST /api/run (replays buffered events, then streams live ones)

Runs and evaluation

Method Path Purpose
GET /api/runs List saved runs
GET /api/runs/summary Return aggregate run counts
GET /api/runs/{filename} Read one saved run
POST /api/runs/{filename}/evaluate Evaluate one saved run
GET /api/runs/{filename}/evaluation Read a saved evaluation
GET /api/runs/{run_id}/stream Stream saved run events over SSE
POST /api/eval/compare Compare evaluation results
GET /api/eval/datasets List evaluation datasets
GET /api/eval/datasets/{source}/{dataset_id:path}/samples List dataset samples
GET /api/eval/datasets/{source}/{dataset_id:path}/samples/{sample_index} Read one dataset sample

The query-string routes /api/eval/datasets/sample-list and /api/eval/datasets/sample-detail remain available for older clients. New clients should use the path-based routes in the table.


Endpoint reference

Health


GET /api/health

Liveness probe. No authentication required.

Response 200

{
  "status": "ok",
  "version": "0.1.0",
  "no_llm_mode": false
}

no_llm_mode is true when the server runs under AGENTIC_NO_LLM=1; it is read from the environment on every request.

GET /api/health/ready

Readiness probe. It checks the state needed to accept work and returns 503 when a required dependency is unavailable. Use this route for deployment readiness checks; use /api/health for liveness checks.

{
  "status": "ready",
  "dependencies": [{"name": "redis", "status": "skipped", "detail": null}],
  "degraded_selection_count": 0,
  "open_circuit_breakers": []
}

status is ready (HTTP 200) or not_ready (HTTP 503). Today the only critical dependency is Redis, and only when REDIS_URL is set (ok, down, or skipped). Open circuit breakers are reported but do not by themselves cause 503.


Agents


GET /api/agents

List all agents discovered from config/defaults/agents.yaml. Loaded on every request (no caching) to support live config edits during development.

Response 200

{
  "agents": [
    {
      "name": "Coder",
      "description": "Generates and modifies code",
      "tier": "2"
    }
  ]
}
Field Type Description
name string Display name
description string Human-readable role summary
tier string Model tier (e.g. "1", "2", "3")

Models


GET /api/models/probe

Re-run the LLM provider availability check and return the current tier-to-model mapping. Useful after rotating API keys or bringing a new provider online without a server restart. Requires LangChain extras.

Response 200

{
  "available_providers": ["github", "openai"],
  "unavailable_providers": ["anthropic", "gemini"],
  "tier_defaults": {
    "1": "ollama:phi4",
    "2": "gh:gpt-4o-mini",
    "3": "gh:gpt-4o"
  }
}

Error 503 — LangChain extras not installed.


POST /api/chat

Stream inference over SSE. The request has two mutually exclusive overloads:

{
  "model": "lmstudio:qwen2.5-0.5b-instruct",
  "messages": [{"role": "user", "content": "Reply with ok"}],
  "temperature": 0.2
}
{
  "tier": 2,
  "messages": [{"role": "user", "content": "Reply with ok"}],
  "temperature": 0.2
}

Exactly one of model or tier is required. Tiers 1–5 resolve through the configured model-router overrides, probed default, and ordered fallback chain. A tier request emits the selected model before completion output:

data: {"type":"route","requested_tier":2,"model":"lmstudio:qwen2.5-0.5b-instruct"}
data: {"type":"token","delta":"ok"}
data: {"type":"done","model":"lmstudio:qwen2.5-0.5b-instruct"}

Provider failures remain terminal in-stream error events on an HTTP 200 response. Request validation failures return HTTP 422; missing LangChain extras return 503.


Workflows


GET /api/workflows

List all available workflow definitions discovered from the YAML definitions directory.

Response 200

{
  "workflows": ["bug_resolution", "code_review", "test_deterministic"]
}

GET /api/adapters

List all registered execution engine adapters.

Response 200

{
  "adapters": ["native", "langchain"]
}

GET /api/workflows/{name}/dag

Return the DAG structure for React Flow visualization. Includes nodes, edges, and the workflow's input schema so the UI can render a run form.

Path parameters: name — workflow name (e.g. code_review)

Response 200

{
  "name": "code_review",
  "description": "Multi-step code review workflow",
  "nodes": [
    {
      "id": "load_code",
      "agent": "coder",
      "description": "Load the source file",
      "depends_on": [],
      "tier": null
    }
  ],
  "edges": [
    { "source": "load_code", "target": "review_code" }
  ],
  "inputs": [
    {
      "name": "file_path",
      "type": "string",
      "description": "Path to the file to review",
      "default": null,
      "required": true,
      "enum": null
    }
  ]
}

Error 404 — Workflow not found.


GET /api/workflows/{name}/capabilities

Return the I/O capability declarations for a workflow (declared inputs/outputs from YAML).

Response 200

{
  "workflow": "code_review",
  "capabilities": {
    "inputs": { "file_path": { "type": "string" } },
    "outputs": { "review_report": { "type": "object" } }
  }
}

GET /api/workflows/{name}/editor

Retrieve the raw YAML workflow document for in-browser editing. Returns the parsed document alongside the raw YAML text.

Response 200 — WorkflowEditorResponse

{
  "name": "code_review",
  "path": "/app/agentic_v2/workflows/definitions/code_review.yaml",
  "yaml_text": "name: code_review\n...",
  "document": { "name": "code_review", "steps": [] },
  "step_count": 4
}

Error 404 — Workflow YAML file not found. Error 422 — YAML parses but fails schema validation.


PUT /api/workflows/{name}

Validate and persist a modified workflow document. Clears the workflow config cache after a successful save.

Request body — WorkflowEditorRequest

Provide either document (JSON object) or yaml_text (raw YAML string). The model validates both and normalizes to document internally.

{
  "yaml_text": "name: my_workflow\nsteps:\n  ..."
}

Response 200 — Same as GET /api/workflows/{name}/editor after save.

Error 422 — Validation failed (detail contains schema errors). Error 503 — Workflow definitions directory is not writable.


POST /api/workflows/validate

Validate a workflow document without persisting it. Also performs graph topology compilation (cycle detection, missing dependency check).

Request body — Same as PUT /api/workflows/{name}.

Response 200 — WorkflowValidationResponse

{
  "valid": true,
  "name": "my_workflow",
  "step_count": 3,
  "yaml_text": "name: my_workflow\n..."
}

Error 422 — Validation failed (invalid YAML, missing required fields, cycle detected, or LangChain graph compilation error).


POST /api/run

Execute a workflow asynchronously. Returns immediately with a run ID; execution proceeds in a background task.

Request body — WorkflowRunRequest

{
  "workflow": "code_review",
  "input_data": {
    "file_path": "src/main.py",
    "language": "python"
  },
  "run_id": "my-run-001",
  "adapter": "langchain",
  "model_override": "ollama:qwen3-coder:30b",
  "model_pack": {"id": "my-pack", "version": 1},
  "evaluation": {
    "enabled": true,
    "dataset_source": "repository",
    "dataset_id": "humaneval",
    "sample_index": 0,
    "rubric_id": "code"
  },
  "execution_profile": {
    "runtime": "subprocess",
    "max_attempts": 3,
    "max_duration_minutes": 30
  }
}
Field Type Required Default Description
workflow string Yes — Workflow name or YAML path
input_data object No {} Key-value workflow inputs
run_id string No auto 1–128 chars: [a-zA-Z0-9_-]
adapter string No "langchain" "langchain" or "native"
model_override string No null Full prefixed model id applied to every step (max 200 chars; langchain adapter only, else 422)
model_pack {id, version} No null Immutable model-pack reference (langchain adapter only; validated at submit time)
evaluation object No null Evaluation settings
execution_profile object No null Runtime controls

execution_profile fields:

Field Type Default Description
runtime "subprocess" | "docker" "subprocess" Execution isolation
max_attempts int (>= 1) null Max retry attempts per step
max_duration_minutes int (>= 1) null Hard timeout for entire run
container_image string null Docker image (when runtime="docker")

evaluation fields: enabled (default false), enforce_hard_gates (default true), dataset_source (none, repository, or local), dataset_id, local_dataset_path, sample_index (>= 0), rubric_id, and the deprecated rubric.

Response 200 — WorkflowRunResponse

{
  "run_id": "code_review-a3f12b89",
  "status": "pending"
}

Error 400 — A workflow input value was blocked by the sanitization policy. Error 409 — run_id is already in use. Error 422 — Unknown adapter, invalid run_id format, a required workflow input missing or empty, model_override or model_pack with a non-langchain adapter, or an invalid model pack. Error 500 — Unexpected server error. An unknown workflow name currently also surfaces as 500, with the available names in detail. Error 501 — LangChain extras not installed (when adapter="langchain"). Error 503 — Sanitization layer not initialized.


Runs


GET /api/runs

List past workflow runs with summary metadata. Results are returned newest-first up to limit.

Query parameters:

Parameter Type Default Description
workflow string null Filter by workflow name
limit int 50 Maximum runs to return

Response 200 — list[RunSummaryModel]

[
  {
    "filename": "code_review-a3f12b89-2026-05-01T14:32:00.json",
    "run_id": "code_review-a3f12b89",
    "workflow_name": "code_review",
    "status": "success",
    "success_rate": 1.0,
    "total_duration_ms": 4230.5,
    "step_count": 4,
    "failed_step_count": 0,
    "start_time": "2026-05-01T14:32:00Z",
    "end_time": "2026-05-01T14:32:04Z",
    "evaluation_score": 87.5,
    "evaluation_grade": "B"
  }
]

GET /api/runs/summary

Aggregate statistics across all (or filtered) workflow runs.

Query parameters: workflow (optional filter)

Response 200 — RunsSummaryResponse

{
  "total_runs": 42,
  "success": 38,
  "failed": 4,
  "avg_duration_ms": 3812.0,
  "workflows": ["code_review", "research_report"],
  "tokens_30d": 1450000
}

GET /api/runs/{filename}

Full run detail including per-step results, token usage, and model identification. The filename can be the full JSON filename on disk or the run_id.

Path parameters: filename — run filename or run ID.

Response 200 — Raw run log object with run_id, workflow_name, status, steps[], final_output, and optional extra.evaluation.

Error 404 — Run not found or path traversal detected.


GET /api/runs/{filename}/evaluation

Full rubric breakdown for a scored workflow run. Returns criterion-level scores, score layers, hard gate results, and floor violations.

Response 200 — RunEvaluationDetailResponse

{
  "filename": "code_review-a3f12b89-2026-05-01.json",
  "run_id": "code_review-a3f12b89",
  "workflow_name": "code_review",
  "status": "success",
  "evaluation_requested": true,
  "dataset": null,
  "evaluation": {
    "rubric": "Code Quality",
    "rubric_id": "code",
    "overall_score": 82.5,
    "weighted_score": 87.5,
    "grade": "B",
    "passed": true,
    "pass_threshold": 70.0,
    "criteria": [
      {
        "criterion": "correctness",
        "weight": 0.4,
        "raw_score": 90,
        "normalized_score": 0.9,
        "weighted_contribution": 0.36
      }
    ],
    "hard_gates": {
      "required_outputs_present": true,
      "overall_status_success": true,
      "no_critical_step_failures": true
    },
    "floor_violations": []
  }
}

GET /api/runs/{run_id}/stream

Server-Sent Events stream of real-time execution events for a running (or recently completed) workflow of the caller's tenant; an unknown run or another tenant's run returns 404. The stream ends after an error event, an evaluation_complete event, or a workflow_end event with no evaluation pending (evaluation_enabled false). While idle it sends a keepalive event every 30 seconds and stays open.

Response — text/event-stream

data: {"type": "workflow_start", "run_id": "...", "workflow_name": "code_review", "timestamp": "..."}

data: {"type": "step_start", "run_id": "...", "step": "load_code", "timestamp": "..."}

data: {"type": "step_end", "run_id": "...", "step": "load_code", "status": "success", "duration_ms": 120.3, ...}

data: {"type": "workflow_end", "run_id": "...", "status": "success", "timestamp": "..."}

Keepalive events (no-op; sent after every 30 seconds of silence, the stream stays open):

data: {"type": "keepalive"}


Evaluation


GET /api/eval/datasets

List all evaluation dataset options available for the evaluation picker UI. Returns repository datasets, local datasets, and predefined evaluation sets.

Query parameters: workflow (optional — filter datasets compatible with a specific workflow)

Response 200 — ListEvaluationDatasetsResponse

{
  "repository": [
    {
      "id": "humaneval",
      "name": "HumanEval",
      "source": "repository",
      "description": "164 hand-crafted Python programming problems",
      "sample_count": 164
    }
  ],
  "local": [],
  "eval_sets": [
    {
      "id": "code-quality-suite",
      "name": "Code Quality Suite",
      "description": "Combined dataset for code evaluation",
      "datasets": ["humaneval", "mbpp"]
    }
  ]
}

GET /api/eval/datasets/sample-list

Deprecated. Use GET /api/eval/datasets/{source}/{dataset_id}/samples instead; it returns the same DatasetSampleListResponse with the source and dataset id as path segments.

Paginated list of compact sample summaries for a dataset.

Query parameters:

Parameter Type Required Description
dataset_source "repository" | "local" Yes Dataset origin
dataset_id string Yes Dataset identifier
offset int No (0) Pagination offset
limit int No (50) Page size

Response 200 — DatasetSampleListResponse

{
  "dataset_source": "repository",
  "dataset_id": "humaneval",
  "sample_count": 164,
  "offset": 0,
  "limit": 50,
  "samples": [
    {
      "sample_index": 0,
      "sample_id": "HumanEval/0",
      "task_id": "HumanEval/0",
      "title": "has_close_elements",
      "summary": "Check whether in given list of numbers...",
      "field_names": ["task_id", "prompt", "canonical_solution", "test", "entry_point"]
    }
  ]
}

GET /api/eval/datasets/sample-detail

Deprecated. Use GET /api/eval/datasets/{source}/{dataset_id}/samples/{sample_index} instead; it returns the same DatasetSampleDetailResponse with path parameters.

Full detail for a single dataset sample, including all fields and an optional workflow input preview.

Query parameters: dataset_source, dataset_id, sample_index

Response 200 — DatasetSampleDetailResponse

{
  "dataset_source": "repository",
  "dataset_id": "humaneval",
  "sample_index": 0,
  "sample_id": "HumanEval/0",
  "task_id": "HumanEval/0",
  "field_names": ["task_id", "prompt", "canonical_solution", "test"],
  "summary": "Check whether in given list of numbers...",
  "sample": {
    "task_id": "HumanEval/0",
    "prompt": "from typing import List\n\ndef has_close_elements...",
    "canonical_solution": "    for idx, elem in enumerate(numbers):\n..."
  },
  "dataset_meta": { "source": "openai/human-eval", "version": "1.0" },
  "workflow_preview": {
    "code": "from typing import List\n\ndef has_close_elements..."
  }
}

GET /api/workflows/{workflow_name}/preview-dataset-inputs

Preview how a dataset sample would be mapped to workflow inputs before executing a run.

Query parameters: dataset_source, dataset_id, sample_index

Response 200

{
  "workflow": "code_review",
  "dataset_id": "humaneval",
  "sample_index": 0,
  "adapted_inputs": {
    "code": "from typing import List\n\ndef has_close_elements...",
    "language": "python"
  }
}

WebSocket


WS /ws/execution/{run_id}

WebSocket endpoint for real-time execution streaming. Preferred over SSE for bidirectional control or when the client needs to receive events as they are generated during a run rather than polling.

Connection sequence:

  1. Validate browser origin (when Origin header is present).
  2. Validate API key via Authorization: Bearer <key> or X-API-Key: <key> header. Query-string tokens are rejected (close code 1008).
  3. Resolve the caller's tenant and confirm the run exists for it; the socket does not start a run, so connect after POST /api/run.
  4. Accept the connection (when tracing is enabled the first message carries the W3C trace context) and replay all buffered events (up to 500 events) so late-connecting clients can reconstruct current state.
  5. Receive live events as the engine broadcasts them.
  6. Client may send any text (ping); server ignores content.
  7. On disconnect: connection is removed; replay buffer is retained until clear_buffer() is called.

Close codes:

Code Reason
1008 Origin not in allowlist
1008 Query-string API key rejected
1008 Invalid or missing API key
1008 Too many failed authentication attempts from this IP
1008 Run not found (unknown run, or owned by another tenant)

Event payloads — see Runtime data contracts for the full discriminated union.


Error response format

All error responses use the standard FastAPI JSON format:

{
  "detail": "Human-readable error description"
}

For 422 Unprocessable Entity (request body validation failures), FastAPI returns a structured body with per-field details:

{
  "detail": [
    {
      "loc": ["body", "workflow"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}
HTTP status Meaning
400 Workflow input blocked by the sanitization policy, or a malformed JSON body
401 Missing or invalid API key
404 Workflow, run, or dataset not found
409 Run id already in use
422 Validation error (invalid schema, cycle in DAG, missing required input, etc.) or a request body blocked by the sanitization middleware
429 Global rate limit exceeded, or per-IP auth throttle active. Check the Retry-After header.
500 Unhandled internal error
501 LangChain extras not installed
503 External dependency unavailable (LLM probe failed, directory not writable)

Rate limiting and safety

The runtime server enforces HTTP-level rate limiting at the application layer via slowapi. Production deployments may stack an additional reverse-proxy or API-gateway layer on top.

Control Mechanism
Global rate limiting slowapi per-IP sliding-window limit (default 60/minute). Configurable via AGENTIC_RATE_LIMIT_DEFAULT. Exceeding the limit returns 429 Too Many Requests with a Retry-After header. In-process only — multi-replica deployments share no state.
Per-IP auth throttle AuthThrottle in server/auth.py tracks consecutive 401 failures per source IP. Five failures within a 60-second window trigger a 300-second lockout (429 with Retry-After). All thresholds configurable via AGENTIC_AUTH_LOCKOUT_WINDOW_SECONDS, AGENTIC_AUTH_LOCKOUT_THRESHOLD, and AGENTIC_AUTH_LOCKOUT_DURATION_SECONDS. In-process only — same multi-replica caveat as above. See ADR-018.
Input sanitization JSON request bodies pass through the sanitization middleware pipeline (secrets, PII, prompt injection, Unicode normalization, content classification) before reaching route handlers
Path containment Run log file access (GET /api/runs/{filename}) enforces directory boundary checks to prevent traversal attacks
Timing-safe key comparison API key validation uses secrets.compare_digest() to mitigate timing-based key enumeration

SPA fallback

For any request path that does not match a defined API endpoint, the server attempts to serve the compiled frontend application:

GET /{path:path}  →  ui/dist/index.html

This catch-all route is active only when ui/dist/index.html exists on the filesystem. If the file is absent (e.g., in a backend-only deployment), unmatched paths return 404 Not Found.

This pattern enables client-side routing in the React SPA without requiring server-side route configuration for each UI page.


Wire-format drift gate

The Pydantic contracts in agentic_v2/contracts/ and several server/models types are the source of truth for the committed JSON schemas under tests/schemas/ and the generated TypeScript in ui/src/api/. The wire-format-drift CI job fails any PR where the committed artifacts diverge from the regenerated output. Regenerate deliberately with python -m scripts.generate_ts_types (backend) followed by npm run generate:types in ui/, and commit the result. See ADR-014.

Note on depends_on: DAGNodeModel.depends_on and WorkflowEditorStep.depends_on are required fields (no default). Consumers that construct these models from dicts must supply depends_on explicitly — even as an empty list — to avoid a ValidationError. The generated TypeScript types require the field.