API Keys

Generate scoped Bearer tokens for headless access — scripts, CI pipelines, and AI agents.

API keys are long-lived Bearer tokens for programmatic access to the Spunto API — for scripts, CI pipelines, or AI agents that need to operate the platform without a browser session. They're scoped to a single organization and to a specific set of permissions, and can be revoked independently of any user's login session.

Note

Prefer an API key over signing your own session JWT for any headless use case — it's scoped, named, revocable on its own, and never expires the dashboard session of the human who created it.

1

Open Account Settings

From the dashboard, go to Account Settings and find the API keys card.

2

Pick an organization

A key is always scoped to one organization. If you belong to several, pick the one the key should act on.

3

Choose owner type

Personal keys act as you — they stop working if you lose membership in the org. Org (service account) keys belong to the organization itself and keep working even if the creator leaves; creating one requires the admin or owner role.

4

Select scopes

Pick the narrowest set of scopes the script or agent actually needs (see Scopes below).

5

Copy the token

The full token (spk_...) is shown exactly once, right after creation. Store it somewhere safe — Spunto only ever stores a hash of it, so it can't be shown again. If you lose it, revoke the key and create a new one.

Personal (ownerType: "user")Org service account (ownerType: "org")
Acts asYouThe organization
Who can create itAny org member, for themselvesadmin / owner only
Survives creator leaving the orgNo — tied to your membershipYes
Typical usePersonal scripts, your own CLI toolingLong-running CI, an autonomous agent deployed in prod

Scopes are coarse and resource-based, in the form resource:action. A request needs at least one matching scope (or the * wildcard) to pass — sessions created via the dashboard login (JWT/cookie) are unaffected and always have full access; scoping only applies to API keys.

ScopeGrants
*Every scope below — full access
projects:readList/get projects, image builds, version history
projects:writeCreate/update/delete projects, trigger builds, restore versions
workers:readList/get workers, logs, stats, git status, ports
workers:writeSpawn/stop/start/rebuild/delete workers
workers:execTerminal sessions and SSH tokens — interactive shell access inside a worker
deployments:readList/get deployments and services, service logs, job runs
deployments:writeCreate/update/delete deployments and services, start/stop/deploy, run jobs
nodes:readList/get worker nodes, inventory
nodes:writeCreate/delete nodes, rotate tokens, drain — infrastructure-level access

Warning

workers:exec and nodes:write are split out from the generic read/write scopes on purpose — they grant shell access or infra control. Don't bundle them into a "read-only monitoring" key.

A request is matched against GET:read, every other HTTP method → :write, except terminal/SSH-token endpoints under /workers/..., which always require :exec regardless of method.

Send it as a standard Bearer token — exactly like a session JWT:

curl -H "Authorization: Bearer spk_..." \
  https://spunto.net/api/orgs/{orgId}/projects
curl -X POST \
  -H "Authorization: Bearer spk_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "ci-worker", "image": "..." }' \
  https://spunto.net/api/orgs/{orgId}/projects/{projectId}/workers

A request with a missing, malformed, revoked, or expired key returns 401 Unauthorized. A request with a valid key but a missing scope returns 403 Forbidden.

Warning

API keys cannot create, list, or revoke other API keys — there is no key-to-key escalation path. Managing keys requires a real session (browser login), the same trust tier as inviting a member or rotating a node token.

GET    /api/orgs/:orgId/api-keys           List keys (token values are never returned, only a `tokenPrefix`)
POST   /api/orgs/:orgId/api-keys           Create a key { name, ownerType, scopes } { ...key, token }
DELETE /api/orgs/:orgId/api-keys/:keyId    Revoke a key

The full request/response schemas (including the ApiKey/ApiKeyCreated shapes) are in the interactive reference — look for the ApiKeys tag at /api/docs, or browse the raw spec at /openapi.json.

curl -X DELETE \
  -H "Authorization: Bearer <session token, not an API key>" \
  https://spunto.net/api/orgs/{orgId}/api-keys/{keyId}

Revocation is immediate — the very next request made with that token returns 401. The key row isn't deleted, only marked revoked, so it stays visible (and auditable) in the list.

  • No expiration support yet. Keys are valid until revoked — there's no "expires in 90 days" option in the dashboard or API today.
  • No per-project/per-deployment restriction. A scope like workers:write applies to every project in the org, not a single one.
  • No rate limiting. A misbehaving script or agent loop using an API key is bound by the same limits as everything else — i.e. none yet.

Tip

For the AI-agent use case specifically (spawning workers, running commands, reading output), see the AI Agents guide — it walks through the full lifecycle using an API key.