API reference
The raw HTTP surface: every endpoint, every status code, every body shape, callable with curl or any HTTP client. No SDK required and no SDK code on these pages. If you work in TypeScript, Python, Go, Rust or C++, the SDK reference documents the client libraries that wrap these routes.
The clients rename fields, resolve presigned URLs for you, and synthesize a few values that never appear on the wire at all. Where that matters, these pages say so. Treat this reference as the contract; treat the SDK as one convenient consumer of it.
Contents
Base URLs
The browser product and the VM/sandbox product are separate gateways on separate hosts. One API key works on both.
| Surface | Base URL | Reference |
|---|---|---|
| Browsers | https://api.getsolari.com | Browser API |
| VMs & sandboxes | https://api.getsolari.com | VM API, Sandbox API |
Request and response bodies are JSON unless stated otherwise. Set Content-Type: application/json on any request that carries a body. The two file data-path routes on the VM gateway are the exception: they move raw application/octet-stream bytes.
Authentication
Every route takes your console API key as a bearer token. The key format is slr_live_<id>_<secret>.
GET /profiles HTTP/1.1
Host: api.getsolari.com
Authorization: Bearer slr_live_4b91ac2f_s3cr3tvalueCreate keys in the console. A key is shown once, at creation. There are four exceptions to the bearer rule:
| Surface | Credential |
|---|---|
GET /health, GET /healthz | None. Unauthenticated on both gateways. |
/ws/…, /cdp/…, /control/…, /stream/… | The signed session ID in the URL is the capability. Treat these URLs as secrets. (The VM control channel additionally takes a bearer upgrade header.) |
/files/download, /files/upload | The signed token in the query string. No Authorization header, since these URLs are handed to browsers and plain curl. |
The browser gateway caches verified bearers for 60 seconds, so a revoked key can keep authorizing for up to that long unless the control plane invalidates it first.
Errors
Body shape
Both gateways return a JSON object with a human-readable error. Some handlers add a machine-readable code; branch on the status or on code, never on the prose.
| Field | Type | Description |
|---|---|---|
error | string | Human-readable message. Not stable across releases, so do not parse it. |
code | string | Machine-readable discriminator, when the handler sets one. See code values. |
detail | string | Browser gateway only. Upstream diagnostic, truncated to 512 characters. Prose. Do not branch on it. |
message | string | VM gateway only. Alternate message field some clients read. |
retryable | boolean | VM gateway only. true marks a transient failure that an idempotent client should retry with backoff. |
{
"code": "ConcurrencyLimitExceeded",
"error": "Concurrent session limit reached (20)",
"plan": "starter",
"cap": 20
}Individual errors carry extra context fields alongside these: plan, cap, feature, supported, profileId. They are documented on the endpoints that emit them.
Code values
code | Status | Gateway | Meaning |
|---|---|---|---|
FeatureRequiresPlan | 402 | Both | The plan lacks a requested feature: stealth, proxy, captcha, desktops, custom templates. |
ConcurrencyLimitExceeded | 429 | Both | The org is at its concurrent-session cap. Not retryable. |
PlanLimitExceeded | 403 | Browser | Profile cap reached. Generated by the control plane and forwarded verbatim. |
ConcurrencyCheckUnavailable | 503 | Browser | The concurrency store is wedged. The gateway fails closed rather than let an org blow past its cap. Retryable. |
InvalidSessionId | 404 | Browser | The session ID was malformed, forged, or another org's. Nothing was released. Do not treat as success. |
NotEntitled | 403 | VM | The plan does not include desktops or sandboxes. |
InsufficientCredit | 402 | VM | Admission rejected on prepaid balance. |
TemplateKindMismatch | 400 | VM | The custom template's kind does not match the requested kind. |
TemplateNotReady | 409 | VM | The template exists but is still building, or failed. |
TemplateBuilding | 409 | VM | A template cannot be deleted while its build is running. |
SnapshotHasChildren | 409 | VM | Live VMs still descend from this snapshot. |
LocalFilesUnsupported | 400 | VM | compiled.localFiles is not implemented. Fetch files inside the build instead. |
RecordingRequiresDesktop | 400 | VM | record: true on a headless kind: "sandbox". There is no display to capture. |
RecordingRequiresGoldenBoot | 400 | VM | record: true combined with fromSnapshot or a tpl_… template. The host cannot wire recording on the restore path. |
The TypeScript SDK's error union names BrowserUnhealthy, but no gateway response ever carries it. The client synthesizes it when its own post-connect probe fails. Do not write an HTTP handler expecting it.
Retries and status conventions
| Status | Retry? | Convention |
|---|---|---|
2xx | N/A | Success. 201 on create, 202 on an async template build, 204 on browser release, 200 elsewhere, including a VM exec whose command exited non-zero. |
400, 402, 403, 404, 409, 413 | No | Deterministic. The same request will fail the same way. Fix the request, the plan, or the resource state. |
429 | No | You are at your concurrency cap. Retrying does not help. Pause or kill a session first. Neither gateway's clients retry this. |
501 | No | The feature is not configured on this deployment (volumes, port preview). Not transient. |
502, 503, 504 | Yes | Transient. An upstream was unreachable or capacity was unavailable. Retry with exponential backoff and jitter. |
On the VM gateway, a transient failure additionally carries "retryable": true in the body, which is the signal to prefer over the bare status. Send Idempotency-Key: <uuid-v4> on create routes to make that retry safe. GET and DELETE are idempotent by nature: deleting an already-gone VM still returns 200 {"ok": true}.
On the browser gateway 503 also means the concurrency store is down (ConcurrencyCheckUnavailable) or the control plane is unconfigured. On the VM gateway it also covers an unreachable admission service, since a create must fail closed rather than slip past the balance gate. All three are still worth retrying.
Sections
| Reference | Base URL | Covers |
|---|---|---|
| Browser API | api.getsolari.com | Sessions, profiles, proxy countries, replays, health, and the Playwright/CDP/observer WebSocket upgrades. |
| VM API | api.getsolari.com | The GUI desktop surface: create, status, kill, pause, resume, and the RFB stream channel. |
| Sandbox API | api.getsolari.com | The unified VM surface plus files, volumes, snapshots, templates, and the control WebSocket that carries commands, files, code execution and git. |
REST covers lifecycle only. A browser session is driven over the Playwright wire protocol or raw CDP; a VM session's commands, files, code.run and git ride the control WebSocket. Both are documented in their sections, but neither is expressible as a REST call.
End-to-end example
Create a sandbox, run a command on it over the warm HTTP fast path, then kill it. URL-encode the session ID before putting it in a path; it contains : and ..
export SOLARI_API_KEY=slr_live_…
# 1. Create. The Idempotency-Key makes a retry safe.
SANDBOX=$(curl -sS -X POST https://api.getsolari.com/sandboxes \
-H "Authorization: Bearer $SOLARI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"template":"base","cpu":2,"memMb":2048}')
ID=$(echo "$SANDBOX" | jq -r .sandboxId)
ENC=$(jq -rn --arg v "$ID" '$v|@uri')
# 2. Run one command. No shell: cmd is the binary, args its argv tail.
curl -sS -X POST "https://api.getsolari.com/sandboxes/$ENC/exec" \
-H "Authorization: Bearer $SOLARI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cmd":"python3","args":["--version"]}' | jq .
# => { "exitCode": 0, "stdout": "Python 3.11.2\n", "stderr": "" }
# 3. Kill it. Idempotent: a second DELETE also returns {"ok": true}.
curl -sS -X DELETE "https://api.getsolari.com/sandboxes/$ENC" \
-H "Authorization: Bearer $SOLARI_API_KEY"Health probes
On the shared api.getsolari.com host, the public GET /health is answered by the browser gateway and reports pool capacity (documented in full as GET /health). It is unauthenticated. The VM gateway's bare { "ok": true } liveness probes run behind the load balancer and are not exposed at this hostname: GET /healthz returns 401 here.
GET/health
Unauthenticated pool-health probe served by the browser gateway. Reports idle/busy pool counts and saturation. See the browser reference for the full body.
Example request
curl -s https://api.getsolari.com/healthExample response
{ "ok": true, "idle": 68, "busy": 2, "recycling": 0, "pools": 6, "saturated": false }GET/healthz
The VM gateway's container liveness probe. Not exposed on the public api.getsolari.com host, where it returns 401.
Example request
curl -s https://api.getsolari.com/healthz
# {"error":"Unauthorized"}For error semantics in the SDKs rather than on the wire, see Errors.
