HTTP API
The box's HTTP API is how the portal, a phone and your own scripts list tasks, start them, answer approvals, undo work and follow the action log. Every operation is documented in full under HTTP API operations, generated from the routes the core serves.
Where it listens
Section titled “Where it listens”| Address | Who can use it |
|---|---|
http://127.0.0.1:7700 |
Processes on the box. The default. |
http://<BOX_LISTEN>:7700 |
Anything that can reach that address, once you set BOX_LISTEN. |
BOX_HOME/api.sock |
Root on the box. A Unix socket with mode 0600, serving the same routes. |
BOX_PORT changes the port, and BOX_PORT=0 turns the TCP listener off. The socket is always on.
Every route answers both with and without the /api prefix. These two requests are the same:
curl -s http://127.0.0.1:7700/api/taskscurl -s http://127.0.0.1:7700/tasksApp routes (/t/<app>/...) and webhooks (/hook/<app>/<name>) are the exception. They answer only at those paths.
Requests and responses
Section titled “Requests and responses”Send JSON with content-type: application/json and read JSON back.
curl -s http://127.0.0.1:7700/api/tasks \ -H 'content-type: application/json' \ -d '{"title": "Summarise the notes", "prompt": "Read notes.md and write summary.md.", "budgetCents": 50}'Errors
Section titled “Errors”A failed request returns status 400 and an object with one field:
{ "error": "no workspace notes" }| Status | When |
|---|---|
| 400 | Bad input, or an id that does not exist (no task t3k9x2a1b2, no approval ...) |
| 401 | POST /api/approvals/<id> with a device token that is unknown or revoked |
| 404 | No such route, or DELETE /api/devices/<id> for an unknown device |
| 500 | An app route threw an error |
Authentication and its limits
Section titled “Authentication and its limits”The API has no authentication. Anyone who can open a connection to the port can start tasks, answer approvals and undo work. That is why it listens on 127.0.0.1 by default.
- To use the portal from another device, put the box on a network you trust, such as a VPN, and set
BOX_LISTENto that interface's address. - Do not expose port 7700 to the internet. If an outside service must call one app route, put a reverse proxy in front that forwards only that path. The Jira guide does this.
- A paired phone sends
Authorization: Bearer <token>when it answers an approval. The box checks the token and logs the phone's name. Other routes do not read the header. See Pairing.
Cross-origin requests
Section titled “Cross-origin requests”The portal is served from the same address as the API, so it needs no CORS. A web app served from another origin, such as a development server, needs that origin in BOX_ORIGINS:
BOX_ORIGINS=http://localhost:5173Separate several origins with commas, or use * for any origin. The box then answers OPTIONS preflight requests and adds access-control-allow-origin for GET, POST and OPTIONS.
The portal on the same address
Section titled “The portal on the same address”The box serves the built portal from portal/dist (or the folder in BOX_PORTAL) on the API's address. A GET for a file returns the file. A page load (an Accept header with text/html) for any other path returns index.html, so the portal's own routes work. A fetch('/tasks') without text/html still reaches the API.
App routes
Section titled “App routes”An app's routes are served at /t/<app>/<path>. For example, the wallet app's page is /t/wallet/ and the shopper's store is /t/shopper/store. GET /api/apps lists the pages apps want in the portal's app grid. See Apps and slots.
Webhooks
Section titled “Webhooks”An app that uses the webhook trigger serves POST /hook/<app>/<name>. The JSON body becomes the payload, and the app usually starts a task:
curl -s http://127.0.0.1:7700/hook/example/start \ -H 'content-type: application/json' -d '{"title": "From a webhook"}'The response is the new task, or {"ok": true} if the app chose not to start one. See Triggers.
Following the action log
Section titled “Following the action log”Two ways to read the log as it grows:
- Polling.
GET /api/log?after=<seq>returns every event after that line. Keep the lastseqyou saw. - Streaming.
GET /api/log/streamsends Server-Sent Events. It replays from?after=<seq>or theLast-Event-IDheader, then streams new events live. Each event isid: <seq>anddata: <json>. A: beatcomment arrives every 25 seconds.
curl -N http://127.0.0.1:7700/api/log/stream?after=0Both accept task=<id> to follow one task. For live task status and approvals, the portal uses SpacetimeDB instead: see Live updates.
Personal: real folders
Section titled “Personal: real folders”Pairing
Section titled “Pairing”A phone pairs once and gets its own device token:
- The portal's Settings asks the box for a one-time code,
POST /api/pair/start, and shows it as a QR code. The code works once and expires after 5 minutes. - The QR code opens the portal's pairing screen on the phone (
/#pair/<code>), which redeems the code:POST /api/pair {"code": "...", "name": "My phone"}returns{token, device, box}. - When the phone answers an approval, it sends
Authorization: Bearer <token>, and the log records the phone's name asby. DELETE /api/devices/<id>stops that token. Its next answer gets 401.
The older names POST /api/pair/new, GET /api/pair/devices and POST /api/pair/devices/<id>/revoke still work. A GET /pair is a portal page, not an API route.
Every route
Section titled “Every route”| Method and path | Does |
|---|---|
GET /api/tasks |
List every task |
POST /api/tasks |
Start a task |
GET /api/tasks/<id> |
One task, its approvals and its log events |
GET /api/tasks/<id>/diff |
What the task changed, and a unified diff |
POST /api/tasks/<id>/undo |
Undo the task |
POST /api/tasks/<id>/keep |
Personal (preview): apply a done task's changes to the owner's real folder |
GET /api/owner |
Personal (preview): the owner's user and home, or null |
GET /api/approvals?state= |
Approvals, all or in one state |
POST /api/approvals/<id> |
Approve or deny. The first answer wins. |
GET /api/log?after=&task= |
Log events after a cursor |
GET /api/log/stream?after=&task= |
The log as Server-Sent Events |
GET /api/apps |
Pages apps add to the portal |
GET /api/health |
{ok, startedAt, resumed, tasks, tools, spacetime} |
GET /api/boxes |
This box and its peers from BOX_PEERS |
POST /api/pair/start |
Make a one-time pairing code |
POST /api/pair |
Trade a code for a device token |
GET /api/devices |
Paired phones |
DELETE /api/devices/<id> |
Revoke a phone |
POST /hook/<app>/<name> |
Call an app's webhook |
/t/<app>/... |
App routes, defined by each app |
Known limits
Section titled “Known limits”- Path parameters are not percent-decoded. An approval id with a space or a slash in it, such as one made from Droid's own question, cannot be answered through
POST /api/approvals/<id>. Answer it with the SpacetimeDBanswerreducer, which is what the portal does when it is connected. GET /api/tasks/<id>for an unknown id returns 400, not 404.