HTTP API
The loopback API: authentication, the Host and Origin rules, every operation route, the event stream and the agent job routes, from the OpenAPI document.
The local API core serves on 127.0.0.1. The CLI and the web UI use it, and the OpenAPI document
this page is built from is served at GET /v1/openapi.json.
How it is kept safe.
Reaching it
- Where.
127.0.0.1and the port inruntime.json([api] port, 0 for a free one). - Auth.
Authorization: Bearer <token>, the token inapi_tokenin the config directory. OnlyGET /v1/healthis open, and it returns{"status": "ok"}. Agent routes take an agent token (oaken_agent_...) instead, and no other route accepts one. - Host. Must be
127.0.0.1:<port>orlocalhost:<port>, else400. - Origin. A
POSTfrom another origin, or withSec-Fetch-Site: cross-site, is403. There are no CORS headers unless one development origin is configured. - Content type. A
POSTmust beapplication/json, else415. - Limits. Body size (
413), request rate on mutating calls (429), concurrent calls (429). - Caller class.
X-Oaken-Client: cli(the default),uiormcp. It can lower privilege and never raise it:mcpmakes the call an agent's, so it cannot confirm by saying so. Anything else is400. - Errors.
{"code": "...", "message": "..."}. Messages never contain mail text.
The MCP server is not in this document. When [api] mcp = true it is a separate door on the same
listener, /mcp, with its own token and limits, and with the swarm on, /mcp/agent for agent
tokens. See Connect an AI client.
Routes
| Method | Path | Auth | What it does |
|---|---|---|---|
| GET | /v1/health |
none | Liveness. Needs no token and reveals nothing else. |
| GET | /v1/operations |
API token | The operations this caller may call, with their schemas. |
| GET | /v1/openapi.json |
API token | This document. |
| GET | /v1/events |
API token | Server-Sent Events: what core does, as it happens. |
| POST | /v1/agent/jobs/claim |
agent token | Lease the best runnable job of the agent's kinds, or none. |
| POST | /v1/agent/jobs/{job_id}/heartbeat |
agent token | Extend the lease; says whether the job was cancelled. |
| POST | /v1/agent/jobs/{job_id}/complete |
agent token | Finish the job with a result that matches its kind's schema. |
| POST | /v1/agent/jobs/{job_id}/fail |
agent token | Give the job back with an error; core decides whether to retry. |
| POST | /v1/operations/{name} |
API token | Call one operation. 46 exist. |
Calling an operation
POST /v1/operations/{name} takes input and answers ``request_id, status, output?.
status is ok when the operation ran, or confirmation_required for a mutation that was not
confirmed: nothing has changed, and proposal says what would, with a confirmation_token.
A person at the CLI or UI may send confirm: true. An agent calls again with the token.
Status codes: 422 input or result does not validate, 403 not permitted, 404 unknown,
409 conflict, 429 quota, 502 a plugin or provider failed.
| Operation | Permission | MCP | Route |
|---|---|---|---|
ask |
read | off until turned on | POST /v1/operations/ask |
authorization_status |
read | never | POST /v1/operations/authorization_status |
check_account |
read | never | POST /v1/operations/check_account |
create_account |
mutate | never | POST /v1/operations/create_account |
create_agent |
mutate | never | POST /v1/operations/create_agent |
enrich_message |
mutate | never | POST /v1/operations/enrich_message |
get_account |
read | never | POST /v1/operations/get_account |
get_enrichment |
read | on by default | POST /v1/operations/get_enrichment |
get_message |
read | on by default | POST /v1/operations/get_message |
get_message_rules |
read | on by default | POST /v1/operations/get_message_rules |
get_raw |
read | never | POST /v1/operations/get_raw |
list_accounts |
read | on by default | POST /v1/operations/list_accounts |
list_agents |
read | never | POST /v1/operations/list_agents |
list_audit |
read | never | POST /v1/operations/list_audit |
list_labels |
read | on by default | POST /v1/operations/list_labels |
list_messages |
read | on by default | POST /v1/operations/list_messages |
list_operations |
read | never | POST /v1/operations/list_operations |
list_plugins |
read | on by default | POST /v1/operations/list_plugins |
list_schedules |
read | on by default | POST /v1/operations/list_schedules |
mark_read |
mutate | on by default | POST /v1/operations/mark_read |
mark_unread |
mutate | on by default | POST /v1/operations/mark_unread |
message_dismissed |
read | never | POST /v1/operations/message_dismissed |
message_presented |
mutate | never | POST /v1/operations/message_presented |
pause_schedule |
mutate | never | POST /v1/operations/pause_schedule |
pipeline_status |
read | on by default | POST /v1/operations/pipeline_status |
plugin_health |
read | on by default | POST /v1/operations/plugin_health |
rebuild_index |
mutate | never | POST /v1/operations/rebuild_index |
remove_schedule |
mutate | never | POST /v1/operations/remove_schedule |
reprocess |
mutate | never | POST /v1/operations/reprocess |
resume_schedule |
mutate | never | POST /v1/operations/resume_schedule |
revoke_agent |
mutate | never | POST /v1/operations/revoke_agent |
search |
read | on by default | POST /v1/operations/search |
search_keyword |
read | on by default | POST /v1/operations/search_keyword |
set_account_config |
mutate | never | POST /v1/operations/set_account_config |
set_account_password |
mutate | never | POST /v1/operations/set_account_password |
set_operation_exposure |
mutate | never | POST /v1/operations/set_operation_exposure |
set_schedule |
mutate | never | POST /v1/operations/set_schedule |
sieve_check |
read | never | POST /v1/operations/sieve_check |
sieve_list |
read | never | POST /v1/operations/sieve_list |
sieve_test |
read | never | POST /v1/operations/sieve_test |
start_authorization |
mutate | never | POST /v1/operations/start_authorization |
status |
read | on by default | POST /v1/operations/status |
summarize_message |
mutate | on by default | POST /v1/operations/summarize_message |
sync_now |
mutate | on by default | POST /v1/operations/sync_now |
undo |
mutate | on by default | POST /v1/operations/undo |
usage_summary |
read | on by default | POST /v1/operations/usage_summary |
Each operation's input and output are in Reference: Operations, and as JSON Schema in the OpenAPI document and in GET /v1/operations.
Events
GET /v1/events is server-sent events. Each frame has an id (a number), an event (the type
below) and data (one line of JSON: ids and counts, never message text). Send Last-Event-ID to
resume from the in-memory ring. If that id is older than the ring, or from before core restarted, a
gap event comes first, meaning fetch state again. A quiet stream sends : heartbeat comments.
| Event type | Meaning | Fields |
|---|---|---|
AttachmentQuarantined |
Flagged attachments of a message were written to the quarantine folder (never opened). | ts, event_id, account_id, message_id, count |
IndexRebuildProgress |
A rebuild of index.db is under way. total is the live messages in scope when it started; done counts the ones looked at (changed or not). finished is the last event. |
ts, event_id, account_id, done, total, indexed, failed, finished |
JobStateChanged |
Job state changed. | ts, event_id, job_id, kind, status, account_id |
LargeMessageStored |
A message was too big for raw.db and went to the LFS folder, whole. oversize is set when it is also past storage.max_message_bytes (worth a warning in a UI). |
ts, event_id, account_id, message_id, size_bytes, oversize |
MessageEmbedded |
Message embedded. | ts, event_id, account_id, message_id, model |
MessageEnriched |
Message enriched. | ts, event_id, account_id, message_id, enricher |
MessageFiltered |
Sieve ran on a message and its organization attributes were written. | ts, event_id, account_id, message_id, disposition |
MessageIndexed |
Message indexed. | ts, event_id, account_id, message_id |
MessageParsed |
Message parsed. | ts, event_id, account_id, message_id |
MessageReadChanged |
Message read changed. | ts, event_id, account_id, message_id, read, reason, audit_id, local_only, error |
MessageStored |
is_new: first time seen. changed: an existing message got different bytes (or came back after a delete), so anything derived from it is stale. A no-op put publishes nothing. |
ts, event_id, account_id, message_id, is_new, changed |
PluginEvent |
Base for events a plugin defines itself: subclass it, add plain-data fields, and publish with ctx.publish(...). plugin names the sender (the host fills it in). |
ts, event_id, plugin |
PluginHealthChanged |
Plugin health changed. | ts, event_id, plugin, healthy, detail |
SyncFinished |
Sync finished. | ts, event_id, account_id, new_messages, error, quiet, folder |
SyncPaused |
An account's scheduled sync stopped until the user acts (for example, the password was refused). quiet is as on SyncFinished. |
ts, event_id, account_id, reason, quiet |
SyncProgress |
Sync progress. | ts, event_id, account_id, done, total |
SyncScheduleChanged |
A sync schedule was added, edited, paused, resumed or removed (change says which). |
ts, event_id, account_id, folder, change |
SyncStarted |
Sync started. | ts, event_id, account_id |
gap |
That id is no longer available; fetch state again. | first_available |
Agent job routes
Authenticated by an agent token, never by the API token. See the swarm.
POST /v1/agent/jobs/claim
Lease the best runnable job of the agent's kinds, or none.
- Request:
kinds? - Response:
job
POST /v1/agent/jobs/{job_id}/heartbeat
Extend the lease; says whether the job was cancelled.
- Request: none
- Response:
ok?,cancel_requested
POST /v1/agent/jobs/{job_id}/complete
Finish the job with a result that matches its kind's schema.
- Request:
result?,produced_by? - Response:
ok?
POST /v1/agent/jobs/{job_id}/fail
Give the job back with an error; core decides whether to retry.
- Request:
error,permanent? - Response:
status