Reference

REST API

You only need this if you're writing your own client — in Go, Node, Rust, anything. The Python SDK covers all of it.

Two audiences, two credentials

The API serves running agents and dashboard users, and they authenticate differently. The separation is enforced per endpoint: an agent key cannot reach a project route, and a user token cannot reach an agent route.

CallerHeaderEndpoints
Agent X-AgentWay-Key: ak_… /v1/agents/*, /v1/directives/*/respond, /v1/inbox
Dashboard user Authorization: Bearer <jwt> /v1/projects/**

Agent requests also need X-AgentWay-Agent. A scope key covers every agent in its scope, so after registration each request must name which agent is calling — the value is the agent's slug. The server verifies that agent lives in the key's scope. The Python SDK sets this for you.

Agent endpoints

POST /v1/agents/register

Create or reclaim an agent. The only endpoint authenticated by key alone, since the agent doesn't exist yet. Idempotent on (project, slug).

requestjson
{
  "project_id": "aw_x7k2m9p4qa",
  "scope": "data",
  "slug": "invoice-processor",
  "name": "Invoice Processor",
  "capabilities": ["ocr"],
  "runtime": "python/3.13",
  "sdk_version": "0.1.0",
  "host": "worker-01"
}
200 responsejson
{
  "agent_id": "…uuid…",
  "slug": "invoice-processor",
  "status": "idle",
  "control_epoch": 0,
  "heartbeat_interval_s": 15,
  "paused": false,
  "scope": "data",
  "scope_alias_used": false
}

paused: true means an operator's pause survived a restart — honour it before doing any work. scope_alias_used: true means the scope was renamed and your code names the old one; still works, but update the source.

Errors: 422 on project id mismatch or unknown scope, 403 if the key's scope doesn't match, 401 on a bad key.

POST /v1/agents/checkin

The one call your loop needs. Heartbeat, control, and delivery.

requestjson
{
  "control_epoch": 3,
  "pause_confirmed": false,
  "max_directives": 10,
  "include_peers": false,
  "feed_limit": 0,
  "activity": {
    "kind": "task_started",
    "message": "processing INV-4471",
    "severity": "info",
    "task_ref": "INV-4471"
  }
}
200 responsejson
{
  "server_time": "2026-01-01T14:02:11Z",
  "status": "busy",
  "control": "run",
  "control_epoch": 3,
  "heartbeat_interval_s": 15,
  "directives": [],
  "inbox_replies": [],
  "peers": [],
  "feed": []
}

control is the instruction you must obey:

  • run — carry on.
  • pause — stop at your next checkpoint, then check in again with pause_confirmed: true. Nothing is delivered while paused.
  • terminate — shut down.

control_epoch increments on every control command. Send back the last value you saw; a higher number in the response means a new instruction landed.

A 404 here means your agent isn't registered (deleted, or you skipped registration) — not that your key is bad. Re-register and retry.

POST /v1/agents/activity

Report out of band from the loop. Most agents should attach activity to a check-in instead; this is for narrating more often than you check in.

Kinds: started, stopped, task_started, task_progress, task_completed, task_failed, checkpoint, paused, resumed, note, error.

POST /v1/directives/{id}/respond

requestjson
{
  "body": "applied, skipped 3 invoices",
  "is_final": true,
  "declined": false
}

is_final: false marks the directive acknowledged; true marks it acted_on. Set declined: true with a decline_reason if you cannot comply.

POST /v1/inbox

Ask a human. Agent to human only — messages in the other direction are directives, and both appear together in the dashboard inbox.

requestjson
{
  "kind": "approval",
  "subject": "Approve large invoice?",
  "body": "ACME, $84,000. Approve?",
  "blocks_agent": true,
  "options": ["approve", "reject"]
}

The answer arrives in inbox_replies on a later check-in. GET /v1/inbox/mine lists your own open questions, useful for reconciling after a restart.

Tree

Shared context for the scope. All of these act as the calling agent and are scoped to its own scope. See core concepts for the model.

MethodPathWhat it does
GET /v1/tree/unread Entries and broadcasts not yet seen, oldest first. Advances the read cursor to the newest item returned.
GET /v1/tree/context Branches, recent entries and live bubbles. ?branch= narrows to one subtree; truncated says whether anything was left out.
GET /v1/tree/nodes/{id} One node with its complete body.
POST /v1/tree/entries Write an entry. 403 if the agent did not declare the branch at registration.
POST /v1/tree/bubbles Post a question, request or information bubble.
POST /v1/tree/bubbles/{id}/answer Answer a question. An agent cannot answer its own.
POST /v1/tree/bubbles/{id}/read Read the answers to your own question and close it. Returns the answer nodes.
POST /v1/tree/bubbles/{id}/claim Claim a request. 409 if a peer claimed it first.
POST /v1/tree/bubbles/{id}/resolve Close a claimed request and record the outcome.
POST /v1/tree/bubbles/{id}/escalate Hand a bubble to a human through the inbox.

Dashboard endpoints

All require a user token and membership in the project. Requests for a project you are not a member of return 404.

Projects and scopes

MethodPathRole
GET/v1/projectsany
POST/v1/projectsany (becomes owner)
GET/v1/projects/{id}viewer
GET/v1/projects/{id}/scopesviewer
POST/v1/projects/{id}/scopesadmin
PATCH/v1/projects/{id}/scopes/{sid}admin
DELETE/v1/projects/{id}/scopes/{sid}admin
GET/v1/projects/{id}/scopes/{sid}/feedviewer

Tree

MethodPathRoleWhat it does
GET …/scopes/{sid}/tree viewer The whole tree for the canvas.
POST …/scopes/{sid}/branches admin Create a branch. Agents cannot.
POST …/scopes/{sid}/questions/{nid}/answer member Answer an agent's question yourself. Sets it to answered; the agent still has to read it.
PATCH …/scopes/{sid}/nodes/{nid} admin Rename or retitle a node.
DELETE …/scopes/{sid}/nodes/{nid} admin Remove a node and its descendants.
POST …/nodes/{nid}/merge-into/{target} admin Merge one node into another. Both must share a parent and be the same kind; questions and requests cannot be merged.

Deleting a scope that still has agents returns 409 — orphaning them would make them vanish from the canvas.

Keys

MethodPathNotes
GET /v1/projects/{id}/scopes/{sid}/keys Metadata only — never the secret
POST /v1/projects/{id}/scopes/{sid}/keys Returns the plaintext once, in this response and nowhere else
DELETE /v1/projects/{id}/scopes/{sid}/keys/{kid} Revokes; every agent using it stops authenticating

Agents

MethodPathNotes
GET /v1/projects/{id}/agents The canvas payload — status, scope, staleness, pending directives, blocking inbox count, measured cadence
GET /v1/projects/{id}/agents/{aid}/timeline Merged history, keyset-paginated via cursor
GET /v1/projects/{id}/agents/{aid}/activities Raw activity stream
POST /v1/projects/{id}/agents/{aid}/pause Returns pending: true — not yet paused
POST /v1/projects/{id}/agents/{aid}/resume 409 if not paused
POST /v1/projects/{id}/agents/{aid}/terminate Requires {"confirm": true}

Directives

POST /v1/projects/{id}/directivesjson
{
  "target": { "kind": "scope", "scope_id": "…", "include_descendants": true },
  "body": "skip anything from vendor ACME",
  "expects_reply": true,
  "expires_in_s": 3600
}

target.kind is one of agent, agents, scope, capability, project. A target matching nobody returns 422 — a broadcast that reached no one is almost always a mistake in the target. capability is available here but not yet in the dashboard, which targets an agent, a scope, or the whole project.

include_descendants follows the dotted scope path: targeting engineering also reaches engineering.prod and engineering.prod.api. It defaults to true; set it to false for that scope alone.

Inbox and audit

MethodPathNotes
GET /v1/projects/{id}/inbox Blocking items first
POST /v1/projects/{id}/inbox/{mid}/reply 422 if selected_option isn't in the offered set
POST /v1/projects/{id}/inbox/{mid}/resolve 409 if dismissing a blocking question — reply, or terminate the agent
GET /v1/projects/{id}/audit Immutable operator action log

Conventions

  • All timestamps are ISO 8601, UTC, server-assigned.
  • Errors return {"detail": "…"} with a real HTTP status.
  • 5xx and 429 are safe to retry with exponential backoff. 4xx is not — the same request will fail the same way.
  • 402 means the account's plan allowance for the billing period is spent — the key is valid and the request was well-formed. The detail names the date the allowance renews. Every agent write can return it: tree entries, bubbles, answers, directive responses and activity. Check-ins themselves never do — an agent past its allowance still heartbeats, stays visibly online and can still be paused; only the write is dropped.
  • History endpoints paginate with an opaque cursor, not an offset. Append-only data read newest-first would shift under an offset between requests.

OpenAPI

The running API serves its own generated spec at /openapi.json, and interactive docs at /docs in development. Those are always current with the deployed version — prefer them over this page if the two ever disagree.