API Reference
Explore the full Spunto REST API — all endpoints, request bodies, and response schemas.
The Spunto API is a REST API. All endpoints require a Bearer token (except public auth routes).
The full interactive API reference is available at:
https://spunto.net/api/docsIt's generated automatically from the OpenAPI 3.1 spec — always up to date.
All API requests (except /api/auth/*) require:
Authorization: Bearer <token>Two kinds of token are accepted:
- Session token — issued when you sign in with Google. The same token is set as an httpOnly
sessioncookie by the auth flow — browser requests include it automatically. Carries full access (no scoping). - API key (
spk_...) — a scoped, revocable token generated from the dashboard for headless access (scripts, CI, AI agents). See the API Keys page for how to create and use one.
https://spunto.netAll endpoints start with /api/.
PATCH means partial: a request body only touches the fields it names, and everything it leaves
out is kept as-is. Renaming a project is {"name": "..."} — its repositories, features and
extensions stay where they were.
Which means emptying something has to be said out loud:
- a list is cleared by sending it explicitly empty —
{"forwardPorts": []} - a free-text field is cleared by sending
""—{"description": ""}storesnull(whitespace counts as empty). Omitting the key keeps the old value; it never means "erase it".
A project's shared secrets ride in the same body, and are partial in the same way — a list of changes, not the desired final state:
PATCH /api/orgs/:orgId/projects/:projectId
{
"secrets": [
{ "name": "API_TOKEN", "value": "sk-..." }, // write (creates or replaces, by name)
{ "name": "OLD_TOKEN", "value": null } // remove
]
}Any name you don't mention keeps whatever it had. Values are write-only: they are never returned by
any endpoint. Writing them is owner/admin only, and an API key needs the secrets:write scope on
top of projects:write — the same gates as the dedicated
/secrets routes, which remain available for one-off changes.
GET /api/orgs List organizations
POST /api/orgs Create organization
GET /api/orgs/:orgId Get organization details
GET /api/orgs/:orgId/members List membersGET /api/orgs/:orgId/projects List projects
POST /api/orgs/:orgId/projects Create project
GET /api/orgs/:orgId/projects/:projectId Get project
PATCH /api/orgs/:orgId/projects/:projectId Update project
DELETE /api/orgs/:orgId/projects/:projectId Delete project
POST /api/orgs/:orgId/projects/:projectId/build Trigger pre-build on all nodes
GET /api/orgs/:orgId/projects/:projectId/image-builds List build recordsGET /api/orgs/:orgId/projects/:projectId/workers List workers
POST /api/orgs/:orgId/projects/:projectId/workers Spawn worker
GET /api/orgs/:orgId/projects/:projectId/workers/:workerId Get worker
POST /api/orgs/:orgId/projects/:projectId/workers/:workerId/stop Stop worker
POST /api/orgs/:orgId/projects/:projectId/workers/:workerId/start Start worker
DELETE /api/orgs/:orgId/projects/:projectId/workers/:workerId Destroy worker
GET /api/orgs/:orgId/projects/:projectId/workers/:workerId/logs Docker logs
GET /api/orgs/:orgId/projects/:projectId/workers/:workerId/stats CPU + memory
GET /api/orgs/:orgId/projects/:projectId/workers/:workerId/git-status Git status
GET /api/orgs/:orgId/projects/:projectId/workers/:workerId/ports Listening ports
POST /api/orgs/:orgId/projects/:projectId/workers/:workerId/ssh-token Generate SSH tokenGET /api/orgs/:orgId/nodes List nodes
POST /api/orgs/:orgId/nodes Register node
GET /api/orgs/:orgId/nodes/:nodeId Get node
DELETE /api/orgs/:orgId/nodes/:nodeId Delete node
POST /api/orgs/:orgId/nodes/:nodeId/token Rotate token
POST /api/orgs/:orgId/nodes/:nodeId/drain Drain node
GET /api/orgs/:orgId/nodes/:nodeId/inventory Live container inventoryGET /api/orgs/:orgId/deployments List deployments
POST /api/orgs/:orgId/deployments Create deployment
GET /api/orgs/:orgId/deployments/:deploymentId Get deployment
PATCH /api/orgs/:orgId/deployments/:deploymentId Update deployment
DELETE /api/orgs/:orgId/deployments/:deploymentId Delete deployment
POST /api/orgs/:orgId/deployments/:deploymentId/start Start all services
POST /api/orgs/:orgId/deployments/:deploymentId/stop Stop all services
POST /api/orgs/:orgId/deployments/:deploymentId/deploy Deploy all servicesGET /api/orgs/:orgId/deployments/:deploymentId/services List services
POST /api/orgs/:orgId/deployments/:deploymentId/services Add service
PATCH /api/orgs/:orgId/deployments/:deploymentId/services/:serviceId Update service
DELETE /api/orgs/:orgId/deployments/:deploymentId/services/:serviceId Remove service
POST /api/orgs/:orgId/deployments/:deploymentId/services/:serviceId/start Start
POST /api/orgs/:orgId/deployments/:deploymentId/services/:serviceId/stop Stop
POST /api/orgs/:orgId/deployments/:deploymentId/services/:serviceId/deploy Deploy
POST /api/orgs/:orgId/deployments/:deploymentId/services/:serviceId/verify-domain Verify domain
GET /api/orgs/:orgId/deployments/:deploymentId/services/:serviceId/logs Service logsGET /api/orgs/:orgId/api-keys List keys
POST /api/orgs/:orgId/api-keys Create a key (token shown once)
DELETE /api/orgs/:orgId/api-keys/:keyId Revoke a keySession-only — an API key cannot be used to manage other API keys. See API Keys.
GET /api/users/me Get current user (dotfilesRepo)
PATCH /api/users/me Update user settings
GET /api/users/me/secrets List secret names
POST /api/users/me/secrets Create / replace secret
DELETE /api/users/me/secrets/:secretId Delete secretAnything live is a WebSocket upgrade rather than a REST call. These endpoints are in the OpenAPI spec like the rest of the API — look for (WebSocket) in the summary; each one documents its query parameters and the exact shape of the frames it exchanges.
| Endpoint | Description |
|---|---|
GET /api/workers/:id/terminal?mode=terminal | Attach to a persistent terminal session in a workspace |
GET /api/workers/:id/terminal?mode=logs | Follow the workspace container's logs |
GET /api/services/:id/terminal | Follow a deployed service's logs |
GET /api/services/:id/shell | Interactive shell in a deployed service |
GET /api/job-runs/:runId/logs | Deploy / hook / cron run output, live or replayed |
GET /api/image-builds/:id/logs | Image build log, with the build's steps |
Authenticate the handshake exactly like a REST call — Authorization: Bearer <token>, with a
session token, or an API key scoped workers:exec on the workspace terminal. A browser can't set
headers on a WebSocket, so from the browser the session cookie is used instead.
A workspace terminal is persistent: the program keeps running when you disconnect, and reattaching lands on it with its scrollback — which is what makes it usable from a phone.
GET /api/orgs/:orgId/projects/:projectId/workers/:workerId/terminals List sessions
POST /api/orgs/:orgId/projects/:projectId/workers/:workerId/terminals Create a session
DELETE /api/orgs/:orgId/projects/:projectId/workers/:workerId/terminals/:name Kill a sessionList the sessions, then attach to one by name. Closing the socket detaches; it doesn't kill it.
const ws = new WebSocket(`wss://spunto.net/api/workers/${workerId}/terminal?session=main`)
ws.onmessage = (e) => {
const frame = JSON.parse(e.data)
// "ready" once attached, then "output" frames — base64-encoded raw bytes.
if (frame.type === "output") term.write(atob(frame.data))
}
ws.send(JSON.stringify({ type: "input", data: btoa("ls -la\n") }))
ws.send(JSON.stringify({ type: "resize", cols: 80, rows: 24 }))When you want the last N lines rather than a stream, both logs have a plain REST form —
GET …/workers/:workerId/logs?tail=200 and
GET …/services/:serviceId/logs?tail=200, returning text/plain.
Not part of the API surface — they authenticate with infrastructure tokens, not yours.
| Endpoint | Description |
|---|---|
GET /api/agent/connect?token= | Node agent connection (node token) |
GET /api/tunnel/relay | Proxy ↔ agent relay |