API reference
The public REST API is available at api.collimate.ai. All requests
and responses are JSON.
- Base URL:
https://api.collimate.ai - Auth:
Authorization: Bearer col_<tier>_<id>_<secret>on every endpoint exceptGET /v1/health. See Authentication. - Content type:
application/json - Machine spec:
openapi.yaml
Endpoints
Section titled “Endpoints”| Method | Path | Description |
|---|---|---|
GET | /v1/health | Liveness + version. Unauth. |
GET | /v1/images | The image catalog your key can address. |
GET | /v1/templates | Alias of /v1/images (same handler, same body). |
POST | /v1/templates | Bake your own image into a template (Pro). |
GET | /v1/templates/{id} | Bake phase + readiness for one template. |
DELETE | /v1/templates/{id} | Delete a template you created. |
POST | /v1/templates/{id}/expect | Declare the width of your next session (Pro). |
POST | /v1/templates/{id}/release | End the session; relax capacity now (Pro). |
POST | /v1/sandboxes | Create a sandbox (fork a template). |
GET | /v1/sandboxes | List your tenant's sandboxes. |
GET | /v1/sandboxes/{id} | One sandbox's info. |
POST | /v1/sandboxes/{id}/exec | Run a command in a sandbox. |
POST | /v1/sandboxes/{id}/fork | Live-fork a running sandbox into N children. |
POST | /v1/sandboxes/{id}/connect-url | Mint a scoped URL to a guest port (Pro). |
GET | /v1/sandboxes/{id}/connect | WebSocket tunnel to a guest port (the minted URL). |
POST | /v1/sandboxes/{id}/suspend | Suspend to disk (stops the meter). |
POST | /v1/sandboxes/{id}/resume | Resume a suspended sandbox. |
DELETE | /v1/sandboxes/{id} | Destroy a sandbox. |
GET | /v1/usage | Aggregated usage for your tenant. |
GET /v1/health
Section titled “GET /v1/health”Liveness and version. No authentication required.
{ "status": "ok", "version": "1.4.2" }version is the API build stamp.
GET /v1/images
Section titled “GET /v1/images”The image catalog: everything your key can create sandboxes from — the
shared public catalog plus templates your tenant baked. GET /v1/templates is
a working alias (same handler, same body); the list is wrapped in a
templates object on the wire.
{ "templates": [ { "id": "python", "name": "python", "visibility": "public", "tier": "demo", "description": "Python 3, ready to run" } ]}| Field | Type | Description |
|---|---|---|
id | string | Id to pass as template on create. |
name | string | Display name. |
description | string | What the image is / what's preloaded. |
visibility | string | public (shared catalog) or private (yours). |
tier | string | The plan tier the image runs on, when applicable. |
On the managed cloud only python ships today; more runtimes are on the
roadmap, and bring-your-own images are a Pro capability (see
Templates).
POST /v1/templates · GET /v1/templates/{id} · DELETE /v1/templates/{id}
Section titled “POST /v1/templates · GET /v1/templates/{id} · DELETE /v1/templates/{id}”Bring your own image (Pro): POST /v1/templates bakes an OCI image into a
ready-state template. Body: { "name", "image", "ready"?, "width"?, "expected_width"? } — width is the fan-out you expect (a hint, not a knob),
expected_width pre-warms for your first rollout, and ready declares the
ready-state check ({cmd?, http?, http_timeout_s?, ports?, env?, network?}).
Idempotent — safe to retry.
GET /v1/templates/{id} returns the bake phase
(Pending → Converting → Distributing → Live) and readiness telemetry;
DELETE /v1/templates/{id} removes a template you created (403 forbidden for shared catalog rows, 409 template_in_use while sandboxes
still run on it).
See Templates and the Pro quickstart for
the SDK flow (Template.bake, client.env).
POST /v1/templates/{id}/expect · .../release
Section titled “POST /v1/templates/{id}/expect · .../release”The explicit session lifecycle (Pro): expect declares the width your next
rollout session will fan out — body { "width": 512 } — so capacity is ready
before the burst lands; release ends the session so capacity relaxes
immediately instead of decaying. Both are optional (the platform also learns
from observed demand), idempotent, and only valid on templates you created.
The SDK wraps the pair as client.env(ref, width=N) — a context manager that
releases on exit.
POST /v1/sandboxes
Section titled “POST /v1/sandboxes”Create a sandbox by forking a template. Alias: POST /v1/sessions.
Request — CreateSandboxRequest:
{ "template": "python", "label": "grpo-step-14", "labels": { "run": "grpo-step-14" }, "egress": { "allow": ["pypi.org"] }}| Field | Type | Required | Description |
|---|---|---|---|
template | string | yes | Template to fork. |
label | string | no | Convenience single label. |
labels | object | no | Arbitrary string labels. |
egress | object | no | Per-sandbox egress policy. |
Response 201 — CreateSandboxResponse:
{ "id": "sbx_9f2a1c7b3e40", "template": "python", "state": "running", "fork_time_ms": 2.4, "create_time_ms": 3.1, "endpoints": {}}fork_time_ms/create_time_ms are per-call measurements, not guarantees; see
the published benchmarks for the distribution.
endpoints maps each exposed guest port to a connect hint
("<port>": "<hint>") — not a raw dialable host address. Turn one into a usable
URL with POST …/connect-url or the SDK's
connect_url / cdp_url / port_forward. Empty unless the template exposes
ports.
Status: 201 created · 400 bad_request · 401 unauthorized ·
403 forbidden · 404 unknown template · 429 rate_limited /
quota_exceeded · 503 at_capacity (retry with backoff).
GET /v1/sandboxes · GET /v1/sandboxes/{id}
Section titled “GET /v1/sandboxes · GET /v1/sandboxes/{id}”GET /v1/sandboxes lists your tenant's sandboxes, wrapped in a sandboxes
object:
{ "sandboxes": [ { "id": "sbx_9f2a1c7b3e40", "template": "python", "state": "running" } ]}GET /v1/sandboxes/{id} returns a single SandboxInfo:
{ "id": "sbx_9f2a1c7b3e40", "template": "python", "state": "running", "age_secs": 42.5, "idle_secs": 1.2, "exec_count": 7, "labels": { "run": "grpo-step-14" }, "endpoints": {}}404 not_found if the sandbox doesn't exist or isn't yours. endpoints is the
same per-exposed-port connect-hint map as on create —
resolve a port via connect-url.
POST /v1/sandboxes/{id}/exec
Section titled “POST /v1/sandboxes/{id}/exec”Run a command in an existing sandbox. Execs are serialized within a sandbox; files and processes persist between calls.
Request — ExecRequest (see Running code for the full field
table):
{ "files": [{ "path": "solve.py", "content": "print(6 * 7)" }], "command": "python3 solve.py", "timeout_seconds": 30, "seed": "c0ffee…"}Response 200 — ExecResponse:
{ "id": "sbx_9f2a1c7b3e40", "stdout": "42\n", "stderr": "", "exit_code": 0, "fork_time_ms": 0.0, "exec_time_ms": 9.7, "total_time_ms": 10.2, "seed": "c0ffee…", "seed_ack": "verified"}Status: 200 · 400 bad_request · 404 not_found · 409 busy (in-flight
exec) · 413 payload too large · 429.
POST /v1/sandboxes/{id}/fork
Section titled “POST /v1/sandboxes/{id}/fork”Live-fork a running sandbox into count copy-on-write
children that keep the parent's process memory. The parent stays running.
Request — ForkRequest:
{ "count": 8, "labels": ["branch-a", "branch-b"] }| Field | Type | Description |
|---|---|---|
count | integer | Children to fork (default 1, bounded per request). |
labels | string[] | Optional per-child labels. |
Response 200 — ForkResponse:
{ "parent": "sbx_9f2a1c7b3e40", "children": [ { "id": "sbx_1a4f8c", "endpoints": {} }, { "id": "sbx_2b5e9d", "endpoints": {} } ]}Status: 200 · 404 not_found · 409 busy (parent has an in-flight exec) ·
503 at_capacity (retry with backoff).
Each child's endpoints is the same connect-hint map as on
create — resolve a port with
connect-url.
POST /v1/sandboxes/{id}/connect-url
Section titled “POST /v1/sandboxes/{id}/connect-url”Mint a scoped, time-boxed URL to a port inside a running sandbox — the
connect-from-outside primitive
for driving an interactive in-guest service (a browser, a preview server, a
notebook kernel, an OpenEnv env-server) from your own client code.
Pro/Enterprise only — a Demo key gets 403 demo_tier_limit.
Request — ConnectUrlRequest:
{ "port": 9222, "ttl_seconds": 600, "protocol": "cdp" }| Field | Type | Required | Description |
|---|---|---|---|
port | integer | yes | A guest port from the template's exposed ports. |
ttl_seconds | integer | yes | URL lifetime in seconds. Clamped down to 3600 (1h). |
protocol | string | no | tcp (default) — a raw byte tunnel for any TCP (HTTP, WebSocket, Postgres, a preview or env-server). cdp — a DevTools-framed relay a CDP client (connect_over_cdp) consumes directly. |
Response 200 — ConnectUrlResponse:
{ "url": "wss://api.collimate.ai/v1/sandboxes/sbx_9f2a1c7b3e40/connect?port=9222&protocol=cdp&t=…", "expires_at": "2026-07-17T00:10:00Z"}Open url directly, or hand it to connect_over_cdp; the SDK wraps this as
connect_url(port) / cdp_url() / port_forward(port) (see
connect-from-outside). The token
is HMAC-scoped to (tenant, sandbox, port, exp) and is revoked when the sandbox
is deleted.
Status: 200 · 401 unauthorized · 403 demo_tier_limit (below Pro) ·
404 not_found (unknown or foreign sandbox) · 409 port_not_exposed (the
port is not in the template's exposed ports).
GET /v1/sandboxes/{id}/connect
Section titled “GET /v1/sandboxes/{id}/connect”The WebSocket the minted URL points at:
/v1/sandboxes/{id}/connect?port=<p>[&protocol=cdp]&t=<token>. A WS upgrade
whose frames are proxied to the guest port; the guest must be listening.
Authenticate with the scoped ?t= token from connect-url or a
Bearer col_* key — same Pro/Enterprise gate and ownership check.
Clients don't normally call this directly — open the minted url, pass it to
connect_over_cdp, or use the SDK's port_forward.
Status: 101 switching protocols (upgrade) · 401 unauthorized ·
403 demo_tier_limit · 404 not_found · 409 port_not_exposed (the port
is not in the template's exposed ports).
POST /v1/sandboxes/{id}/suspend · .../resume
Section titled “POST /v1/sandboxes/{id}/suspend · .../resume”Suspend snapshots a running sandbox to disk and stops the meter; resume
restores it. Both return a StateResponse:
{ "id": "sbx_9f2a1c7b3e40", "state": "suspended", "mem_bytes": 41943040 }mem_bytes (suspend) is the on-disk memory image size. See
Suspend & resume.
DELETE /v1/sandboxes/{id}
Section titled “DELETE /v1/sandboxes/{id}”Destroy a sandbox and reclaim its resources.
{ "deleted": true, "id": "sbx_9f2a1c7b3e40" }404 if it was already gone.
GET /v1/usage
Section titled “GET /v1/usage”Aggregated usage for your tenant over a window. Query params from and to
(RFC 3339).
{ "from": "2026-07-01T00:00:00Z", "to": "2026-07-02T00:00:00Z", "totals": { "cpu_core_hours": 12.4, "ram_gb_hours": 24.8, "disk_gb_hours": 3.1, "snapshot_gb_months": 0.05 }}Error envelope
Section titled “Error envelope”Every error carries a stable machine code plus an HTTP status:
{ "code": "quota_exceeded", "message": "concurrent sandbox limit reached" }