API reference#
The full, machine-readable reference is the OpenAPI 3.1 spec:
- Hosted:
https://api.dormo.dev/v1/openapi.yaml - On this site:
openapi.yaml(a copy ofapi/openapi.yamlfrom the repository, published with every docs build)
Generate a client in any language from it (for example with openapi-generator), or import it into Postman or Insomnia. The official TypeScript and Python SDKs are hand-written from the same file and tested against it.
Basics#
| Topic | Rule |
|---|---|
| Base URL | https://api.dormo.dev/v1 |
| Auth | Authorization: Bearer dormo_sk_... (scheme name case-insensitive); each endpoint needs the scope listed below (Security) |
| Format | JSON with snake_case fields; file content is raw bytes; streams are Server-Sent Events; PTY is a WebSocket |
| IDs | sbx_ sandbox, cmd_ command, pty_ PTY, dsk_ desktop stream, snap_ snapshot, tpl_ template, whk_ webhook, evt_ event, key_ API key |
| Timestamps | RFC 3339, UTC |
| Pagination | ?limit= (1–100, default 20) and ?cursor=; responses {"data": [...], "has_more": true, "next_cursor": "..."} |
| Idempotency | Idempotency-Key header on POST /sandboxes, /fork, /snapshots, /templates (kept 24 h) |
| Rate limits | RateLimit-Policy and RateLimit headers on every response, including 401 and unknown routes; 429 with Retry-After (Limits) |
| Request ID | X-Request-Id on every response |
| Wake on request | Calls that need a running VM wake a paused sandbox; add ?auto_resume=false to get 409 sandbox_paused instead |
Errors#
Every error has the same shape:
{
"error": {
"code": "concurrency_limit_reached",
"message": "Your plan allows 10 concurrent sandboxes.",
"retryable": false,
"status": 409,
"request_id": "req_01HF3X",
"details": { "limit": 10, "used": 10 }
}
}
Branch on code, never on message. Retry only when retryable is true, after Retry-After if present.
| Status | Codes |
|---|---|
| 400 | invalid_request, invalid_path, invalid_template, invalid_image |
| 401 | unauthorized |
| 402 | payment_required, spend_limit_reached, free_hours_exhausted |
| 403 | forbidden, insufficient_scope |
| 404 | not_found |
| 408 | exec_timeout |
| 409 | invalid_state, sandbox_paused, sandbox_busy (retryable, Retry-After), concurrency_limit_reached, idempotency_conflict, already_exists, desktop_not_available, snapshot_in_use |
| 412 | precondition_failed |
| 413 | payload_too_large |
| 416 | invalid_request: a Range on GET .../files/content starts past the end of the file (details.size) |
| 429 | rate_limited (retryable); details.reason = "too_many_operations": too many concurrent operations on one sandbox |
| 500 | internal_error (retryable) |
| 502 | sandbox_unreachable (see retryable) |
| 503 | capacity_unavailable (retryable; details.reason = "incompatible_host" when a paused sandbox's snapshot needs a compatible host, see pause_reason), service_unavailable (retryable) |
| 507 | insufficient_storage: the sandbox disk is full (not retryable) |
Endpoints#
Sandboxes#
| Method | Path | Summary | Scope |
|---|---|---|---|
GET |
/sandboxes |
List sandboxes | sandboxes:read |
POST |
/sandboxes |
Create a sandbox | sandboxes:write |
GET |
/sandboxes/{sandbox_id} |
Get a sandbox | sandboxes:read |
PATCH |
/sandboxes/{sandbox_id} |
Update a sandbox | sandboxes:write |
DELETE |
/sandboxes/{sandbox_id} |
Delete a sandbox | sandboxes:write |
POST |
/sandboxes/{sandbox_id}/pause |
Pause a sandbox | sandboxes:write |
POST |
/sandboxes/{sandbox_id}/resume |
Resume a sandbox | sandboxes:write |
POST |
/sandboxes/{sandbox_id}/fork |
Fork a sandbox | sandboxes:write |
Exec#
| Method | Path | Summary | Scope |
|---|---|---|---|
POST |
/sandboxes/{sandbox_id}/exec |
Run a command | exec |
GET |
/sandboxes/{sandbox_id}/commands |
List commands | exec |
GET |
/sandboxes/{sandbox_id}/commands/{command_id} |
Get a command | exec |
GET |
/sandboxes/{sandbox_id}/commands/{command_id}/stream |
Attach to a command's output (SSE) | exec |
POST |
/sandboxes/{sandbox_id}/commands/{command_id}/stdin |
Write to a command's stdin | exec |
POST |
/sandboxes/{sandbox_id}/commands/{command_id}/kill |
Send a signal to a command | exec |
POST |
/sandboxes/{sandbox_id}/pty |
Create an interactive PTY session | exec |
GET |
/sandboxes/{sandbox_id}/pty/{pty_id} |
Get a PTY session (and a fresh connect URL) | exec |
DELETE |
/sandboxes/{sandbox_id}/pty/{pty_id} |
Close a PTY session | exec |
GET |
/sandboxes/{sandbox_id}/pty/{pty_id}/connect |
PTY WebSocket endpoint | signed token |
Files#
| Method | Path | Summary | Scope |
|---|---|---|---|
GET |
/sandboxes/{sandbox_id}/files |
List a directory | files:read |
DELETE |
/sandboxes/{sandbox_id}/files |
Delete a file or directory | files:write |
GET |
/sandboxes/{sandbox_id}/files/content |
Read a file | files:read |
PUT |
/sandboxes/{sandbox_id}/files/content |
Write a file | files:write |
GET |
/sandboxes/{sandbox_id}/files/stat |
Get file metadata | files:read |
POST |
/sandboxes/{sandbox_id}/files/mkdir |
Create a directory | files:write |
POST |
/sandboxes/{sandbox_id}/files/move |
Move or rename a file or directory | files:write |
POST |
/sandboxes/{sandbox_id}/files/upload |
Upload files (multipart) | files:write |
GET |
/sandboxes/{sandbox_id}/files/download |
Download a directory as an archive | files:read |
POST |
/sandboxes/{sandbox_id}/files/upload-url |
Create a signed upload URL | files:write |
POST |
/sandboxes/{sandbox_id}/files/download-url |
Create a signed download URL | files:read |
GET |
/sandboxes/{sandbox_id}/files/watch |
Watch a path for changes (SSE) | files:read |
Networking#
| Method | Path | Summary | Scope |
|---|---|---|---|
GET |
/sandboxes/{sandbox_id}/ports |
List exposed ports | sandboxes:read |
POST |
/sandboxes/{sandbox_id}/ports |
Expose a port on a public HTTPS URL | sandboxes:write |
DELETE |
/sandboxes/{sandbox_id}/ports/{port} |
Remove a public URL | sandboxes:write |
POST |
/sandboxes/{sandbox_id}/access-tokens |
Create an access token for private ports | sandboxes:write |
POST |
/sandboxes/{sandbox_id}/ssh |
Get SSH access | ssh |
GET |
/ssh-keys |
List account SSH keys | ssh |
POST |
/ssh-keys |
Add an account SSH key | ssh |
DELETE |
/ssh-keys/{ssh_key_id} |
Remove an account SSH key | ssh |
Desktop#
| Method | Path | Summary | Scope |
|---|---|---|---|
POST |
/sandboxes/{sandbox_id}/desktop |
Create a desktop stream URL | desktop |
GET |
/sandboxes/{sandbox_id}/desktop |
List active desktop stream URLs | desktop |
DELETE |
/sandboxes/{sandbox_id}/desktop/{stream_id} |
Revoke a desktop stream URL | desktop |
GET |
/sandboxes/{sandbox_id}/screenshot |
Take a screenshot | desktop |
POST |
/sandboxes/{sandbox_id}/input |
Send mouse and keyboard input | desktop |
GET |
/sandboxes/{sandbox_id}/cursor |
Get the cursor position | desktop |
Snapshots#
| Method | Path | Summary | Scope |
|---|---|---|---|
POST |
/sandboxes/{sandbox_id}/snapshots |
Create a named snapshot | snapshots:write |
GET |
/snapshots |
List snapshots | snapshots:read |
GET |
/snapshots/{snapshot_id} |
Get a snapshot | snapshots:read |
DELETE |
/snapshots/{snapshot_id} |
Delete a snapshot | snapshots:write |
Templates#
| Method | Path | Summary | Scope |
|---|---|---|---|
GET |
/templates |
List templates | templates:read |
POST |
/templates |
Create a template | templates:write |
GET |
/templates/{template_id} |
Get a template | templates:read |
DELETE |
/templates/{template_id} |
Delete a template | templates:write |
GET |
/templates/{template_id}/logs |
Get template build logs | templates:read |
Webhooks#
| Method | Path | Summary | Scope |
|---|---|---|---|
GET |
/webhooks |
List webhook endpoints | webhooks:manage |
POST |
/webhooks |
Create a webhook endpoint | webhooks:manage |
GET |
/webhooks/{webhook_id} |
Get a webhook endpoint | webhooks:manage |
PATCH |
/webhooks/{webhook_id} |
Update a webhook endpoint | webhooks:manage |
DELETE |
/webhooks/{webhook_id} |
Delete a webhook endpoint | webhooks:manage |
POST |
/webhooks/{webhook_id}/rotate-secret |
Rotate the signing secret | webhooks:manage |
POST |
/webhooks/{webhook_id}/test |
Send a test event | webhooks:manage |
GET |
/webhooks/{webhook_id}/deliveries |
List recent deliveries | webhooks:manage |
Usage#
| Method | Path | Summary | Scope |
|---|---|---|---|
GET |
/usage |
Get usage and cost | usage:read |
GET |
/sandboxes/{sandbox_id}/usage |
Get usage for one sandbox | usage:read |
GET |
/limits |
Get account limits and current consumption | usage:read |
API keys#
| Method | Path | Summary | Scope |
|---|---|---|---|
GET |
/api-keys |
List API keys | api_keys:manage |
POST |
/api-keys |
Create an API key | api_keys:manage |
GET |
/api-keys/{api_key_id} |
Get an API key | api_keys:manage |
DELETE |
/api-keys/{api_key_id} |
Revoke an API key | api_keys:manage |
POST |
/api-keys/{api_key_id}/rotate |
Rotate an API key | api_keys:manage |
Account#
| Method | Path | Summary | Scope |
|---|---|---|---|
GET |
/account |
Get the account | account:read |
PUT |
/account/spend-limit |
Set the monthly spend limit | account:manage |
Members and billing#
Used by the web console with a session, or with an API key (Teams and roles).
| Method | Path | Summary | Scope |
|---|---|---|---|
GET |
/members |
List organization members | account:read |
PATCH |
/members/{user_id} |
Change a member's role | account:manage |
DELETE |
/members/{user_id} |
Remove a member (or leave) | account:manage |
GET |
/invitations |
List invitations | account:read |
POST |
/invitations |
Invite someone to the organization | account:manage |
DELETE |
/invitations/{invitation_id} |
Revoke a pending invitation | account:manage |
GET |
/billing |
Get the billing overview | account:read |
POST |
/billing/checkout |
Upgrade to a paid plan | account:manage |
POST |
/billing/portal |
Open the billing portal | account:manage |
Console sign-in and sessions#
Cookie-based endpoints of the web console (app.dormo.dev); API keys get 403 on the session-only ones.
| Method | Path | Summary | Auth |
|---|---|---|---|
POST |
/auth/magic-link |
E-mail a sign-in link | none |
POST |
/auth/magic-link/verify |
Sign in with a magic-link token | none |
GET |
/auth/github/start |
Start GitHub sign-in | none |
GET |
/auth/github/callback |
GitHub sign-in callback | none |
POST |
/auth/logout |
Sign out | session |
GET |
/me |
Get the signed-in user | session |
PATCH |
/me |
Update the profile | session |
POST |
/me/switch-org |
Switch the session's organization | session |
GET |
/me/sessions |
List my active sessions | session |
DELETE |
/me/sessions/{session_id} |
Revoke one of my sessions | session |
POST |
/organizations |
Create a team organization | session |
POST |
/invitations/accept |
Accept an invitation | session |
Outgoing webhooks#
Events sandbox.ready, sandbox.paused, sandbox.resumed, sandbox.error, sandbox.deleted, usage.threshold (and webhook.test) are POSTed to your endpoints, signed with HMAC-SHA256 in Webhook-Signature. Defined under webhooks in the spec; see Webhooks.