HTTP API
Everything the dashboard and the CLI do goes through this API. Base URL: https://api.waken.sh. Requests and responses are JSON. Errors look like {"error": "..."} with a meaningful status code (see errors).
Authentication
Send an API key as a bearer token. Create keys in the dashboard under Developers. A key only sees the agents of its own account.
curl https://api.waken.sh/agents \-H "Authorization: Bearer $WAKEN_KEY"
| Scope | Allows |
|---|---|
| provision | Create agents |
| deploy | Message agents, run commands, read and write files, schedules, checkpoints |
| secrets | Set environment variables, change the model or the harness |
| computers | Desktops: screenshots, actions, live view, the MCP server |
| manage | Everything, including deleting agents and managing keys and webhooks |
Give each part of your system the smallest scope it needs. A key for your chat backend only needs deploy.
Keys are managed from the dashboard, or with the API itself: POST /keys ({"name": "prod", "scopes": ["provision", "deploy"]}, the key is returned once), GET /keys to list them without their secret, DELETE /keys/{id} to revoke one. Scopes: provision creates agents, deploy talks to them and handles their files, secrets changes their variables, model or harness, computers drives desktops and the MCP server, manage does everything, deletion and keys included. Listing and reading agents needs no particular scope.
Create an agent
curl -X POST https://api.waken.sh/agents \-H "Authorization: Bearer $WAKEN_KEY" \-d '{"name": "customer-42","external_id": "customer-42","model": { "provider": "anthropic", "api_key": "sk-ant-..." },"spend_cap_usd": 10,"idle_s": 300}'# 201{ "id": "4ec0b05a", "name": "customer-42", "status": "running", "harness": "claude-code", ... }
| Field | Meaning |
|---|---|
| name | A label. Lowercase letters, digits and dashes |
| external_id | Your own id for this agent. Creating twice with the same one returns the same agent: safe to retry |
| harness | claude-code (default), codex, openclaw, cursor, hermes |
| model | provider, name (optional), api_key. See Models and keys |
| env | Extra environment variables for the agent |
| spend_cap_usd | Token budget, 5 by default |
| idle_s | Seconds without activity before it sleeps. 300 by default, 0 for always on (paid option) |
| desktop | true to give it a Linux desktop (paid plans) |
| mem_mib, disk_gib | Bigger machine: up to 8192 and 100 (paid option) |
| image | A public container image instead of a harness. See Container images |
Talk to it
curl -X POST https://api.waken.sh/agents/4ec0b05a/message \-H "Authorization: Bearer $WAKEN_KEY" \-d '{ "text": "Sort my March receipts" }'{ "text": "Done, 14 receipts sorted.", "ms": 4936 }
If the agent is asleep, this call wakes it first. You never call wake yourself. The request stays open until the agent answers: allow a few minutes of timeout on your side.
Sleep, checkpoints, rewind
| Route | What it does |
|---|---|
| GET /agents | List your agents. ?external_id= to find one |
| GET /agents/{id} | One agent: status, harness, model, size |
| POST /agents/{id}/sleep | Sleep now. It wakes on the next call that needs it |
| POST /agents/{id}/wake | Wake it without sending a message, for example before a burst of calls |
| POST /agents/{id}/restart | Restart the harness process. Files and checkpoints untouched |
| POST /agents/{id}/snapshot | Take a checkpoint now |
| GET /agents/{id}/snapshots | List its checkpoints |
| POST /agents/{id}/restore | Rewind: {"snapshot_id": "..."}, or an empty body for the latest |
| GET /agents/{id}/usage | Spend so far and the cap |
| PUT /agents/{id}/size | Change memory and disk: {"mem_mib": 4096, "disk_gib": 20} |
| DELETE /agents/{id} | Delete it, its files and its checkpoints |
Files and commands
Paths are relative to the agent's working folder, /root/work. Files up to 100 MB.
| Route | What it does |
|---|---|
| GET /agents/{id}/files/list?path= | List a folder |
| GET /agents/{id}/files/download?path= | Download a file |
| PUT /agents/{id}/files/write?path= | Write a file: the request body is the content |
| POST /agents/{id}/files | Upload a whole folder as a gzipped tarball |
| POST /agents/{id}/files/mkdir, /move | {"path": "..."} and {"from": "...", "to": "..."} |
| DELETE /agents/{id}/files/delete?path= | Delete a file or a folder |
| POST /agents/{id}/exec | {"command": "ls -la", "timeout_s": 60}. Returns exit_code and output |
| GET /agents/{id}/logs?lines=100 | What the harness printed |
| GET /agents/{id}/console | The machine's boot log, when something looks wrong at a lower level |
Model, harness and variables
| Route | What it does |
|---|---|
| PUT /agents/{id}/model | {"model": {provider, name, api_key}}: same harness, new model |
| PUT /agents/{id}/harness | {"harness": "codex", "model": {...}}: new harness, files kept, conversation handed over |
| GET /agents/{id}/env | The names of its variables. Values are never returned |
| PUT /agents/{id}/env | {"env": {"FOO": "bar"}, "unset": ["OLD"], "reload": true}. reload restarts the harness so it sees the change |
Schedules and incoming webhooks
# Every weekday at 09:00 UTC, wake the agent and send it this message{ "cron": "0 9 * * 1-5", "message": "Check the inbox and file what arrived" }
GET /agents/{id}/triggers lists them, DELETE /agents/{id}/triggers/{tid} removes one, and POST /agents/{id}/triggers/{tid}/run fires one right now, to test it.
GET /agents/{id}/webhook returns a secret URL. Any POST to it becomes a message to the agent, 30 per minute at most: plug it into Stripe, GitHub or a form. POST /agents/{id}/webhook/rotate replaces the URL.
Events sent to you
Register a URL with POST /webhooks ({"url": "https://...", "events": ["agent.capped"]}, or no events for all of them). The response contains a signing secret, shown once. Each delivery carries X-Waken-Event, a unique X-Waken-Delivery id, and X-Waken-Signature: sha256= followed by the HMAC SHA-256 of the raw body with your secret.
- agent.deployed, agent.sleeping, agent.woken, agent.restored, agent.resized, agent.deleted
- agent.capped: the spend cap was reached
- agent.error
- computer.takeover_requested: an agent on a desktop needs a human