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.
| Caller | Header | Endpoints |
|---|---|---|
| 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).
{
"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"
}
{
"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.
{
"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"
}
}
{
"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 withpause_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
{
"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.
{
"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.
| Method | Path | What 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
| Method | Path | Role |
|---|---|---|
| GET | /v1/projects | any |
| POST | /v1/projects | any (becomes owner) |
| GET | /v1/projects/{id} | viewer |
| GET | /v1/projects/{id}/scopes | viewer |
| POST | /v1/projects/{id}/scopes | admin |
| PATCH | /v1/projects/{id}/scopes/{sid} | admin |
| DELETE | /v1/projects/{id}/scopes/{sid} | admin |
| GET | /v1/projects/{id}/scopes/{sid}/feed | viewer |
Tree
| Method | Path | Role | What 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
| Method | Path | Notes |
|---|---|---|
| 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
| Method | Path | Notes |
|---|---|---|
| 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
{
"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
| Method | Path | Notes |
|---|---|---|
| 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. -
5xxand429are safe to retry with exponential backoff.4xxis not — the same request will fail the same way. -
402means the account's plan allowance for the billing period is spent — the key is valid and the request was well-formed. Thedetailnames 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.