Long-lived workspace / desktop sessions
Every request needs a credential — see Authentication.
| Method | Path | Summary |
|---|
GET | /sandbox/sandboxes/v1 | List Sandboxes |
POST | /sandbox/sandboxes/v1 | Create Sandbox |
POST | /sandbox/sandboxes/v1/_reap | Reap Sandboxes |
DELETE | /sandbox/sandboxes/v1/{sandbox_id} | Destroy Sandbox |
GET | /sandbox/sandboxes/v1/{sandbox_id} | Get Sandbox |
GET | /sandbox/sandboxes/v1/{sandbox_id}/artifacts | List Sandbox Artifacts |
GET | /sandbox/sandboxes/v1/{sandbox_id}/artifacts/{capture_id} | Get Sandbox Artifact |
POST | /sandbox/sandboxes/v1/{sandbox_id}/capture | Capture Sandbox |
GET | /sandbox/sandboxes/v1/{sandbox_id}/events | SSE sandbox lifecycle events |
POST | /sandbox/sandboxes/v1/{sandbox_id}/exec | Exec Sandbox |
DELETE | /sandbox/sandboxes/v1/{sandbox_id}/files | Delete File |
GET | /sandbox/sandboxes/v1/{sandbox_id}/files | Get File |
PUT | /sandbox/sandboxes/v1/{sandbox_id}/files | Put File |
POST | /sandbox/sandboxes/v1/{sandbox_id}/start | Start Sandbox |
POST | /sandbox/sandboxes/v1/{sandbox_id}/stop | Stop Sandbox |
POST | /sandbox/sandboxes/v1/{sandbox_id}/verify | Verify Sandbox |
GET | /sandbox/sandboxes/v1/{sandbox_id}/viewer | Mint viewer URL or proxy the desktop viewer UI |
POST | /sandbox/sandboxes/v1/{sandbox_id}/viewer/actions | Upload the viewer action log (clicks, scrolls, keys, searches) |
POST | /sandbox/sandboxes/v1/{sandbox_id}/viewer/annotations | Submit a task-ambiguity label for the session |
POST | /sandbox/sandboxes/v1/{sandbox_id}/viewer/recordings | Upload a viewer session screen recording |
POST | /sandbox/sandboxes/v1/{sandbox_id}/viewer/screenshots | Upload a screenshot captured from the viewer canvas |
POST | /sandbox/sandboxes/v1/{sandbox_id}/viewer/start | Start Viewer |
POST | /sandbox/sandboxes/v1/{sandbox_id}/viewer/stop | Stop Viewer |
POST | /sandbox/sandboxes/v1/{sandbox_id}/viewer/token | Remint a fresh signed viewer URL (token refresh) |
POST | /sandbox/sandboxes/v1/{sandbox_id}/workspace/sync | Sync tenant GCS workspace ↔ guest SandboxWorkspace |
POST | /sandbox/sandboxes/v1/images/build | Build-or-resolve a content-addressed desktop image |
Accepted on every endpoint on this page.
| Name | In | Type | Required | Description |
|---|
x-correlation-id | header | string | no | End-to-end correlation id for this eval run. When set, the gateway and downstream services honor it verbatim in logs, Cloud Trace span attributes, async job payloads, and Langfuse trace.id. When omitted, the gateway generates a ULID and echoes it on the response. |
X-Sandbox-Project-Id | header | string | null | no | Project to bill and rate-limit this request against. Validated against the credential: a project-scoped token may only name its own project, and gets 403 otherwise. Defaults to the token's project when omitted. |
X-Sandbox-Tenant-Id | header | string | null | no | Tenant for this request. Must match the credential's tenant when the credential carries one. |
GET /sandbox/sandboxes/v1
| Name | In | Type | Required | Description |
|---|
task_id | query | string | null | no | |
reuse_fingerprint | query | string | null | no | Filter by create-intent fingerprint (sha256:…). |
status | query | string | null | no | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1
application/json, required — CreateSandboxRequest
| Field | Type | Required | Description |
|---|
profile | string | no | Default: "sandboxes-default". |
source | object | null | no | |
image_ref | string | null | no | Immutable pin: OCI repo@sha256:<digest> / content-hash tag under SANDBOX_HARBOR_TASK_REPO (desktop-osworld / images/build). Floating tags rejected. |
autostop_sec | number | null | no | |
ttl_sec | number | null | no | |
linked_job_id | string | null | no | |
task_id | string | null | no | Optional Gen Studio / CTP task id for labels and episode metadata; when omitted, sandbox_id is used. |
reuse | boolean | no | When true, return an existing non-destroyed sandbox whose create fingerprint matches (owner salt + tenant/project + profile + image_ref + workspace_key + purpose/template labels) instead of provisioning a new session. Default false so pass@k / episode reset stay create-always. Default: false. |
reuse_salt | string | null | no | Optional client nonce mixed into the fingerprint salt. Rotate to force a fresh VM while keeping the same task_id. Required for fingerprinting when the caller has no Keycloak owner principal. |
labels | object | no | Consumer tags for analytics (trainer, pass, program, …). |
| Status | Description | Body |
|---|
201 | Successful Response | any |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/_reap
Operator hook: stop/destroy idle sandboxes for this scope.
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
DELETE /sandbox/sandboxes/v1/{sandbox_id}
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
| Status | Description | Body |
|---|
204 | Successful Response | — |
422 | Validation Error | HTTPValidationError |
GET /sandbox/sandboxes/v1/{sandbox_id}
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
GET /sandbox/sandboxes/v1/{sandbox_id}/artifacts
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
kind | query | string | null | no | |
limit | query | integer | no | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
GET /sandbox/sandboxes/v1/{sandbox_id}/artifacts/{capture_id}
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
capture_id | path | string | yes | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/capture
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
GET /sandbox/sandboxes/v1/{sandbox_id}/events
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
Last-Event-ID | header | string | null | no | |
| Status | Description | Body |
|---|
200 | Successful Response | any |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/exec
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
application/json, required — ExecRequest
| Field | Type | Required | Description |
|---|
command | string[] | yes | |
cwd | string | null | no | |
timeout_sec | number | null | no | |
env | object | null | no | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
DELETE /sandbox/sandboxes/v1/{sandbox_id}/files
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
path | query | string | yes | |
| Status | Description | Body |
|---|
204 | Successful Response | — |
422 | Validation Error | HTTPValidationError |
GET /sandbox/sandboxes/v1/{sandbox_id}/files
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
path | query | string | yes | |
| Status | Description | Body |
|---|
200 | Successful Response | any |
422 | Validation Error | HTTPValidationError |
PUT /sandbox/sandboxes/v1/{sandbox_id}/files
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
path | query | string | yes | |
application/json, required
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/start
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/stop
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/verify
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
application/json, required — VerifyRequest
| Field | Type | Required | Description |
|---|
require_verifier_evidence | boolean | no | Default: false. |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
GET /sandbox/sandboxes/v1/{sandbox_id}/viewer
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
| Status | Description | Body |
|---|
200 | Successful Response | any |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/viewer/actions
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
application/json, required — ActionLogRequest
| Field | Type | Required | Description |
|---|
events | ActionEvent[] | yes | |
recording_id | string | null | no | Recording this log is time-aligned with. |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/viewer/annotations
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
application/json, required — AmbiguityAnnotation
| Field | Type | Required | Description |
|---|
clarity | "clear" | "ambiguous" | yes | |
ambiguity_types | "intent" | "referential" | "parametric" | "cross_modal"[] | no | Required when clarity is 'ambiguous'. |
severity | "low" | "medium" | "high" | "blocking" | null | no | |
risk | "low" | "medium" | "high" | null | no | |
feasibility | "feasible" | "partially_feasible" | "infeasible" | "unknown" | null | no | |
alternate_answers | string[] | no | Other defensible answers a solver could have given. |
root_of_confusion | string | null | no | What in the task made it ambiguous. |
task_prompt | string | null | no | Instruction that was annotated. |
notes | string | null | no | |
annotator | string | null | no | |
recording_id | string | null | no | |
actions_id | string | null | no | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/viewer/recordings
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
duration_sec | query | number | null | no | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/viewer/screenshots
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/viewer/start
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
| Status | Description | Body |
|---|
200 | Successful Response | ViewerDescriptor |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/viewer/stop
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/viewer/token
Issue a new viewer descriptor when the previous signed token is near expiry.
Requires API key / Keycloak / team token — not an expired viewer cookie.
Gen Studio should call this and replace the iframe src with the new url.
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
| Status | Description | Body |
|---|
200 | Successful Response | ViewerDescriptor |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/{sandbox_id}/workspace/sync
| Name | In | Type | Required | Description |
|---|
sandbox_id | path | string | yes | |
application/json, required — WorkspaceSyncRequest
| Field | Type | Required | Description |
|---|
direction | "pull" | "push" | yes | pull = GCS→guest SandboxWorkspace; push = guest→GCS snapshot. |
| Status | Description | Body |
|---|
200 | Successful Response | object |
422 | Validation Error | HTTPValidationError |
POST /sandbox/sandboxes/v1/images/build
Publish a curated desktop image via the platform BuildKit pool.
Pass catalog desktop_app ids in apps or a desktop_app_bundle id
in bundle (see GET /sandbox/v1/catalog?kind=desktop_app and
kind=desktop_app_bundle). The service generates the Dockerfile from
platform-owned recipes — callers cannot supply arbitrary build instructions.
The returned image_uri is an immutable content-hash tag under
SANDBOX_HARBOR_TASK_REPO. Pass it as image_ref on
POST /v1 (expert session or trainer boot from a CTP task seed).
MVP note: this handler is synchronous; cold BuildKit builds can take
minutes. The gateway routes /images/build onto
SANDBOX_LONG_PROXY_TIMEOUT_SEC so the ordinary 120s proxy budget
does not cut cold builds. Prefer content-addressed cache hits
(cached=true); async 202 + poll is follow-up work.
application/json, required — BuildDesktopImageRequest
| Field | Type | Required | Description |
|---|
profile | string | no | Default: "desktop-ubuntu". |
apps | string[] | null | no | Catalog desktop_app ids (e.g. chrome). Mutually exclusive with bundle. |
bundle | string | null | no | Catalog desktop_app_bundle id (e.g. osworld). Mutually exclusive with apps. |
datapoint_id | string | null | no | Optional builder tag prefix; defaults to sandboxes-{profile}. |
| Status | Description | Body |
|---|
200 | Successful Response | BuildDesktopImageResponse |
422 | Validation Error | HTTPValidationError |
The object shapes referenced above.
One operator interaction inside the viewer.
| Field | Type | Required | Description |
|---|
at | number | yes | Milliseconds since the recording started. |
type | string | yes | click, scroll, key, search, screenshot… |
x | integer | null | no | |
y | integer | null | no | |
delta_x | number | null | no | |
delta_y | number | null | no | |
key | string | null | no | |
text | string | null | no | |
detail | object | null | no | |
| Field | Type | Required | Description |
|---|
events | ActionEvent[] | yes | |
recording_id | string | null | no | Recording this log is time-aligned with. |
Task-ambiguity label for a session, with its evidence references.
| Field | Type | Required | Description |
|---|
clarity | "clear" | "ambiguous" | yes | |
ambiguity_types | "intent" | "referential" | "parametric" | "cross_modal"[] | no | Required when clarity is 'ambiguous'. |
severity | "low" | "medium" | "high" | "blocking" | null | no | |
risk | "low" | "medium" | "high" | null | no | |
feasibility | "feasible" | "partially_feasible" | "infeasible" | "unknown" | null | no | |
alternate_answers | string[] | no | Other defensible answers a solver could have given. |
root_of_confusion | string | null | no | What in the task made it ambiguous. |
task_prompt | string | null | no | Instruction that was annotated. |
notes | string | null | no | |
annotator | string | null | no | |
recording_id | string | null | no | |
actions_id | string | null | no | |
Build-or-resolve a content-addressed desktop image from curated catalog apps.
| Field | Type | Required | Description |
|---|
profile | string | no | Default: "desktop-ubuntu". |
apps | string[] | null | no | Catalog desktop_app ids (e.g. chrome). Mutually exclusive with bundle. |
bundle | string | null | no | Catalog desktop_app_bundle id (e.g. osworld). Mutually exclusive with apps. |
datapoint_id | string | null | no | Optional builder tag prefix; defaults to sandboxes-{profile}. |
| Field | Type | Required | Description |
|---|
image_uri | string | yes | |
content_hash | string | yes | |
cached | boolean | yes | |
profile | string | yes | |
apps | string[] | no | |
| Field | Type | Required | Description |
|---|
profile | string | no | Default: "sandboxes-default". |
source | object | null | no | |
image_ref | string | null | no | Immutable pin: OCI repo@sha256:<digest> / content-hash tag under SANDBOX_HARBOR_TASK_REPO (desktop-osworld / images/build). Floating tags rejected. |
autostop_sec | number | null | no | |
ttl_sec | number | null | no | |
linked_job_id | string | null | no | |
task_id | string | null | no | Optional Gen Studio / CTP task id for labels and episode metadata; when omitted, sandbox_id is used. |
reuse | boolean | no | When true, return an existing non-destroyed sandbox whose create fingerprint matches (owner salt + tenant/project + profile + image_ref + workspace_key + purpose/template labels) instead of provisioning a new session. Default false so pass@k / episode reset stay create-always. Default: false. |
reuse_salt | string | null | no | Optional client nonce mixed into the fingerprint salt. Rotate to force a fresh VM while keeping the same task_id. Required for fingerprinting when the caller has no Keycloak owner principal. |
labels | object | no | Consumer tags for analytics (trainer, pass, program, …). |
| Field | Type | Required | Description |
|---|
command | string[] | yes | |
cwd | string | null | no | |
timeout_sec | number | null | no | |
env | object | null | no | |
| Field | Type | Required | Description |
|---|
detail | ValidationError[] | no | |
| Field | Type | Required | Description |
|---|
loc | string | integer[] | yes | |
msg | string | yes | |
type | string | yes | |
input | any | no | |
ctx | object | no | |
| Field | Type | Required | Description |
|---|
require_verifier_evidence | boolean | no | Default: false. |
Signed desktop viewer entry returned on running desktop/ide sandboxes.
| Field | Type | Required | Description |
|---|
url | string | yes | Gateway URL to open the viewer (includes signed token). |
mode | "novnc" | "guacamole" | "provider" | "selkies-ws" | "openvscode" | yes | Integration mode for the external viewer_ui library. |
embed | string | no | Default: "iframe". |
input_enabled | boolean | yes | False while the sandbox is not in running status. |
resolution | integer[] | no | |
expires_at | integer | yes | Unix epoch seconds when the signed viewer token in url expires. Remint via POST /v1/{sandbox_id}/viewer/token before this time. |
guest_username | string | null | no | Guest OS login name when the provider exposes identity. |
guest_hostname | string | null | no | Guest OS hostname shown in the desktop shell. |
guest_home | string | null | no | Guest home directory. |
workspace_mount | string | null | no | Guest path for tenant workspace files (SandboxWorkspace). |
stream_url | string | null | no | Optional C1 gateway WebSocket path for pixels/input (/sandbox/stream/v1/{id}/websockify?ticket=…). |
| Field | Type | Required | Description |
|---|
direction | "pull" | "push" | yes | pull = GCS→guest SandboxWorkspace; push = guest→GCS snapshot. |
Machine-readable spec: OpenAPI YAML.