Skip to main content

Authentication & Errors

Overview

NanoTerm has two authentication layers:

LayerPurposeMethod
Managed identityUser signup and signinBrowser-based OAuth via the dashboard
API keysProgrammatic access from CLI, SDK, and the APIAuthorization: Bearer nt_xxx

API Keys

Every API request carries your key in the Authorization header:

curl https://api.nanoterm.dev/api/workspaces \
-H 'Authorization: Bearer nt_live_a1b2c3d4...'

Create and revoke keys in the dashboard's API Keys page or via the CLI (nanoterm auth login will generate one for you).

warning

The full key is shown once at creation time. Store it somewhere you can find it — there's no way to retrieve it later.

Permission levels

PermissionWorkspacesExecFilesAdmin (keys, org)
fullread/writeyesread/writeyes
executeread/writeyesread/writeno
readreadnoreadno

read keys are enforced at every route — including the terminal and proxy WebSockets and the diff endpoint, all of which shell out and therefore count as exec. A read key that tries to open /api/workspaces/:id/terminal or PUT to /files/* gets 403 FORBIDDEN, not a silent escalation. (This contract is regression- tested; if a future route forgets the gate, CI fails.)

Passing the key on WebSocket connections

Browsers can't set custom headers on a WebSocket handshake, so the terminal (/api/workspaces/:id/terminal) and proxy (/api/workspaces/:id/proxy/:port) endpoints accept a ?token= query parameter as a fallback only on WebSocket upgrade handshakes (the request must carry Upgrade: websocket). Regular HTTP requests must use the Authorization header — passing ?token= on a plain GET is silently ignored to keep your API key out of access logs, CDN logs, Referer headers, and browser history.

About the key format

Keys look like nt_<random>. The CLI and SDK don't need to know any structure beyond that — just paste the key. Upgrading your plan takes effect immediately for existing keys; no rotation required.

Rate Limits

API-key traffic (CLI, SDK, agent code) is rate-limited per plan:

PlanRequests / minute
Free60
Developer600
Team3,000
EnterpriseCustom

Exceeding the limit returns 429 Too Many Requests with a Retry-After header telling you how many seconds to wait. The official CLI and SDK auto-retry once when the wait is ≤5 seconds; longer waits throw a clear error your code can catch. Upgrading your plan raises the ceiling immediately — no key rotation needed.

Dashboard traffic (human sessions) is not subject to the per-plan cap. Every request still passes through a generic per-IP anti-abuse limit (1,000 rpm) that protects the service from floods.

Errors

Every API error looks the same:

{
"error": {
"code": "WORKSPACE_NOT_FOUND",
"message": "Workspace 'ws_abc' not found.",
"action": "Check the ID or list workspaces with GET /api/workspaces.",
"retryable": false,
"docs": "https://docs.nanoterm.dev/api-workspaces#get-workspace"
}
}

The fields are:

FieldWhat it's for
codeStable programmatic identifier — match on this in your code
messageHuman-readable description
actionOne sentence telling you what to do next
retryabletrue if the same request might succeed later (network, rate limit); false if not (bad input, missing resource)
docsDeep-link to the relevant docs page

Common status codes

These can come from any endpoint. Endpoint pages list additional endpoint-specific codes.

StatusCodeMeaning
400VALIDATION_FAILEDRequest body failed validation.
401UNAUTHORIZEDMissing or invalid Authorization header.
403FORBIDDENAuthenticated, but your key/role lacks the required permission. Body's action lists which role you'd need.
404NOT_FOUNDResource doesn't exist or belongs to another org.
409CONFLICTUnique constraint hit (e.g., duplicate slug).
426CLI_VERSION_TOO_OLDThe official CLI must be upgraded — server has raised the minimum version.
429RATE_LIMITEDPlan rate limit exceeded.
429QUOTA_EXCEEDEDWorkspace-count quota hit.
429COMPUTE_QUOTA_EXCEEDEDMonthly compute-hours budget exhausted.
429MEMBER_QUOTA_EXCEEDEDPlan's member cap reached (Free 1 / Dev 5 / Team 10 / Ent ∞).
429STORAGE_QUOTA_EXCEEDEDSnapshot/storage budget exhausted.
500INTERNAL_ERRORUnhandled server error. Retry after a short backoff.
501NOT_SUPPORTEDFeature isn't available on the current runtime.
503RUNTIME_UNAVAILABLEContainer runtime temporarily unreachable.

426 in practice

The server reads the X-Client-Name and X-Client-Version headers that the official CLI/SDK send. If X-Client-Name === "nanoterm-cli" and the version is below the documented minimum, every request returns 426. Other clients (curl, dashboards, your own SDK code) are not gated by this check.

HTTP/1.1 426 Upgrade Required
Content-Type: application/json

{
"error": {
"code": "CLI_VERSION_TOO_OLD",
"message": "CLI 0.1.2 is below the minimum required version (0.1.3).",
"action": "Run: npm install -g @nanoterm/cli@latest",
"retryable": false
}
}

429 in practice

HTTP/1.1 429 Too Many Requests
Retry-After: 17
Content-Type: application/json

{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Slow down requests or upgrade your plan.",
"action": "Retry after 17s, or upgrade at https://nanoterm.dev/pricing",
"retryable": true
}
}