Agent Sandboxes API
The Agent Sandboxes API lets you provision isolated gVisor sandboxes for agent workloads in Quave ONE. Sandboxes are created from versioned sandbox templates whose immutable revisions pin the image, resources, persistence mode, and lifetime limits, and Quave ONE owns the whole sandbox lifecycle: allocation, idle suspension, hard-lifetime expiry, and termination.
Availability. Agent Sandboxes are available for client regions on the Connect and Full Private plans. If your account is not on one of these plans, sandbox provisioning is not enabled for your regions — talk to Quave ONE to enable it.
Make sure to read the Get Started document to understand how the API works.
Note: These endpoints accept user tokens, and MCP or OAuth tokens carrying the scopes documented per operation. Environment tokens are not accepted, because a sandbox records the caller who requested it and an environment token identifies an environment rather than a person.
Sandboxes are account-scoped. Every request must include accountId, and the caller needs access to that account: read operations require account membership, and create or terminate operations require an admin or technical member.
Sandbox template object
A sandbox template is the named, reusable definition an agent asks for. Its current revision holds the runtime configuration.
| Field | Type | Description |
|---|---|---|
templateId | String | Template ID. |
accountId | String | Owning account ID. |
appId | String | Optional app the template belongs to. |
name | String | Human-readable template name. |
slug | String | URL-safe identifier derived from the name. |
description | String | Optional description. |
enabled | Boolean | Whether new sandboxes may be requested from this template. |
deprecatedAt | String | ISO 8601 timestamp set when the template was deprecated. Absent while active. |
currentRevisionNumber | Integer | Revision number of the latest revision. |
createdAt | String | ISO 8601 creation timestamp. |
updatedAt | String | ISO 8601 last update timestamp. |
The current revision is returned by the get-template operation:
| Field | Type | Description |
|---|---|---|
revisionId | String | Revision ID. |
templateId | String | Parent template ID. |
revision | Integer | Monotonic revision number. Revisions are immutable. |
imageDigest | String | Immutable image digest the sandbox runs. |
imageRef | String | Human-readable image reference. |
command | Array of strings | Optional entrypoint command. |
workingDir | String | Optional working directory inside the sandbox. |
ports | Array | Optional declared ports. |
resources | Object | CPU and memory request and limit values. |
persistenceMode | String | EPHEMERAL or PERSISTENT_VOLUME. |
persistentWorkspace | Object | Persistent workspace settings when the mode is PERSISTENT_VOLUME. |
isolationProfile | String | Runtime isolation profile. GVISOR in this version. |
idleTimeoutSeconds | Integer | Idle time after which Quave ONE suspends the sandbox. |
hardLifetimeSeconds | Integer | Maximum lifetime, after which Quave ONE terminates the sandbox. |
suspendedRetentionSeconds | Integer | How long a suspended workspace is retained. |
warmPool | Object | Optional warm-pool capacity with desired, min, and max. Mutually exclusive with PERSISTENT_VOLUME. |
createdAt | String | ISO 8601 creation timestamp. |
Sandbox object
| Field | Type | Description |
|---|---|---|
sandboxId | String | Sandbox ID. |
accountId | String | Owning account ID. |
appId | String | App the sandbox belongs to. |
templateId | String | Template the sandbox was created from. |
templateRevisionId | String | Exact template revision the sandbox runs. |
templateRevisionNumber | Integer | Revision number of that revision. |
region | String | Region the sandbox runs in. |
workloadProfile | String | Workload profile the sandbox was provisioned for. |
isolationProfile | String | Runtime isolation profile, denormalized from the revision. |
persistenceMode | String | Persistence mode, denormalized from the revision. |
ownerKind | String | Typed owner reference kind required by the workload profile. |
ownerId | String | ID of the owner the sandbox is attached to. |
externalSessionId | String | Optional external session identifier. |
callerKey | String | Caller provenance, in the form user:USER_ID. |
status | String | Current lifecycle status. See Sandbox statuses below. |
statusReason | String | Optional short reason for the current status. |
desiredStatus | String | Status Quave ONE is driving the sandbox toward, when different from status. |
generation | Integer | Increments on every status change. Useful to detect concurrent transitions. |
isWarmAllocation | Boolean | Whether the sandbox came from a warm pool. |
resources | Object | CPU and memory the sandbox was allocated. |
claimedAt | String | ISO 8601 timestamp when the sandbox was claimed. |
readyAt | String | ISO 8601 timestamp when the sandbox became ready. |
lastActiveAt | String | ISO 8601 timestamp of the last activity. Drives idle suspension. |
suspendedAt | String | ISO 8601 timestamp of the last suspension. |
resumedAt | String | ISO 8601 timestamp of the last resume. |
terminatedAt | String | ISO 8601 termination timestamp. |
expiresAt | String | ISO 8601 hard-lifetime deadline, derived from the revision. |
terminationReason | String | Why the sandbox was terminated. |
errorCode | String | Stable product error code when the sandbox failed. |
errorRetryable | Boolean | Whether errorCode describes a condition worth retrying. |
correlationId | String | Correlation ID for tracing the sandbox across systems. |
createdAt | String | ISO 8601 creation timestamp. |
updatedAt | String | ISO 8601 last update timestamp. |
Note:
idempotencyKeyis write-only. You send it when creating a sandbox, and it is never returned in a sandbox object.
Create or connect a sandbox
Send a POST request to /api/public/v1/sandbox/create. Requires the quave:write:deploy scope.
| Field | Type | Required | Description |
|---|---|---|---|
accountId | String | Yes | Account that owns the sandbox. |
appId | String | Yes | App the sandbox belongs to. |
templateId | String | Yes | Sandbox template to provision from. It must be enabled, not deprecated, and have at least one revision. |
workloadProfile | String | Yes | TESS_CODE_TURN, QUAVE_INTERACTIVE_AGENT, or QUAVE_AUTOMATION_RUN. |
ownerKind | String | Yes | Owner reference kind. Must be the kind required by the workload profile: EXTERNAL_SESSION, AGENT_CONVERSATION, or AGENT_RUN. |
ownerId | String | Yes | ID of the owner the sandbox is attached to. |
externalSessionId | String | No | Optional external session identifier. |
region | String | Yes | Region to run the sandbox in. |
idempotencyKey | String | Yes | Caller-chosen key that makes this call safe to retry. |
This operation is idempotent. The same account, the same caller, and the same idempotencyKey always resolve to the same sandbox:
- The first call creates the sandbox and answers
201withwasCreated: true. - Any repeated call connects to the existing sandbox and answers
200withwasCreated: false.
Retrying a create request after a network timeout is therefore safe and never provisions a second sandbox.
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"accountId": "ACCOUNT_ID",
"appId": "APP_ID",
"templateId": "TEMPLATE_ID",
"workloadProfile": "QUAVE_INTERACTIVE_AGENT",
"ownerKind": "AGENT_CONVERSATION",
"ownerId": "CONVERSATION_ID",
"region": "us-5",
"idempotencyKey": "conversation-42-turn-7"
}' \
https://api.quave.cloud/api/public/v1/sandbox/create
Example response:
{
"success": true,
"wasCreated": true,
"sandbox": {
"sandboxId": "SANDBOX_ID",
"accountId": "ACCOUNT_ID",
"appId": "APP_ID",
"templateId": "TEMPLATE_ID",
"templateRevisionId": "TEMPLATE_REVISION_ID",
"templateRevisionNumber": 3,
"region": "us-5",
"workloadProfile": "QUAVE_INTERACTIVE_AGENT",
"isolationProfile": "GVISOR",
"persistenceMode": "EPHEMERAL",
"ownerKind": "AGENT_CONVERSATION",
"ownerId": "CONVERSATION_ID",
"callerKey": "user:USER_ID",
"status": "REQUESTED",
"generation": 0,
"expiresAt": "2026-08-07T13:00:00.000Z",
"createdAt": "2026-08-07T12:00:00.000Z"
}
}
List sandboxes
Send a GET request to /api/public/v1/sandboxes. Requires the quave:read scope.
| Query parameter | Type | Description |
|---|---|---|
accountId | String | Account ID. Required. |
appId | String | Optional app filter. |
status | String | Optional status filter. Pass one status or comma-separated statuses, for example READY,BUSY. Unknown statuses are ignored. |
limit | Integer | Sandboxes per page. Default: 50. Maximum: 100. |
page | Integer | One-based page number. Default: 1. |
The response includes accountId, page, limit, totalCount, and sandboxes, sorted by creation time, newest first.
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/sandboxes?accountId=ACCOUNT_ID&status=READY,BUSY&page=1&limit=20'
Get a sandbox
Send a GET request to /api/public/v1/sandbox. Requires the quave:read scope.
| Query parameter | Type | Description |
|---|---|---|
accountId | String | Account ID. Required. |
sandboxId | String | Sandbox ID. Required. |
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/sandbox?accountId=ACCOUNT_ID&sandboxId=SANDBOX_ID'
Terminate a sandbox
Send a POST request to /api/public/v1/sandbox/terminate. Requires the quave:write:deploy scope.
| Field | Type | Required | Description |
|---|---|---|---|
accountId | String | Yes | Account ID. |
sandboxId | String | Yes | Sandbox ID. |
Termination is idempotent. Terminating a sandbox that is already terminated succeeds and returns the sandbox unchanged, so retries are safe. A sandbox that has not reached a running state yet is first marked as failed and then terminated, so no sandbox is ever left holding compute.
If the sandbox status changes concurrently while the request is being processed, Quave ONE answers 409 with Sandbox is changing state. Retry the call.
curl -X POST \
-H 'Authorization: YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"accountId": "ACCOUNT_ID",
"sandboxId": "SANDBOX_ID"
}' \
https://api.quave.cloud/api/public/v1/sandbox/terminate
Example response:
{
"success": true,
"sandbox": {
"sandboxId": "SANDBOX_ID",
"status": "TERMINATED",
"terminationReason": "USER_REQUESTED",
"terminatedAt": "2026-08-07T12:30:00.000Z"
}
}
List sandbox templates
Send a GET request to /api/public/v1/sandbox-templates. Requires the quave:read scope.
| Query parameter | Type | Description |
|---|---|---|
accountId | String | Account ID. Required. |
appId | String | Optional app filter. |
includeDeprecated | Boolean | Include deprecated templates. Default: false. |
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/sandbox-templates?accountId=ACCOUNT_ID'
Get a sandbox template
Send a GET request to /api/public/v1/sandbox-template. Requires the quave:read scope.
| Query parameter | Type | Description |
|---|---|---|
accountId | String | Account ID. Required. |
templateId | String | Template ID. Required. |
The response contains template and currentRevision. currentRevision is null when the template has no revision yet, and a template without a revision cannot serve sandboxes.
curl -X GET \
-H 'Authorization: YOUR_TOKEN' \
'https://api.quave.cloud/api/public/v1/sandbox-template?accountId=ACCOUNT_ID&templateId=TEMPLATE_ID'
Sandbox statuses
| Status | Description |
|---|---|
REQUESTED | The sandbox was requested and is waiting to be allocated. |
ALLOCATING | Quave ONE is allocating capacity for the sandbox. |
STARTING | The sandbox is starting. |
READY | The sandbox is running and idle. It can accept work. |
BUSY | The sandbox is running work. |
SUSPENDING | Quave ONE is suspending the sandbox, usually after an idle timeout. |
SUSPENDED | Compute was released. A persistent workspace is retained for the configured retention window. |
RESUMING | The sandbox is being resumed from a suspended workspace. |
FAILED | The sandbox failed. See errorCode and errorRetryable. |
TERMINATING | Termination is in progress. |
TERMINATED | Terminal status. The sandbox no longer exists and cannot be resumed. |
Quave ONE drives these transitions itself. Sandboxes are suspended after their template's idleTimeoutSeconds of inactivity and terminated when they reach expiresAt, without any call from you.
Retryable errors
Sandbox failures surface as stable product error codes rather than raw runtime messages, so you can branch on them safely. The sandbox object carries errorCode together with errorRetryable, which tells you whether waiting and retrying can succeed.
| Error code | Retryable | Description |
|---|---|---|
SANDBOX_SUSPENDED | Yes | The sandbox is suspended and must be resumed first. |
SANDBOX_NOT_READY | Yes | The sandbox is not ready to accept work yet. |
AGENT_UNREACHABLE | Yes | The sandbox runtime is temporarily unreachable. |
SANDBOX_NOT_FOUND | No | The sandbox does not exist in the runtime. |
NAMESPACE_DENIED | No | The sandbox runtime rejected the namespace. |
UNAUTHENTICATED | No | The runtime credential was not accepted. |
TOKEN_EXPIRED | No | The runtime credential expired. |
INSUFFICIENT_SCOPE | No | The runtime credential lacked the required scope. |
PATH_ESCAPE | No | A file path outside the sandbox workspace was rejected. |
UNKNOWN | No | Unclassified failure. Unmapped runtime errors fail closed to this code. |
Retryable codes are safe to retry with backoff, ideally reusing the same idempotencyKey so a retry reconnects instead of provisioning a second sandbox.
Note: Running commands, streaming output, and reading or writing files inside a sandbox are not part of this API version. This version covers the sandbox and sandbox template control plane only.