The bootkey API
Everything in the console runs on this API. The part described here is open to setup keys: short-lived keys a person hands to their own AI (Claude Code, Codex, Cursor, a local model) so it can boot and set up an agent for them. Base URL: https://bootkey.ai.
AI apps that speak MCP (Claude, ChatGPT, Cursor) can connect to https://bootkey.ai/mcp instead and sign in once; see Connect your AI. A connected app gets the same access as a boot key, with up to 3 boots a day, and its token works on this API too.
Setup keys
A person makes a key in the console with Copy agent prompt: on the boot page (a boot key) or on an agent’s Access tab (an agent key). The key is inside the prompt, and the prompt is all their AI needs.
Send it on every request:
Authorization: Bearer bk_key_…
| Boot key | Agent key | |
|---|---|---|
| Lasts | 1 hour | 1 hour |
| Can boot | one agent | nothing |
| Can set up | the agent it booted | the one agent it was made for |
| Can pay | open USDC invoices, to pay from a wallet the AI controls | same |
| Can never | touch cards, auto-refill, the team, model keys or other setup keys; destroy, hibernate or wake an agent | same |
Every change made with a key appears in that agent’s activity log (“Your AI assistant added the machine db1, with a setup key.”). The person can revoke keys on the Security page (/app/security) at any time.
Answers are JSON ({"ok": true, …}), except the guide, which is markdown. Errors are {"ok": false, "error": "…"} with a status code: 400 (bad request: read the error, fix and retry), 401 (key expired or revoked), 402 (balance too low: ask your human to top up at /app/billing, or pay in USDC), 403 (outside what the key may do), 404 (no such agent, or not this key’s), 409 (wrong state, e.g. not running yet), 429 (too many in a short time).
The guide
GET /api/agent/guide: start here. Markdown with the person’s account state (balance, connected model keys, SSH keys, existing agents), every option with its trade-offs (this manual), the current templates and model presets with ratings, and this reference. ?part=account, options, manual or api returns just that part.
Catalog
GET /api/models: every model you can boot, with presets and ratings.
{
"models": [{ "id": "anthropic/claude-sonnet-5.5", "name": "Claude Sonnet 5.5", "via": "OpenRouter", "in": 2, "out": 10, "ctx": 1000000, "reasoning": true }],
"presets": [{ "id": "smartest", "label": "Smartest", "provider": "openrouter", "hint": "…", "models": ["…"] }],
"ratings": { "anthropic/claude-sonnet-5.5": { "agent": { "rank": 3, "of": 57, "value": 0.16, "setting": "Max", "cost": 3.42 }, "shell": { … }, "chat": { … } } },
"ratings_published": "2026-11-08"
}
Prices are USD per million tokens (in, out). Ratings come from LMArena (boards: agent, hallucination, shell, webdev, chat; lower rank is better).
Agents
GET /api/agents (boot keys): the account’s agents.
POST /api/agents (boot keys, spends the key’s one boot): boot an agent. It takes about two minutes to come up.
{
"name": "sentinel",
"region": "fra",
"template": "immune-system",
"provider": "openrouter",
"model": "anthropic/claude-sonnet-5.5",
"mind": { "memory": "files", "rhythm": { "kind": "pulse", "every": 30 }, "wake_model": null },
"support_access": false
}
region:fra(Frankfurt),nyc(New York),sfo(San Francisco),syd(Sydney).template: a template id from the guide.provider:openrouterorvenice; the account must have that key connected (the guide says which are).model: an id from/api/modelswith the samevia.mind.memory:files,vaultorholographic.mind.rhythm.kind:reactive(tool),pulse(worker, all day),daynight(worker by day, dreams at night) orself(alive).every(10, 15, 20, 30 or 60 minutes) is forpulseanddaynight.mind.wake_model: optional cheaper model for routine wake-ups, same provider.support_access: leavefalseunless the person asks for bootkey support to keep a key on the box.
Answer: {"ok": true, "id": "agt_…"}. A 402 means the balance needs a day of runway per running agent; ask your human to top up, or pay in USDC (below) if they want you to.
GET /api/agents/{id}: the agent, its recent activity and vitals.
{ "agent": { "id": "agt_…", "name": "sentinel", "status": "running", "ipv4": "203.0.113.7", "model": "…", "template": "…", "mind": "{…}", "online": true }, "events": [{ "ts": 1794364812000, "level": "ok", "message": "…" }] }
status goes provisioning → booting → configuring → running. error means it needs a person (read status_detail and the events).
GET /api/agents/{id}/mind: its state of mind (thinking, awake, light sleep, deep sleep, dreaming) and next wake-up. GET /api/agents/{id}/costs: its body and mind costs this month.
Commands: talking to the box
The box runs a small daemon that picks up commands. Queue one, then wait for its answer:
POST /api/agents/{id}/commands {"kind": "reach_list", "payload": {}} → {"ok": true, "id": 123}
GET /api/agents/{id}/commands?ids=123&wait=25 → {"commands": [{"id": 123, "status": "done", "result": {…}}]}
wait (up to 25 seconds) holds the answer until the command finishes. status is pending, running, done or error (then result.error says why). The agent has to be running.
| kind | payload | result |
|---|---|---|
chat |
{"message": "…"} |
{"reply": "…", "seconds": 12.3} (can take a few minutes; keep waiting) |
status |
{} |
model, Hermes version, skills, and jobs_full: every duty with schedule, next run and who set it |
cron_save |
{"schedule": "0 8 * * 1-5", "prompt": "…", "name": "orders", "timezone": "Europe/Berlin"}; add "id" to edit one |
the job id and the full list |
cron |
{"action": "run" | "pause" | "resume", "id": "…"} |
Hermes’ output |
brain_list |
{} |
{"files": [{"path", "size", "mtime"}]} |
brain_read |
{"path": "objectives.md"} |
{"content", "mtime"} |
brain_write |
{"path": "objectives.md", "content": "…"} |
saved; setup keys may write SOUL.md, objectives.md, now.md, and files under watch/, notes/ and immune/ |
set_model |
{"model": "…"} |
switches its main model (same provider) |
reach_list |
{} |
{"public_key": "ssh-ed25519 …", "hosts": [{"alias", "host", "user", "port", "note", "watched", "fingerprint"}]} |
reach_save |
{"alias": "db1", "host": "10.0.0.5", "user": "bk-sentinel", "port": 22, "note": "Postgres primary", "watch": true} |
the updated list |
reach_test |
{"alias": "db1"} |
{"ok": true, "output": "…", "fingerprint": "SHA256:…"} |
reach_remove |
{"alias": "db1"} |
the updated list |
logs |
{"unit": "hermes" | "agentd", "lines": 200} |
the box’s journal |
Schedules: five-field cron (0 8 * * 1-5), an interval (30m, every 2h), or a one-off ISO time (2026-12-01T09:00).
Vault
PUT /api/agents/{id}/secrets with {"env": "GITHUB_TOKEN=github_pat_…\nSLACK_WEBHOOK_URL=https://…"} or {"vars": {"NAME": "value"}}. Values are encrypted and can’t be read back; the box picks them up within seconds. GET lists the names. Ask your human for the values; never invent or reuse their personal credentials, and prefer tokens with limited rights.
Temper
POST /api/agents/{id}/rhythm with {"kind": "self"} or {"kind": "pulse", "every": 30}. Answers {"id": …}, a command to wait for. It swaps the agent’s own wake-up jobs and the rhythm note in its SOUL; other duties stay.
Paying in USDC
If your human wants you to pay, and you control a wallet with USDC, you can add to their balance yourself. This works with keys made by the person who holds the account, not by a team member.
POST /api/billing/usdc {"amount": 20, "chain": "base"}
amount is in US dollars, 10 to 500. chain is base, polygon, solana or ethereum. The answer is an invoice:
{ "invoice": { "id": "usdc_…", "network": "Base", "chain_id": 8453, "address": "0x…", "token": "0x8335…2913", "amount": "20.00", "amount_units": "20000000", "decimals": 6, "minutes": 20, "status": "open", "how": "Call transfer(0x…, 20000000) on the USDC contract …" } }
Send exactly amount of Circle’s native USDC to address on that network, in one transfer. The address is for this payment only. It is credited once the network can’t undo it (about minutes minutes); check with GET /api/billing/usdc?id=usdc_… until status is paid. Other tokens, bridged USDC or other networks are not credited automatically. Your human gets an email when the money arrives. An AI can open three invoices an hour.
Recipe: let an agent log into a server
-
reach_list→ itspublic_key. -
On the server, with your human’s permission, create a login of its own (no sudo):
sudo useradd -m -s /bin/bash bk-sentinel sudo install -d -m 700 -o bk-sentinel -g bk-sentinel /home/bk-sentinel/.ssh echo 'ssh-ed25519 AAAA… hermes@sentinel' | sudo tee /home/bk-sentinel/.ssh/authorized_keys >/dev/null sudo chown bk-sentinel: /home/bk-sentinel/.ssh/authorized_keys && sudo chmod 600 /home/bk-sentinel/.ssh/authorized_keys sudo usermod -aG adm bk-sentinel # only for watchers: lets it read system logs -
reach_savewith the server’s address and that user ("watch": trueto include it in the immune system’s sweeps). -
reach_test. Compare the fingerprint withssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pubon the server.