Skip to the content.

API Reference

Koor exposes a REST API on the configured bind address (default localhost:9800). All responses are JSON. All endpoints except /health require authentication when an auth token is configured.

Authentication

When the server is started with --auth-token, every request (except /health) must include a Bearer token:

Authorization: Bearer <token>

If no auth token is configured (local mode), all requests pass through without authentication.

Unauthorized requests return:

{"error": "invalid or missing bearer token", "code": 401}

Error Format

All errors return a JSON body:

{
  "error": "description of what went wrong",
  "code": 404
}

Standard HTTP status codes are used: 200 (success), 304 (not modified), 400 (bad request), 401 (unauthorized), 404 (not found), 500 (internal server error).


Health

Health check endpoint. No authentication required.

GET /health

Response 200

{
  "status": "ok",
  "uptime": "3h24m10s"
}

State

Key/value store for shared data. Values can be any content type (defaults to application/json). Supports ETag-based caching. Version auto-increments on each update.

GET /api/state

List all state keys. Returns summaries (no values).

Response 200

[
  {
    "key": "api-contract",
    "version": 3,
    "content_type": "application/json",
    "updated_at": "2026-02-09T14:30:00Z"
  },
  {
    "key": "build-config",
    "version": 1,
    "content_type": "application/json",
    "updated_at": "2026-02-09T12:00:00Z"
  }
]

Returns an empty array [] when no keys exist.

GET /api/state/{key…}

Get the value for a key. Keys can contain slashes for project scoping (e.g. Truck-Wash/backend-task). Returns the raw stored value with its original content type.

Also supports version history, specific version retrieval, and version diffing via query parameters.

Query Parameters

Parameter Description
history=1 List version history instead of returning the current value
limit=N Limit history results (default 50, only with history=1)
version=N Get a specific historical version
diff=v1,v2 JSON diff between two versions

Response Headers (default mode)

Header Description
Content-Type The content type set when the value was stored
ETag SHA-256 hash of the value, quoted ("abc123...")
X-Koor-Version Integer version number

Response 200 — Raw value body (default mode)

ETag Caching — Send If-None-Match with the ETag value to get a 304 Not Modified if the value hasn’t changed:

GET /api/state/api-contract
If-None-Match: "e3b0c44298fc1c149afb..."

Response 304 with empty body if unchanged.

History ModeGET /api/state/api-contract?history=1&limit=10

{
  "key": "api-contract",
  "versions": [
    {"version": 3, "hash": "abc...", "updated_at": "2026-02-16T14:30:00Z"},
    {"version": 2, "hash": "def...", "updated_at": "2026-02-16T13:00:00Z"},
    {"version": 1, "hash": "ghi...", "updated_at": "2026-02-16T12:00:00Z"}
  ]
}

Version ModeGET /api/state/api-contract?version=2

Returns the raw value body of version 2 with Content-Type and X-Koor-Version headers.

Diff ModeGET /api/state/api-contract?diff=1,3

{
  "key": "api-contract",
  "v1": 1,
  "v2": 3,
  "diffs": [
    {"operation": "replace", "path": "/version", "old_value": "1.0", "new_value": "3.0"}
  ]
}

Error 404

{"error": "key not found: api-contract", "code": 404}

PUT /api/state/{key…}

Create or update a state entry. Keys can contain slashes for project scoping (e.g. Truck-Wash/backend-task). Send the raw value as the request body (up to 10 MB).

Request Headers

Header Required Default Description
Content-Type No application/json Stored with the value

Request Body — Raw value (any format).

Response 200

{
  "key": "api-contract",
  "version": 2,
  "hash": "e3b0c44298fc1c149afb...",
  "content_type": "application/json",
  "updated_at": "2026-02-09T14:30:00Z"
}

Version starts at 1 for new keys and increments by 1 on each update.

Error 400 — Empty body:

{"error": "empty body", "code": 400}

POST /api/state/{key…}?rollback=N

Rollback a state key to a previous version. The historical value is restored as a new version.

Query Parameters

Parameter Required Description
rollback Yes Version number to rollback to

Example

POST /api/state/api-contract?rollback=2

Response 200

{
  "key": "api-contract",
  "version": 4,
  "hash": "e3b0c44298fc1c14...",
  "rolled_back": 2,
  "updated_at": "2026-02-16T15:00:00Z"
}

Error 400 — Missing or invalid rollback version. Error 404 — Key or version not found.


DELETE /api/state/{key…}

Delete a state entry. Keys can contain slashes for project scoping.

Response 200

{"deleted": "api-contract"}

Error 404

{"error": "key not found: api-contract", "code": 404}

Specs

Per-project specification storage. Specs are keyed by {project}/{name}. Supports ETag caching and auto-incrementing versions.

GET /api/specs/{project}

List all specs for a project. Returns summaries (no data blobs).

Response 200

{
  "project": "w2c-forms",
  "specs": [
    {
      "name": "button-schema",
      "version": 2,
      "updated_at": "2026-02-09T14:30:00Z"
    },
    {
      "name": "modal-schema",
      "version": 1,
      "updated_at": "2026-02-09T12:00:00Z"
    }
  ]
}

Returns {"project": "...", "specs": []} when no specs exist for the project.

GET /api/specs/{project}/{name}

Get a spec’s data. Returns the raw stored data.

Response Headers

Header Description
Content-Type application/json
ETag SHA-256 hash of the data, quoted
X-Koor-Version Integer version number

Response 200 — Raw spec data body

ETag Caching — Same behaviour as state: send If-None-Match for 304 Not Modified.

Error 404

{"error": "spec not found: w2c-forms/button-schema", "code": 404}

PUT /api/specs/{project}/{name}

Create or update a spec. Send the raw data as the request body (up to 10 MB).

Response 200

{
  "project": "w2c-forms",
  "name": "button-schema",
  "version": 2,
  "hash": "a1b2c3d4e5f6...",
  "updated_at": "2026-02-09T14:30:00Z"
}

Error 400 — Empty body:

{"error": "empty body", "code": 400}

DELETE /api/specs/{project}/{name}

Delete a spec.

Response 200

{"deleted": "w2c-forms/button-schema"}

Error 404

{"error": "spec not found: w2c-forms/button-schema", "code": 404}

Events

Pub/sub event bus with SQLite-backed history and real-time WebSocket streaming. Topics are dot-separated strings (e.g. api.change.contract). Pattern matching uses glob syntax via path.Match.

POST /api/events/publish

Publish an event to a topic. The event is persisted to history and fanned out to active WebSocket subscribers whose patterns match.

Request Body

{
  "topic": "api.change.contract",
  "data": {"version": "2.0", "breaking": true}
}
Field Required Description
topic Yes Dot-separated topic string
data No Any JSON value (stored as-is)

Response 200

{
  "id": 42,
  "topic": "api.change.contract",
  "data": {"version": "2.0", "breaking": true},
  "source": "",
  "created_at": "2026-02-09T14:30:00Z"
}

Error 400

{"error": "topic is required", "code": 400}

GET /api/events/history

Retrieve recent events from history. Supports time-range and source filtering.

Query Parameters

Parameter Default Description
last 50 Number of events to return (most recent first)
topic * (all) Glob pattern to filter by topic
from (none) Start time (RFC 3339, e.g. 2026-02-16T14:00:00Z)
to (none) End time (RFC 3339)
source (none) Filter by event source

Examples

GET /api/events/history
GET /api/events/history?last=10
GET /api/events/history?last=100&topic=api.*
GET /api/events/history?from=2026-02-16T14:00:00Z&to=2026-02-16T15:00:00Z
GET /api/events/history?source=agent-1&topic=api.*

Response 200

[
  {
    "id": 42,
    "topic": "api.change.contract",
    "data": {"version": "2.0"},
    "source": "",
    "created_at": "2026-02-09T14:30:00Z"
  }
]

Returns an empty array [] when no events match.

GET /api/events/subscribe

WebSocket endpoint for real-time event streaming. Connect with a WebSocket client to receive events as they are published.

Query Parameters

Parameter Default Description
pattern * (all) Glob pattern to filter events by topic

Example Connection

ws://localhost:9800/api/events/subscribe?pattern=api.*

Each event is sent as a JSON text frame:

{
  "id": 43,
  "topic": "api.change.contract",
  "data": {"version": "2.0"},
  "source": "",
  "created_at": "2026-02-09T14:30:05Z"
}

The connection remains open until the client disconnects or the server shuts down. If a subscriber is slow, events may be dropped (64-event channel buffer per subscriber).

Topic Pattern Matching

Patterns use Go’s path.Match glob syntax on dot-separated topics:

Pattern Matches
* All topics
api.* api.change, api.deploy
api.change.* api.change.contract, api.change.schema

Instances

Agent instance registration and discovery. Each instance gets a unique ID and token on registration. Tokens are only returned on registration, not on subsequent GET requests.

GET /api/instances

List all registered instances. Supports optional query parameter filters.

Query Parameters

Parameter Required Description
name No Filter by agent name
workspace No Filter by workspace
stack No Filter by technology stack (e.g. goth, react)
capability No Filter by declared capability

Examples

GET /api/instances
GET /api/instances?stack=goth
GET /api/instances?name=claude&workspace=/projects/frontend

Response 200

[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "claude-frontend",
    "workspace": "/projects/frontend",
    "intent": "implementing dark mode",
    "stack": "goth",
    "capabilities": ["code-review", "testing"],
    "status": "active",
    "registered_at": "2026-02-09T12:00:00Z",
    "last_seen": "2026-02-09T14:30:00Z"
  }
]

Returns an empty array [] when no instances are registered.

GET /api/instances/{id}

Get a single instance by ID. Token is not included in the response.

Response 200

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "claude-frontend",
  "workspace": "/projects/frontend",
  "intent": "implementing dark mode",
  "stack": "goth",
  "registered_at": "2026-02-09T12:00:00Z",
  "last_seen": "2026-02-09T14:30:00Z"
}

Error 404

{"error": "instance not found: 550e8400-...", "code": 404}

POST /api/instances/register

Register a new agent instance. Returns the instance with its token (save this — it is only returned once).

Request Body

{
  "name": "claude-frontend",
  "workspace": "/projects/frontend",
  "intent": "implementing dark mode",
  "stack": "goth"
}
Field Required Description
name Yes Agent name (e.g. claude-frontend)
workspace No Workspace path or identifier
intent No Current task description
stack No Technology stack identifier (e.g. goth, react)

Response 200

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "claude-frontend",
  "workspace": "/projects/frontend",
  "intent": "implementing dark mode",
  "stack": "goth",
  "token": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "registered_at": "2026-02-09T14:30:00Z",
  "last_seen": "2026-02-09T14:30:00Z"
}

Error 400

{"error": "name is required", "code": 400}

POST /api/instances/{id}/heartbeat

Update the last_seen timestamp for an instance. Call periodically to indicate the agent is still active.

Request Body — None required.

Response 200

{"id": "550e8400-...", "status": "ok"}

Error 404

{"error": "instance not found: 550e8400-...", "code": 404}

POST /api/instances/{id}/activate

Activate an agent instance (confirms CLI connectivity after registration).

Request Body — None required.

Response 200

{"id": "550e8400-...", "status": "active"}

Error 404

{"error": "instance not found: 550e8400-...", "code": 404}

POST /api/instances/{id}/capabilities

Set the capabilities for an agent instance. Capabilities are strings describing what the agent can do (e.g. code-review, testing, deployment).

Request Body

{
  "capabilities": ["code-review", "testing", "deployment"]
}

Response 200

{"id": "550e8400-...", "capabilities": ["code-review", "testing", "deployment"]}

Error 404

{"error": "instance not found: 550e8400-...", "code": 404}

GET /api/instances/stale

List instances that have been marked stale (no heartbeat within the configured timeout, default 5 minutes).

Response 200

[
  {
    "id": "550e8400-...",
    "name": "claude-frontend",
    "workspace": "/projects/frontend",
    "status": "stale",
    "last_seen": "2026-02-09T14:00:00Z"
  }
]

Returns an empty array [] when no instances are stale.

DELETE /api/instances/{id}

Deregister an instance.

Response 200

{"deleted": "550e8400-e29b-41d4-a716-446655440000"}

Error 404

{"error": "instance not found: 550e8400-...", "code": 404}

Liveness

Background liveness monitoring detects agents that have stopped sending heartbeats.

POST /api/liveness/check

Force an immediate liveness check. Returns any instances that were newly marked as stale.

Response 200

{
  "checked": true,
  "newly_stale": [
    {"id": "550e8400-...", "name": "claude-frontend", "status": "stale"}
  ],
  "count": 1
}

Validation

Rule-based content validation. Rules are stored per-project and can check for forbidden patterns (regex), required patterns (missing), or custom checks. Rules can be scoped to a technology stack (e.g. goth, react) so that stack-specific rules only fire when validating content for that stack.

GET /api/validate/{project}/rules

List all validation rules for a project. Supports optional stack filter.

Query Parameters

Parameter Required Description
stack No Filter rules by technology stack

Examples

GET /api/validate/w2c-forms/rules
GET /api/validate/w2c-forms/rules?stack=goth

Response 200

{
  "project": "w2c-forms",
  "rules": [
    {
      "project": "w2c-forms",
      "rule_id": "no-inline-style",
      "severity": "error",
      "match_type": "regex",
      "pattern": "style\\s*=",
      "message": "Inline styles are not allowed",
      "applies_to": ["*.html", "*.templ"],
      "stack": "goth"
    }
  ]
}

Returns {"project": "...", "rules": []} when no rules exist.

PUT /api/validate/{project}/rules

Replace all validation rules for a project. Existing rules are deleted and replaced with the provided set.

Request Body — Array of rule objects:

[
  {
    "rule_id": "no-inline-style",
    "severity": "error",
    "match_type": "regex",
    "pattern": "style\\s*=",
    "message": "Inline styles are not allowed",
    "applies_to": ["*.html", "*.templ"],
    "stack": "goth"
  },
  {
    "rule_id": "require-data-ai-id",
    "severity": "warning",
    "match_type": "missing",
    "pattern": "data-ai-id",
    "message": "Components should have data-ai-id attributes",
    "applies_to": ["*.templ"]
  }
]

Rule Fields

Field Required Default Description
rule_id Yes Unique identifier within the project
severity No error error or warning
match_type No regex regex, missing, or custom
pattern Yes Regex pattern or custom check name
message No Auto-generated Human-readable violation message
applies_to No ["*"] Glob patterns for filename filtering
stack No "" (all stacks) Technology stack this rule applies to (e.g. goth, react). Empty means universal.

Match Types

Type Behaviour
regex Flags each line matching the pattern as a violation
missing Flags a violation if the pattern is NOT found anywhere in the content
custom Built-in check (currently: no-console-log). Unknown custom patterns fall back to regex

Response 200

{"project": "w2c-forms", "count": 2}

POST /api/validate/{project}

Validate content against all rules for a project.

Request Body

{
  "filename": "button.templ",
  "content": "<div style=\"color: red\" class=\"c-button\">\n  <span>Click me</span>\n</div>",
  "stack": "goth"
}
Field Required Description
filename No Used to match applies_to glob patterns. If omitted, all rules run.
content Yes The content to validate
stack No Technology stack to filter rules by. When set, only universal rules (no stack) and rules matching this stack are applied.

Response 200

{
  "project": "w2c-forms",
  "violations": [
    {
      "rule_id": "no-inline-style",
      "severity": "error",
      "message": "Inline styles are not allowed",
      "line": 1,
      "match": "style=\"color: red\""
    }
  ],
  "count": 1
}

Returns {"project": "...", "violations": [], "count": 0} when content passes all rules.


Rules Management

Rule lifecycle management — propose, accept, reject, export, and import rules. Rules have three sources (local, learned, external) and a status (accepted, proposed, rejected). Only accepted rules participate in validation.

POST /api/rules/propose

LLM agents propose a rule after solving an issue. The rule is stored with source=learned, status=proposed and must be accepted by a user before it fires during validation.

Request Body

{
  "project": "w2c-forms",
  "rule_id": "no-hardcoded-colors",
  "severity": "warning",
  "match_type": "regex",
  "pattern": "#[0-9a-fA-F]{3,8}",
  "message": "Use CSS custom properties instead of hardcoded colors",
  "applies_to": ["*.templ", "*.css"],
  "stack": "goth",
  "proposed_by": "550e8400-e29b-41d4-a716-446655440000",
  "context": "Instance found hardcoded hex colors causing theme inconsistency."
}
Field Required Description
project Yes Project the rule applies to
rule_id Yes Unique rule identifier
pattern Yes Regex pattern or custom check name
severity No error or warning (default: error)
match_type No regex, missing, or custom (default: regex)
message No Human-readable violation message
stack No Technology stack this rule targets
proposed_by No Instance ID of the proposing agent
context No Description of the issue that led to this rule

Response 200

{"project": "w2c-forms", "rule_id": "no-hardcoded-colors", "status": "proposed"}

POST /api/rules/{project}/{ruleID}/accept

Accept a proposed rule, making it active during validation.

Response 200

{"project": "w2c-forms", "rule_id": "no-hardcoded-colors", "status": "accepted"}

Error 404 — Rule not found or not in proposed status.

POST /api/rules/{project}/{ruleID}/reject

Reject a proposed rule. It remains stored but will never fire during validation.

Response 200

{"project": "w2c-forms", "rule_id": "no-hardcoded-colors", "status": "rejected"}

Error 404 — Rule not found or not in proposed status.

GET /api/rules/export

Export accepted rules filtered by source. Use this to download your organisation’s rules and learned procedures.

Query Parameters

Parameter Default Description
source local,learned Comma-separated list of sources to include

Examples

GET /api/rules/export
GET /api/rules/export?source=local,learned
GET /api/rules/export?source=external

Response 200 — Array of rule objects.

[
  {
    "project": "w2c-forms",
    "rule_id": "no-inline-style",
    "severity": "error",
    "match_type": "regex",
    "pattern": "style\\s*=",
    "message": "Inline styles are not allowed",
    "stack": "goth",
    "source": "local",
    "status": "accepted"
  }
]

POST /api/rules/import

Bulk import rules. Uses UPSERT — existing rules with the same project/rule_id are updated. Imported rules are automatically accepted.

Request Body — Array of rule objects:

[
  {
    "project": "w2c-forms",
    "rule_id": "ext-no-console-log",
    "severity": "error",
    "match_type": "regex",
    "pattern": "console\\.log\\(",
    "message": "Remove console.log statements",
    "applies_to": ["*.js", "*.ts"],
    "source": "external"
  }
]

Response 200

{"imported": 1}

Error 400 — Empty rules array.


Metrics

GET /api/metrics

Server metrics summary (token tax tracking).

Response 200

{
  "uptime": "3h24m10s",
  "state_keys": 5,
  "instances": 2,
  "last_event_id": 42,
  "api_bind": "localhost:9800",
  "dashboard_bind": "localhost:9847"
}

Webhooks

Register URLs to receive HTTP POST notifications when events match specified patterns. Webhooks include HMAC signatures when a secret is configured, and auto-disable after 10 consecutive failures.

POST /api/webhooks

Register a new webhook.

Request Body

{
  "id": "slack-notify",
  "url": "https://hooks.example.com/koor",
  "patterns": ["agent.*", "compliance.*"],
  "secret": "my-hmac-secret"
}
Field Required Default Description
id Yes Unique webhook identifier
url Yes URL to POST events to
patterns No ["*"] Event topic patterns to match
secret No "" HMAC-SHA256 secret for signing payloads

Response 200

{
  "id": "slack-notify",
  "url": "https://hooks.example.com/koor",
  "patterns": ["agent.*", "compliance.*"],
  "active": true,
  "created_at": "2026-02-16T14:30:00Z",
  "fail_count": 0
}

GET /api/webhooks

List all registered webhooks.

Response 200

[
  {
    "id": "slack-notify",
    "url": "https://hooks.example.com/koor",
    "patterns": ["agent.*"],
    "active": true,
    "created_at": "2026-02-16T14:30:00Z",
    "last_fired": "2026-02-16T15:00:00Z",
    "fail_count": 0
  }
]

DELETE /api/webhooks/{id}

Delete a webhook.

Response 200

{"deleted": "slack-notify"}

Error 404

{"error": "webhook not found: slack-notify", "code": 404}

POST /api/webhooks/{id}/test

Fire a test event to the webhook to verify connectivity.

Response 200

{"tested": "slack-notify", "status": "ok"}

Error 404 — Webhook not found. Error 400 — Test delivery failed.


Compliance

Scheduled contract validation that checks active agents against their project contracts. Runs automatically every 5 minutes and emits compliance.violation events on failures.

GET /api/compliance/history

Query recent compliance check results.

Query Parameters

Parameter Default Description
instance_id (all) Filter by specific agent instance
limit 50 Maximum results to return

Response 200

[
  {
    "id": 1,
    "instance_id": "550e8400-...",
    "project": "Truck-Wash",
    "contract": "api-contract",
    "pass": true,
    "violations": [],
    "run_at": "2026-02-16T14:30:00Z"
  }
]

POST /api/compliance/run

Force an immediate compliance check across all active agents.

Response 200

{
  "checked": true,
  "runs": [
    {
      "id": 5,
      "instance_id": "550e8400-...",
      "project": "Truck-Wash",
      "contract": "api-contract",
      "pass": false,
      "violations": [{"field": "color", "message": "missing required field"}],
      "run_at": "2026-02-16T15:00:00Z"
    }
  ],
  "count": 1
}

Templates

Shareable template bundles for packaging and distributing rule sets, contracts, or mixed bundles across projects.

POST /api/templates

Create a new template.

Request Body

{
  "id": "strict-api-rules",
  "name": "Strict API Rules",
  "description": "Standard API validation rules for all projects",
  "kind": "rules",
  "data": [{"rule_id": "no-console-log", "pattern": "console\\.log"}],
  "tags": ["api", "strict"]
}
Field Required Description
id Yes Unique template identifier
name Yes Human-readable name
description No Description
kind No rules, contracts, or bundle
data No Template payload (JSON)
tags No Metadata tags for filtering

Response 200 — The created template object.

GET /api/templates

List all templates.

Query Parameters

Parameter Description
kind Filter by kind (rules, contracts, bundle)
tag Filter by tag

Response 200

[
  {
    "id": "strict-api-rules",
    "name": "Strict API Rules",
    "description": "Standard API validation rules",
    "kind": "rules",
    "tags": ["api", "strict"],
    "version": 1,
    "created_at": "2026-02-16T14:30:00Z",
    "updated_at": "2026-02-16T14:30:00Z"
  }
]

GET /api/templates/{id}

Get a template with its full data payload.

Response 200 — Full template object including data.

Error 404

{"error": "template not found: strict-api-rules", "code": 404}

DELETE /api/templates/{id}

Delete a template.

Response 200

{"deleted": "strict-api-rules"}

POST /api/templates/{id}/apply

Apply a template to a project. For rules templates, rules are imported. For contracts templates, data is stored as a spec.

Request Body

{
  "project": "Truck-Wash"
}

Response 200

{"applied": "strict-api-rules", "project": "Truck-Wash", "kind": "rules"}

Audit

Immutable, append-only log of all configuration changes. Records who changed what, when, and the outcome. Every mutating API call is logged automatically.

GET /api/audit

Query the audit log.

Query Parameters

Parameter Default Description
actor (all) Filter by actor (agent or user)
action (all) Filter by action type (e.g. state.put, webhook.create)
from (none) Start time (ISO 8601)
to (none) End time (ISO 8601)
limit 50 Maximum entries to return

Examples

GET /api/audit
GET /api/audit?action=state.put&limit=10
GET /api/audit?actor=agent-1&from=2026-02-16T00:00:00Z

Response 200

[
  {
    "id": 42,
    "timestamp": "2026-02-16T14:30:00Z",
    "actor": "agent-1",
    "action": "state.put",
    "resource": "Truck-Wash/status",
    "detail": "{\"version\":1}",
    "outcome": "success"
  }
]

Tracked Actions

Action Description
state.put State key created or updated
state.rollback State key rolled back to previous version
state.delete State key deleted
spec.put Spec created or updated
spec.delete Spec deleted
instance.register Agent registered
instance.activate Agent activated
instance.deregister Agent deregistered
instance.capabilities Agent capabilities updated
rule.propose Validation rule proposed
rule.accept Proposed rule accepted
rule.reject Proposed rule rejected
rules.import Rules imported in bulk
webhook.create Webhook registered
webhook.delete Webhook deleted
template.create Template created
template.delete Template deleted
template.apply Template applied to project
llm.usage LLM usage recorded

GET /api/audit/summary

Aggregated summary of audit activity.

Query Parameters

Parameter Description
from Start time (ISO 8601)
to End time (ISO 8601)

Response 200

{
  "total_entries": 42,
  "action_counts": {"state.put": 20, "spec.put": 10, "instance.register": 5},
  "outcome_counts": {"success": 40, "error": 2},
  "unique_actors": 3,
  "unique_resources": 15
}

Agent Metrics

Per-agent operational metrics aggregated in hourly buckets. Tracks call counts, violations, rollbacks, and other counters per agent.

GET /api/metrics/agents

Query agent metrics. Without instance_id, returns aggregated summaries for all agents. With instance_id, returns detailed per-period metrics.

Query Parameters

Parameter Default Description
instance_id (all) Filter by specific agent instance
period (all) Filter by time period prefix (e.g. 2026-02-16 for a day, 2026-02-16T14 for an hour)

Response 200 (summary mode, no instance_id)

[
  {
    "instance_id": "550e8400-...",
    "metrics": {"rest_calls": 150, "mcp_calls": 5, "violations": 2}
  }
]

Response 200 (detail mode, with instance_id)

[
  {
    "instance_id": "550e8400-...",
    "metric_name": "rest_calls",
    "metric_value": 42,
    "period": "2026-02-16T14"
  }
]

GET /api/metrics/agents/{id}

Get metrics for a specific agent.

Query Parameters

Parameter Default Description
period (all) Filter by time period prefix

Response 200

[
  {
    "instance_id": "550e8400-...",
    "metric_name": "rest_calls",
    "metric_value": 42,
    "period": "2026-02-16T14"
  }
]

Returns an empty array [] when no metrics exist.


LLM Cost Tracking

Track per-agent, per-project, per-model token usage and costs. Record usage after each LLM call and query summaries to understand where your budget goes.

POST /api/llm/usage

Record an LLM usage entry.

Request Body

{
  "instance_id": "550e8400-...",
  "project": "Truck-Wash",
  "provider": "anthropic",
  "model": "claude-sonnet-4-20250514",
  "tokens_in": 1500,
  "tokens_out": 800,
  "cost_usd": 0.012,
  "request_type": "completion",
  "session_tag": "refactor-auth"
}
Field Required Default Description
provider Yes LLM provider (e.g. anthropic, openai)
model Yes Model identifier (e.g. claude-sonnet-4-20250514, gpt-4)
instance_id No "" Agent instance that made the call
project No "" Project context
tokens_in No 0 Input/prompt tokens
tokens_out No 0 Output/completion tokens
cost_usd No 0 Estimated cost in USD
request_type No completion completion or embedding
session_tag No "" Grouping key for related calls

Response 200

{
  "id": 42,
  "recorded": true
}

Error 400 — Missing provider or model.

GET /api/llm/usage

Query LLM usage records. Filter by instance, project, or session tag. Only one filter is applied (priority: instance > project > session_tag > all).

Query Parameters

Parameter Default Description
instance (none) Filter by agent instance ID
project (none) Filter by project
session_tag (none) Filter by session tag
from (none) Start time (ISO 8601)
to (none) End time (ISO 8601)
limit 50 Maximum records to return

Examples

GET /api/llm/usage
GET /api/llm/usage?instance=agent-1&limit=20
GET /api/llm/usage?project=Truck-Wash&from=2026-02-16T00:00:00Z
GET /api/llm/usage?session_tag=refactor-auth

Response 200

[
  {
    "id": 42,
    "instance_id": "agent-1",
    "project": "Truck-Wash",
    "provider": "anthropic",
    "model": "claude-sonnet-4-20250514",
    "tokens_in": 1500,
    "tokens_out": 800,
    "cost_usd": 0.012,
    "request_type": "completion",
    "session_tag": "refactor-auth",
    "created_at": "2026-02-28T14:30:00Z"
  }
]

Returns an empty array [] when no records match.

GET /api/llm/usage/summary

Aggregated usage summary grouped by a chosen dimension. Includes both per-group breakdowns and a grand total.

Query Parameters

Parameter Default Description
group_by project Grouping dimension: instance, project, model, or session_tag
from (none) Start time (ISO 8601)
to (none) End time (ISO 8601)

Examples

GET /api/llm/usage/summary
GET /api/llm/usage/summary?group_by=model
GET /api/llm/usage/summary?group_by=instance&from=2026-02-28T00:00:00Z

Response 200

{
  "group_by": "model",
  "from": "",
  "to": "",
  "groups": {
    "claude-sonnet-4-20250514": {
      "total_tokens_in": 15000,
      "total_tokens_out": 8000,
      "total_cost_usd": 0.12,
      "request_count": 10
    },
    "gpt-4": {
      "total_tokens_in": 5000,
      "total_tokens_out": 2000,
      "total_cost_usd": 0.08,
      "request_count": 5
    }
  },
  "total": {
    "total_tokens_in": 20000,
    "total_tokens_out": 10000,
    "total_cost_usd": 0.20,
    "request_count": 15
  }
}

Error 400 — Invalid group_by value.


MCP

Model Context Protocol endpoint using StreamableHTTP transport. This is the discovery-only interface for LLM agents. For data operations, use the REST API or CLI.

POST /mcp

StreamableHTTP MCP transport. Connect via MCP client libraries (e.g. mark3labs/mcp-go, Claude Code MCP config).

MCP Tools

Tool Parameters Description
register_instance name (required), workspace, intent, stack, capabilities Register this agent instance. Returns instance ID, token, and REST endpoints.
discover_instances name, workspace, stack, capability Discover other registered agent instances. Filters are optional.
set_intent instance_id (required), intent (required) Update intent and refresh last_seen timestamp.
get_endpoints (none) Get all REST API and CLI endpoints for direct data access.
propose_rule project (required), rule_id (required), pattern (required), message (required), severity, match_type, stack, proposed_by, context Propose a validation rule for user review.

The MCP interface provides 5 lightweight discovery and proposal tools. All data operations (state, specs, events) should go through the REST API directly, bypassing the LLM context window.


Dashboard

The web dashboard runs on a separate port (default localhost:9847). It serves embedded static files and proxies /api/* requests to the API server to avoid CORS issues.

Path Description
GET / Dashboard web UI
GET /api/* Proxied to API server
GET /health Health check