CLI and API reference
Last updated October 5, 2026
On this page
Draft for review
OrchKernel has two ways in besides the web workspace: the ok command line,
and the HTTP API the workspace itself uses. This page lists both in one
place. Each line points to the guide page that explains the command or route
in full, so use it to find your way, then read the page it names.
The examples use the demo company from the Quickstart:
ada the admin, maya the support lead, alice a sales rep, erin the engineering
lead, pat the product lead, fiona the founder, and sam, tom and eli. Each
person's sign-in token is printed when ok demo serve starts; those
tokens are valid for 7 days.
The examples use 127.0.0.1:8080, the address ok demo serve listens on
unless you pass --bind. Change the port if you started it elsewhere.
The command line#
Where a command works#
Most ok commands do not talk to a server. They open the state file (the
SQLite file that holds the whole company; the demo's is
orchkernel-demo/state.db), do their work and write the file back when they
finish. A running server keeps the company in memory and
writes the same file after each change, so it never sees what a command
wrote, and its next write replaces it.
| Command | Works on | Safe while the server runs |
|---|---|---|
ok serve, ok demo serve |
Starts the server | Not applicable |
ok backup, ok audit ..., ok doctor |
Reads the file without writing it, or (ok doctor) only the model settings |
Yes |
ok policy ... --server <url> |
The running server, with your token in OK_TOKEN |
Yes |
ok tenant ..., ok operator ... |
A tenancy root's files, or its operator listener with --server |
See Hosting several organizations |
| Everything else | The state file | No. Stop the server first, or use the API |
This holds even for commands that only read, such as ok log or
ok inbox: they write the file back too.
To run a command on the demo, work in the folder where you ran ok demo
(the one that holds orchkernel-demo):
- Stop
ok demo servewith Ctrl+C. The server writes the file after every change, so nothing is lost. - Run the command with the demo's state file and the person you act as:
ok --state orchkernel-demo/state.db --as maya brain query tickets. You should see the demo's five support tickets as JSON, the first one T-1042, "Refund for a double charge on the September invoice". - Start the demo again with
ok demo serve. It prints new sign-in tokens and revokes the ones it printed last time. Tokens you issued yourself keep working.
In the demo, ok --state orchkernel-demo/state.db --as ada kill actor:eli
run with the server stopped printed "actor:eli engaged", and once the
server started, GET /api/policy showed the switch on Eli held by an admin.
The same command for Tom run while the server was up also printed
"actor:tom engaged" and wrote a kill_switch event to the log, but the
server never applied it, and its next write removed it from the file. The
event stays in the log, so the log records a switch that never took effect.
See Known problems.
Global flags#
Every command takes these, except -V, which only ok itself takes.
| Flag | Environment variable | Default | What it does |
|---|---|---|---|
--state <path> |
OK_STATE |
./.orchkernel/state.db |
The state file. ok demo ignores it and uses <dir>/state.db. |
--as <id> |
OK_AS |
your login name ($USER) |
The person or agent you act as. Every check is made for this actor. |
--tenant <name> |
OK_TENANT |
none | Act on one tenant of a tenancy root. With --server, sent as the x-ok-tenant header. |
--tenants-dir <dir> |
OK_TENANTS_DIR |
none | The tenancy root. |
--dev-echo-tools |
OK_DEV_ECHO_TOOLS |
off | Answer every tool call with an echo and ignore mcp.yaml. Development only. |
-h, --help |
Help for any command or subcommand: ok brain query --help. |
||
-V, --version |
Prints the version (ok 0.1.0). |
Two things catch people out:
- Without
--state, a command in a folder with no.orchkernel/state.dbcreates a new, empty company there and works on that, with no warning. - An
--asthat names nobody is refused by commands that check what you may do, such asok brain queryandok kill("Error: unknown actor nobody"). Others do not check:ok inbox,ok tasksandok threadsprint nothing and succeed, andok logprints the log. Without--as, the command acts as your computer login name, which the demo does not know either.
Environment variables#
The settings a server reads are listed in full in Self-hosting. These are the ones the command line itself reads.
| Variable | Used by | What it does |
|---|---|---|
OK_STATE, OK_AS, OK_TENANT, OK_TENANTS_DIR, OK_DEV_ECHO_TOOLS |
Every command | As the global flags above. |
OK_SERVER |
ok policy |
The server URL, instead of --server. It must be https, or http to a loopback address such as 127.0.0.1. |
OK_TOKEN |
ok policy with a server |
Your API token. It is never taken on the command line. |
OK_OPERATOR_TOKEN |
ok tenant and ok operator with --server |
An operator token. |
OK_BIND |
ok serve |
Where it listens. ok demo serve ignores it and takes --bind. |
OK_SECRETS_KEY |
ok connection, ok secrets rotate, the server |
The key that seals connection secrets. See Connections. |
OK_DEV_AUTH |
ok connection, the server |
Lets connections use plain http to a loopback address. Development only. |
ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY or GOOGLE_API_KEY, OLLAMA_HOST, OK_LLM_CONFIG |
ok ask, ok doctor, the server |
Model providers. See Models. |
OK_LLM_CHECK |
ok serve, ok demo serve |
0 skips the key check at start. |
Commands#
One line per command; run it with --help for its flags. The last column is
the page that explains it.
Getting started and running
| Command | What it does | Page |
|---|---|---|
ok demo [--dir D] [--force] |
Creates the demo company in ./orchkernel-demo |
Quickstart |
ok demo serve [--dir D] [--bind A] [--dev-auth] |
Starts the demo's local fakes and server, and prints a token per person | Quickstart |
ok init [--admin id] [--blueprint b] |
Creates a state file and its first admin, optionally a whole company | Company setup |
ok serve |
Runs the API and the web workspace | Self-hosting |
ok backup <path> |
Copies the state file safely, even while the server runs | Self-hosting |
ok doctor |
Checks each model key; exits 1 when no provider is set ("FAIL no model provider is configured") | Models |
ok schemas |
Prints the JSON Schema of every definition type (agents, skills, packs) | Packs and skills |
Company, people and tokens
| Command | What it does | Page |
|---|---|---|
ok blueprint list, ok blueprint install <id or file> |
Lists and installs blueprints | Company setup |
ok actor list |
Everyone in the directory | Company setup |
ok actor add-human <id> --role <r>, ok actor add-team <team:id> |
Adds a person or a team | Company setup |
ok actor offboard <id> |
Offboards a person or agent | Company setup |
ok actor hire <file>, ok actor trust <id> <tier> |
Hires an agent from YAML; sets its trust tier | Agents |
ok directory find [--skills a,b] [--humans], ok directory cards |
Who can do something; every agent's card | Company setup, Agents |
ok token issue --for <id> [--ttl] [--label] [--scope] |
Issues an API token, printed once; valid 30 days unless --ttl says otherwise (7d) |
Company setup |
ok token list, ok token revoke, ok token scope |
Lists, revokes or narrows tokens | Company setup |
Agents and work
| Command | What it does | Page |
|---|---|---|
ok ask --agent <id> "<text>" [--plan file] |
Asks an agent in your own conversation. Needs a model or --plan: the demo's stub planner answers only in the workspace, so without a model key the run fails with "no model provider is configured" |
Asking agents |
ok tasks [--archived], ok runs [--archived] |
Tasks and runs, live or archived | Asking agents |
ok tick [--now t] |
Advances the clock once: triggers, SLAs, stalls, pending runs | Asking agents |
ok webhook <source> [--payload json] |
Delivers an event to webhook triggers, unsigned | Modules |
ok inbox [--all] |
Your inbox | Inbox and approvals |
ok approve <id> [--reject] [--note] |
Decides an approval | Inbox and approvals |
ok answer <run> "<text>" |
Answers the question a run waits on | Inbox and approvals |
ok threads [--show id] |
Lists threads, or one thread's transcript | Threads |
ok agent library, ok agent fork <id> --slug <s> |
The agent library; forks a module's agent | Agents |
ok playbook show <id> |
An agent's or skill's playbook | Agents |
ok playbook propose <id> <file> --reason <r> |
Proposes a playbook change | Changes and evals |
ok changes list, review, canary, promote |
The change pipeline | Changes and evals |
ok delegate --to <id> [--scope] [--authority] [--budget-cents] [--tools] |
Delegates authority to someone | Delegations |
Data
| Command | What it does | Page |
|---|---|---|
ok brain collections, describe <c>, catalog <phrase> |
Collections, one collection's fields, collections matching a phrase | The brain |
ok brain query <c>, upsert <c> --fields json, history, rollback, flag, flags |
Records and their versions | The brain |
ok brain recall [topics], quarantine, accept <id>, reject-knowledge <id> |
Knowledge entries | Knowledge and decisions |
ok schema proposals, apply <id>, reject <id>, steward |
Schema proposals | The brain |
ok search "<text>" [--collections] [--kinds] [--limit] |
Keyword search over what you may read | The brain |
ok context sources, run, optin, optout, forget |
Context capture | Context capture |
Governance and audit
| Command | What it does | Page |
|---|---|---|
ok policy export, check, diff, apply, source |
Policy as code | Policy |
ok policy explain [op] [target] --actor <id> |
What the gate would decide, step by step | How the gate decides |
ok kill <scope> [--off] |
Engages or releases a kill switch: global, actor:<id>, tool:<id>, team:<id> |
Policy |
ok log [--type t] [--limit n] |
The newest events of the whole log | Events and audit |
ok replay --run <id> (or --task, --thread) |
One run, task or thread from the log | Events and audit |
ok audit verify, head, checkpoints |
Checks the tamper-evident chain | Events and audit |
Modules, connections and outside agents
| Command | What it does | Page |
|---|---|---|
ok catalog list, upload <file>, retire <id> |
The modules catalog | Modules |
ok module list, show, enable, set, pause, resume, reconcile, adopt |
Installed modules | Modules |
ok pack list, ok pack install <dir> |
Packs installed directly | Packs and skills |
ok connection add, update, set-secret, list, test, remove |
Connections to outside systems | Connections |
ok secrets rotate --new-key-env V |
Re-seals connection secrets under a new key, server stopped | Connections |
ok gateway agent add, list, show, set |
Registers and changes outside agents | Outside agents |
ok gateway token issue <agent> |
Issues an outside agent's token | Outside agents |
ok gateway actions --agent, work-sessions --agent |
An outside agent's actions and work sessions | Outside agents |
ok gateway callback rotate, test |
An outside agent's signed callbacks | Outside agents |
Several organizations on one host
| Command | What it does | Page |
|---|---|---|
ok tenant create, adopt, list, show, set |
Tenants of a tenancy root | Hosting several organizations |
ok tenant suspend, resume, delete, undelete |
A tenant's lifecycle | Hosting several organizations |
ok tenant admin-token, rotate-key |
Break-glass token; the tenant's secrets key | Hosting several organizations |
ok tenant backup, backups, restore, upgrade, check, registry rebuild |
Backups, upgrades and health | Hosting several organizations |
ok operator token issue, list, revoke; ok operator rotate-key |
Operator tokens and key | Hosting several organizations |
The HTTP API#
This part is for people writing scripts or tools against a running server. Everything the web workspace does, it does through this API.
Base path#
Every route is under /api on the server's own address, for example
http://127.0.0.1:8080/api/me. Two exceptions sit at the root: the MCP
endpoint POST /mcp for outside agents, and the operator API on its own
listener when the server hosts several organizations. Any other path that is
not under /api serves the web workspace. An /api path no route serves
answers 404 not_found, "no such API route".
Bodies are JSON and need content-type: application/json. GET /api/health
needs no token and answers { "ok": true, "dev_auth": false }.
Tokens#
Send a token in every request: Authorization: Bearer <token>. There are
no passwords and no cookies.
| Token | Looks like | Reaches |
|---|---|---|
| A person's token | orchk_... |
Every /api route, with that person's rights |
| A scoped token | orchk_..., issued with scopes |
Only the routes its scopes allow (below) |
| An outside agent's token | orchk_ag_... |
Only the agent-facing gateway routes and /mcp. Anywhere else: 403 agent_token_scope |
| An operator token | orchk_op_... |
Only the operator API. A tenant's token is 401 there |
To get a token for a script from the workspace (alice, in the demo):
- Sign in as Alice and open your profile: choose your name, Alice Chen, at the bottom of the sidebar. On a phone, open the menu (the button at the top left) first. The page is titled Profile.
- Under Sessions & tokens, choose New token.
- In New sign-in token, type a Label ("Report script"), pick Valid for (1 day, 7 days, 30 days or 90 days; 30 days is preselected), then choose Issue token. The sheet Your new token shows it once, under "Copy it now: it is shown once. It expires ..." with a Copy button.
- Choose I have copied it. The token now has its own row under Sessions & tokens, with its label, its dates and Revoke.
- Try it:
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/mereturns Alice's directory entry with"admin": false.
An admin issues tokens for others from People: each person has an Issue token button. Another person who tries over the API gets 403 "policy denied token.issue: only admins issue tokens for others". Tokens are valid 30 days unless you choose otherwise; the server keeps only a hash, so a lost token cannot be shown again. The full table of ways to issue and revoke is in Self-hosting.
Scopes. A token can be limited to some routes. The workspace cannot set
scopes; use POST /api/tokens with "scopes", or ok token issue --scope
with the server stopped. A scope can later be narrowed but never widened.
On the profile page a scoped token's row says "limited to read" (or the
scopes it has).
| Scope | Reaches |
|---|---|
read |
Every GET route, as the holder sees it. Not POST reads such as a collection query. |
changes |
POST /changes, and the holder's own proposals: GET /changes, GET /changes/:id/diff, POST /changes/:id/reject. |
policy |
POST /policy/check, POST /policy/diff, POST /policy/proposals, GET /policy/applied, and the holder's own proposals as above. Not /policy/explain. |
| none | Everything the holder may do. |
No scope reaches the audit routes. A route outside a token's scopes answers
403 token_scope, "this token's scopes do not reach
/brain/collections/:name/query".
Failed tokens. A missing or bad token is 401. From the 61st failed
credential in a minute, the address gets 429 rate_limited, "too many failed authentications from
this address", with a Retry-After header for the rest of the minute. A
valid token from the same address still works, and a request with no token
at all is not counted and still gets 401.
Errors and status codes#
An error is JSON with a short code and a sentence:
{ "error": "denied", "message": "brain: access denied: ada may not read tickets" }
Some errors add a field: problems (a list of what is wrong) on 422
invalid, approval (its id) on 202 needs_approval, and evals (the case
names) on evals_failing and evals_not_run.
| Status | Codes | When |
|---|---|---|
| 200 | Done. | |
| 202 | needs_approval |
The action waits for a person. Body: { "error": "needs_approval", "approval": "<id>", ... }. Erin offboarding Eli gets this. Webhook deliveries and loop runs also answer 202, with their own bodies. |
| 400 | bad_request |
A bad query value, such as a cursor the server did not issue ("invalid cursor"). |
| 401 | unauthorized |
"missing bearer token", "invalid token", "not a bearer token", or a webhook without a valid signature. |
| 403 | denied, not_approver, token_scope, agent_token_scope, not_external_agent, not_owner, ... |
The policy or a rule refuses you. |
| 404 | not_found |
It does not exist, or you may not see it. The two read the same: "thread nope not found", "unknown actor ghost". |
| 409 | conflict and named codes (evals_failing, last_approver, module_paused, ...) |
The thing is in the wrong state for this. |
| 422 | invalid and named codes |
The request is well formed but not acceptable, such as GET /api/traces?since=yesterday: "since "yesterday" is neither an RFC 3339 time nor a YYYY-MM-DD date". |
| 429 | rate_limited |
Too many failed credentials, or a rate limit in the policy. |
| 503 | audit_unavailable and others |
The server cannot do it now. |
Not every error has this shape. A body that is not JSON (400), a JSON body
missing a required field (422), a missing content-type (415) and a wrong
method (405) answer in plain text or with an empty body, for example
"Failed to deserialize the JSON body into the target type: missing field
goal". See Known problems.
The named codes each route can return are listed in the For developers section of the page that covers it.
Paging#
Long lists come a page at a time. Ask with ?limit= (default 200, at most
1000). When there is more, the answer carries an x-next-cursor header;
send its value back as ?cursor= for the next page. The last page has no
header.
curl -si -H "Authorization: Bearer $ADA" "http://127.0.0.1:8080/api/delegations?limit=2"returns two delegations and anx-next-cursor: MDky...header.- Repeat with
&cursor=MDky.... You get the next two, and a new cursor. - Stop when a page comes back without the header.
A cursor stays valid when items are added or removed in between, with no
repeats. Paged lists: /threads, /inbox, /approvals, /tasks, /runs,
/traces, /knowledge, /decisions, /changes, /delegations,
/views, /modules, /catalog, /connections and the gateway's action
lists. /traces has its own limit (default 50, at most 200).
GET /events is not paged this way. It takes since (an event number) and
limit, and without either returns the newest 200 events with last_seq,
the newest event number in the log. The workspace polls
/events?since=<last_seq> every two seconds.
Reads of a collection kept in another system can stop early (at 5,000 rows
or after 60 seconds). The answer then carries x-ok-partial: true and names
the vendor's adapter in x-ok-partial-from. GET /traces also sends
x-ok-partial: true when it cut the archived runs short. The demo has no
collection kept in another system, so this was not tried there. See
Connections.
The live event stream#
GET /api/events/stream sends new events as server-sent events as they are
written, only those you may see. Each message has the event number as id,
the event type as event, and the event as JSON in data:
id: 422
event: knowledge_proposed
data: {"seq":422,"actor":"maya","event":{"type":"knowledge_proposed",...}}
- It starts at the newest event. Add
since=<n>to start after eventn. - It takes the same filters as
GET /events:type(comma-separated),run,thread,task,actor. - A browser's
EventSourcecannot set headers, so this route alone also accepts the token as?token=. On any other route a token in the address is ignored and the request gets 401.
To watch Maya's new knowledge entries:
curl -N "http://127.0.0.1:8080/api/events/stream?token=$MAYA&type=knowledge_proposed",
then add an entry as Maya. It arrives within a second.
When a connection drops, reconnect with since= set to the last id you
received. The Last-Event-ID header a browser sends on reconnect is ignored,
so events written while you were away are missed unless you pass since.
Route map#
All paths below are under /api. Each area's page has the bodies, answers
and error codes in its For developers section.
| Area | Routes | Page |
|---|---|---|
| You and the company | GET /me, GET/PUT /company, GET /health |
Company setup, Workspace tour |
| People and agents | GET/POST /actors, GET /actors/:id, POST /actors/:id/offboard, POST /actors/:id/trust, GET /directory/find, GET /directory/cards |
Company setup, Agents |
| Blueprints | GET/POST /blueprints |
Company setup |
| Tokens | GET/POST /tokens, DELETE /tokens/:actor, DELETE/PATCH /tokens/id/:id |
Company setup |
| Asking and work | GET /me/agents, POST /ask, GET/POST /tasks, GET /tasks/:id, POST /tasks/:id/assign, /run, /complete, /refs, GET /runs, GET /runs/:id, POST /runs/:id/answer, GET /traces |
Asking agents |
| Threads | GET/POST /threads, GET /threads/:id, POST /threads/:id/messages, /ask, /artifacts, /decide, /close, /participants, DELETE /threads/:id/participants/:actor, GET /thread-templates, PUT /artifacts/:id |
Threads |
| Inbox and approvals | GET /inbox, POST /inbox/:id/read, GET /approvals, GET/POST /approvals/:id, GET /approvals/:id/explain |
Inbox and approvals |
| Agents and playbooks | GET /library/agents, POST /library/agents/:id/fork, GET /agents/:id/playbook, POST /agents/:id/playbook/propose, /convert, GET /agents/:id/scorecard |
Agents |
| Changes | GET/POST /changes, POST /changes/:id/review, /evals, /canary, /promote, /reject, GET /changes/:id/diff |
Changes and evals |
| The brain | /brain/collections... (query, aggregate, records, history, rollback, flag, semantics), GET /brain/catalog, GET /brain/flags, GET /refs/:collection/:id, GET /search, /schema/proposals..., POST /schema/steward, GET /views, GET /views/:id, POST /views/preview |
The brain |
| Knowledge and decisions | GET/POST /knowledge, GET /knowledge/quarantine, POST /knowledge/:id/accept, /reject, GET /decisions |
Knowledge and decisions |
| Context capture | /context/sources..., /context/optins..., /context/identities/:actor, POST /context/forget, GET /context/inbox, GET /context/functions, /context/flags..., GET /context/items/:id/links |
Context capture |
| Delegations | GET/POST /delegations, DELETE /delegations/:id |
Delegations |
| Policy | GET /policy, GET /policy/export, POST /policy/check, /diff, /proposals, GET /policy/applied, /drift, PUT /policy/source, POST /kill |
Policy |
| The gate | POST /policy/explain, GET /approvals/:id/explain |
How the gate decides |
| Events and audit | GET /events, GET /events/stream, GET /audit/head, GET /audit/checkpoints, POST /audit/verify |
Events and audit |
| Models | GET /llm/models |
Models |
| Modules | GET /catalog, POST /catalog/uploads, /drafts, DELETE /catalog/:id, /catalog/proposals/:id/..., GET /modules, /modules/:pack... (defaults, enable, pause, resume, reconcile, adopt, webhook secrets), POST /tick, POST /webhooks/:source |
Modules |
| Connections | GET/POST /connections, PUT/DELETE /connections/:id, POST /connections/:id/test, GET /adapters, POST /webhooks/connections/:id |
Connections |
| Packs and skills | GET/POST /packs, GET /skills, GET /tools, GET /schemas |
Packs and skills |
| Outside agents | /gateway/agents... (admin), POST/GET /actions, GET /actions/:id, POST /actions/:id/cancel, /gateway/work-sessions..., GET /gateway/tools, and POST /mcp at the root |
Outside agents |
| Several organizations | x-ok-tenant header or tenant subdomain; operator API under /operator/... on the operator listener |
Hosting several organizations |
POST /tick is for admins only; anyone else gets 403 "policy denied tick:
admins only". POST /webhooks/:source takes a signature instead of a token
(see Self-hosting); without a secret configured
for the source it answers 401 "no webhook secret is configured for this
source".
Known problems#
Found while writing this page; reported for fixing.
- Commands write over a running server, silently. Any state-file
command run while the server is up, even a read such as
ok log, writes the file back, and nothing stops or warns you. A change it makes (a kill switch, a token) is reported as done and logged, but the server never applies it and loses it at its next write. The log keeps the event anyway. - A forgotten
--statemakes a new company. In a folder without.orchkernel/state.db, a command quietly creates an empty one. - An unknown
--asis not always caught.ok inbox,ok tasksandok threadsprint nothing and exit 0, and an empty inbox or thread list prints nothing either, so the two look alike.ok logprints the whole log for an actor that does not exist. - Some framework errors are not JSON. Malformed JSON, a missing field,
a missing
content-typeand a wrong method answer in plain text or empty, not witherrorandmessage. Last-Event-IDis ignored on the event stream; passsincewhen you reconnect.- Token kind has two shapes.
POST /tokensanswers"kind": "api";GET /tokenslists"kind": {"type": "api"}. - Help text. Many subcommands (
ok runs,ok actor list, everyok brainsubcommand,ok directory,ok schema,ok changes) show no description in--help, and some help lines cite internal design sections such as "(T§5)", "(SC§5.7)" or "(PC§7)". - A demo that cannot start still prints tokens. When the port is taken,
ok demo serveprints the tokens and "OrchKernel serving on ..." before it fails with "Address already in use", and the tokens it printed do not work against whatever holds the port.
Not possible yet#
- Running the state-file commands against a server: only
ok policy,ok tenantandok operatortake--server. - Issuing a scoped token from the workspace.
- Resuming the event stream from
Last-Event-ID. - A published OpenAPI description of the API.
GET /api/schemasdescribes definitions (agents, skills, packs), not routes.