/workspace directory.
Which provider backs it depends on the MKA1 deployment you call, and the create response reports it as provider: runner-docker, runner-firecracker, runner-process, or in-memory.
The Responses API runs its shell and code_interpreter tools on these same sessions, so you do not need this API to let a model execute code.
Build an agent with memory stores shows the shell tool in use, and Sandbox sessions and the Responses API explains when to call this API yourself.
Before you start
You need:
The CLI command reference documents no sandbox commands, so this guide shows the SDKs and curl.
How a session works
Kinds
Size and image
resource_class is optional, and medium is the only value the API accepts today.
runtime_profile picks the image: standard (default) or eval-python, the profile the evals service uses for Python graders.
Each session record reports the memory it reserved as sandbox_memory_mib.
Lifetime and states
ttl_seconds (default 600) is an idle timeout, not a total lifetime.
Every command or code run renews the session’s lease, and the container is reclaimed only after ttl_seconds pass with no activity.
When that happens the session becomes stopped, but the workspace survives: files persist for 7 days across container restarts, and the record’s workspace_expires_at says when they go.
The next command re-provisions the container and rehydrates the workspace, which takes several seconds, so raise ttl_seconds for interactive workloads with long pauses between commands.
Creating a session is idempotent on session_id.
If a session with that id already exists, the request reuses it when it is running or idle, resumes it when it is stopped, and purges and recreates it when it is failed or terminated.
The session token
Create returns asession_token next to the session record.
It is a credential for that one session, and it can be null on deployments that authorize on the API key and tenancy alone.
When it is present, pass it back on every later call: as session_token in the body of command, code, and terminate requests, and as the session_token query parameter on workspace and browser-URL requests.
Keep it on your server. Never send it to front-end code.
X-On-Behalf-Of works the same way. If you set it on create, send the same header on every later call for that session: sessions are scoped to the caller that created them, and this guide’s later snippets omit the header only to keep them short.
Step 1 - Create a session
user_id, org_id, team_id, and api_key_id, plus the memory_mounts, sandbox_features, and session_key you set.
Create request fields (the record echoes all of them except queue_if_full):
200 returns the session, which may still be provisioning; 202 means it is queued.
Step 2 - Wait for it to be ready
Read the record untilstatus is running or idle.
Step 3 - Run a command
command is the program and args its arguments.
The command runs in /workspace unless you set cwd, with any extra env you pass, and is killed after timeout_seconds (default 60).
stdout, stderr, exit_code, the files_changed under the workspace, and resource_usage (reserved_memory_mib, peak_memory_mib, memory_pressure, oom_killed).
A non-zero exit code is a normal 200 response, not an HTTP error, so check exit_code yourself.
The Python SDK takes the path parameter as session_id_param and the body field as session_id; pass both.
Step 4 - Run code
run_code takes source text and a runtime, so you do not have to write the code to a file first, and returns the same result shape as a command.
runtime accepts these values:
runtime defaults to python.
cwd, env, and timeout_seconds work as they do for commands.
Step 5 - Upload and download files
Workspace paths are relative to/workspace: uploading to input.csv places the file at /workspace/input.csv, and a command reads it there.
Single files travel as raw bytes with Content-Type: application/octet-stream; archives are covered below.
{"status": "uploaded"}; downloads answer with the raw bytes.
The Python SDK returns a download as a streamed httpx.Response, so call read() to get the bytes.
Archives
For many files at once, move a zip instead of one file at a time.Step 6 - Inspect the workspace
The manifest lists every file in the workspace with its size and etag.Step 7 - Terminate
Sessions are billed by elapsed time, so terminate one as soon as you are done with it rather than waiting for the idle timeout to reclaim the container.status set to terminated.
Pass expected_session_kind if you want the call to refuse a session of a different kind than the one you meant to stop.
Browser sessions
Create withsession_kind: "browser" and the session runs headless Chrome instead of a command sandbox.
Three operations are specific to it:
Insert subpaths before the query string, as in
/ports/9222/json/version?session_token=....
Standard sessions expose no public URL: browser-url answers 501 for them, so use the command, code, and workspace operations instead.
running as in Step 2 before asking for the URL, and terminate it as in Step 7 when you are done.
The C# tab stops at the URL; the proxy call takes the same session id, port, and token.
Sandbox sessions and the Responses API
When a response uses theshell or code_interpreter tool, the gateway creates a sandbox session on your behalf, runs the model’s commands in it, and returns the output to the model.
It creates that session with queue_if_full: true and its own default idle timeout.
The gateway names these sessions so that the same caller reuses the same sandbox across responses.
The shell tool reuses the container_reference.container_id from its tool definition when there is one, and code_interpreter reuses its container string; without either, the gateway derives a key from the API key’s user and the end user.
To find them, list sessions and match on session_key.
The tools create these sessions as the end user in X-On-Behalf-Of when that header is present, and as the API key’s user otherwise, so a later request reuses them only when it carries the same header.
A key that drives either tool needs both read:sandbox and write:sandbox: the flow polls session state and downloads files as well as running commands.
Let a response create the session when the model should decide what to run.
Call the sandbox API yourself when:
- Your own code decides what runs, such as a fixed pipeline or a CI-style job.
- You need to move files in and out without spending a model turn on it.
- You want to control the session’s id,
ttl_seconds, orruntime_profile. - You need a browser session.
shell tool as environment: { type: "container_reference", container_id: "<session-id>" }; the response runs its commands in your workspace, and you download the results afterwards.
The session must belong to the same caller: send the same X-On-Behalf-Of header you created it with, since sessions are scoped to the organization, team, and user that created them.
Usage
GET /api/v1/sandbox/usage aggregates sandbox activity over a time range: session starts, stops, and terminations, command and code runs, and workspace transfers.
Buckets are calendar-aligned in UTC. start_time is snapped down and end_time snapped up to the nearest bucket_width boundary (1m, 1h, or 1d), so the first and last buckets can extend slightly past the range you asked for.
Callers see their own usage, organization admins see their team’s, and cluster admins can pass all_orgs or org_ids.
num_operations, num_sessions, request_count, duration_ms, workspace_bytes_in, workspace_bytes_out, files_changed_count, oom_killed_count, peak_memory_mib, and reserved_memory_mib, plus whichever grouping fields you asked for (operation, session_kind, provider, user_id, api_key_id, org_id).
The operation values are session_start, session_stop, session_terminate, turn, repo_task, command, code, workspace_manifest, workspace_download, workspace_archive_download, workspace_upload, and workspace_archive_upload.
Usage counts activity; it does not price it.
Spend in currency comes from the budgeting API’s cost endpoint (sdk.usage.costs, GET /api/v1/budgeting/usage/costs), which is admin-only.
For cluster admins
Cluster-admin bearer required.
getPricing and setPricing answer 403 to any other caller.
Spend for your own organization comes from sdk.usage.costs instead.hours × (per_hour + reserved_memory_GiB × per_gib_hour) against a rate card keyed by SKU: standard, browser, and eval-python.
A SKU with no configured rate accrues no spend.
Shown for the TypeScript SDK and curl; the other SDKs expose the same two operations.
setPricing (PUT /api/v1/sandbox/pricing, cluster admins only) replaces the whole card: SKUs you omit become unpriced.
The new rates apply from the next meter window without a restart.
Troubleshooting
API reference
For the full request and response schemas, open the Sandbox API groups (Sessions, Execution, Workspace, Sandbox Usage, Sandbox Pricing) and the Browser group in the API Reference.See also
- Build an agent with memory stores - the
shelltool with memory stores mounted into the sandbox. - Python graders - grading code the evals service runs in a sandbox.
- Usage auditing - the sandbox command dashboards and log views in SigNoz.