> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mka1.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Run code in a sandbox

> Create a sandbox session, run commands and code in its workspace, move files in and out, hand it to a response, and terminate it when you are done.

Use a sandbox session when a program of yours, rather than a model, needs to run commands or code on the platform's hardware: a cleanup script an agent wrote, a test suite against a file a user uploaded, or a build step inside an automation.
A session is an isolated execution environment with a persistent `/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](/docs/generate-a-response) 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](/docs/build-an-agent-with-memory-stores) shows the `shell` tool in use, and [Sandbox sessions and the Responses API](#sandbox-sessions-and-the-responses-api) explains when to call this API yourself.

## Before you start

You need:

| Requirement    | Notes                                                                                                                                                                                                                                     |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| API key        | Send it as `Authorization: Bearer <mka1-api-key>` on every request.                                                                                                                                                                       |
| Sandbox scopes | `write:sandbox` creates sessions and runs anything in them; `read:sandbox` reads session records, workspace listings, downloads, and usage. Both are ordinary member scopes, so any organization member can mint a key that carries them. |
| A session id   | You name sessions yourself. `session_id` is a string of 1 to 255 characters, and the API applies no other pattern.                                                                                                                        |

The [CLI command reference](/docs/cli/commands) documents no sandbox commands, so this guide shows the SDKs and curl.

## How a session works

### Kinds

| `session_kind`       | What you get                                                                                                                                                                                                                                                              |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `standard` (default) | A command and code sandbox with a `/workspace` directory. Everything in this guide applies to it.                                                                                                                                                                         |
| `browser`            | A session that runs headless Chrome and exposes its DevTools Protocol endpoint through the gateway. It requires a Firecracker-backed deployment; other deployments reject the request with `501 browser_sessions_not_enabled`. See [Browser sessions](#browser-sessions). |
| `coding_harness`     | A managed coding-agent session that the console's Coding Harness creates and drives. Do not create these through the API.                                                                                                                                                 |

### 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](/docs/evals-python-graders) 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`.

| Status                    | Meaning                                                                                                                                                                |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `queued`                  | Waiting for runner capacity. Only sessions created with `queue_if_full: true` enter this state; otherwise a full runner pool returns `503 sandbox_capacity_exhausted`. |
| `pending`, `provisioning` | The container is being allocated and started. Poll until it is `running` or `idle`.                                                                                    |
| `running`, `idle`         | The container is up and accepts commands.                                                                                                                              |
| `stopped`                 | The idle timeout passed and the container was reclaimed. The workspace is kept until `workspace_expires_at`, and the next command brings the session back.             |
| `failed`                  | Provisioning or the runtime failed.                                                                                                                                    |
| `terminated`              | You terminated it. The record stays readable.                                                                                                                          |

### The session token

Create returns a `session_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

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  import { SDK } from '@meetkai/mka1';

  const sdk = new SDK({
    bearerAuth: 'Bearer <mka1-api-key>',
  });

  const created = await sdk.sandbox.create({
    xOnBehalfOf: '<end-user-id>', // optional — attribute the request to one of your end users
    createSessionRequest: {
      sessionId: 'docs-demo-session',
      sessionKind: 'standard',
      ttlSeconds: 600,
    },
  });

  const sessionId = created.session.sessionId;
  const sessionToken = created.sessionToken;
  console.log(created.session.status, created.provider);
  ```

  ```csharp C# SDK theme={null}
  using MeetKai.MKA1;
  using MeetKai.MKA1.Types.Components;
  using System.Text;

  var sdk = new SDK(bearerAuth: "Bearer <mka1-api-key>");

  var created = await sdk.Sandbox.CreateAsync(
      new CreateSessionRequest
      {
          SessionId = "docs-demo-session",
          SessionKind = SessionKind.Standard,
          TtlSeconds = 600,
      },
      xOnBehalfOf: "<end-user-id>" // optional — attribute the request to one of your end users
  );

  var session = created.CreateSessionResponseValue!.Session;
  var sessionId = session.SessionId;
  var sessionToken = created.CreateSessionResponseValue.SessionToken;
  Console.WriteLine($"{session.Status} {created.CreateSessionResponseValue.Provider}");
  ```

  ```python Python SDK theme={null}
  from meetkai_mka1 import SDK

  sdk = SDK(bearer_auth="Bearer <mka1-api-key>")

  created = sdk.sandbox.create(
      session_id="docs-demo-session",
      session_kind="standard",
      ttl_seconds=600,
      x_on_behalf_of="<end-user-id>",  # optional — attribute the request to one of your end users
  )

  session_id = created.session.session_id
  session_token = created.session_token
  print(created.session.status, created.provider)
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/sandbox/sessions \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --header 'X-On-Behalf-Of: <end-user-id>' \
    --data '{
      "session_id": "docs-demo-session",
      "session_kind": "standard",
      "ttl_seconds": 600
    }'
  ```
</CodeGroup>

The response carries the record, the token, and the provider that backs the session.
Trimmed to the fields this guide discusses:

```json theme={null}
{
  "session": {
    "session_id": "docs-demo-session",
    "status": "provisioning",
    "session_kind": "standard",
    "resource_class": "medium",
    "ttl_seconds": 600,
    "workspace_expires_at": "2026-09-02T13:02:41Z",
    "runtime_profile": "standard",
    "sandbox_memory_mib": 256,
    "created_at": "2026-08-26T13:02:41Z",
    "updated_at": "2026-08-26T13:02:41Z"
  },
  "session_token": "<session-token>",
  "provider": "runner-docker"
}
```

The full record also carries the caller's `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`):

| Field              | Notes                                                                                                                                                                                                                                                    |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `session_kind`     | `standard`, `browser`, or `coding_harness` (console-managed). See [Kinds](#kinds).                                                                                                                                                                       |
| `ttl_seconds`      | Idle timeout in seconds. See [Lifetime and states](#lifetime-and-states).                                                                                                                                                                                |
| `runtime_profile`  | `standard` (default) or `eval-python`.                                                                                                                                                                                                                   |
| `queue_if_full`    | `true` queues the request when runner capacity is temporarily unavailable, returning `202` with a `queued` session, instead of failing with `503`. A browser session on a deployment without browser support still fails with `501` instead of queueing. |
| `memory_mounts`    | Mounts [memory stores](/docs/build-an-agent-with-memory-stores) into the workspace, with `store_id`, `label`, and `access` of `read_only` or `read_write`.                                                                                               |
| `sandbox_features` | Optional feature flags. The accepted values are `python`, `git`, and `claude-code`.                                                                                                                                                                      |
| `session_key`      | An optional grouping key. `list` returns it on each record, so a caller that names sessions dynamically can still find the latest one for a key.                                                                                                         |

`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 until `status` is `running` or `idle`.

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const session = await sdk.sandbox.get({ sessionId });
  console.log(session.status, session.workspaceExpiresAt);
  ```

  ```csharp C# SDK theme={null}
  var current = await sdk.Sandbox.GetAsync(sessionId);
  Console.WriteLine($"{current.SessionRecord!.Status} {current.SessionRecord.WorkspaceExpiresAt}");
  ```

  ```python Python SDK theme={null}
  session = sdk.sandbox.get(session_id=session_id)
  print(session.status, session.workspace_expires_at)
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/sandbox/sessions/docs-demo-session \
    --header 'Authorization: Bearer <mka1-api-key>'
  ```
</CodeGroup>

[Lifetime and states](#lifetime-and-states) explains each status.

## 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`).

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const result = await sdk.sandbox.runCommand({
    sessionId,
    commandRequest: {
      sessionId,
      sessionToken,
      command: 'echo',
      args: ['hello from the sandbox'],
    },
  });

  console.log(result.exitCode, result.stdout);
  ```

  ```csharp C# SDK theme={null}
  var command = await sdk.Sandbox.RunCommandAsync(sessionId, new CommandRequest
  {
      SessionId = sessionId,
      SessionToken = sessionToken,
      Command = "echo",
      Args = new List<string> { "hello from the sandbox" },
  });

  Console.WriteLine($"{command.CommandResult!.ExitCode} {command.CommandResult.Stdout}");
  ```

  ```python Python SDK theme={null}
  result = sdk.sandbox.run_command(
      session_id_param=session_id,
      session_id=session_id,
      session_token=session_token,
      command="echo",
      args=["hello from the sandbox"],
  )

  print(result.exit_code, result.stdout)
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/sandbox/sessions/docs-demo-session/commands \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --data '{
      "session_id": "docs-demo-session",
      "session_token": "<session-token>",
      "command": "echo",
      "args": ["hello from the sandbox"]
    }'
  ```
</CodeGroup>

The result reports `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.

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const run = await sdk.sandbox.runCode({
    sessionId,
    codeRequest: {
      sessionId,
      sessionToken,
      runtime: 'python',
      code: 'print("2 + 2 =", 2 + 2)',
    },
  });

  console.log(run.runtime, run.exitCode, run.stdout);
  ```

  ```csharp C# SDK theme={null}
  var run = await sdk.Sandbox.RunCodeAsync(sessionId, new CodeRequest
  {
      SessionId = sessionId,
      SessionToken = sessionToken,
      Runtime = CodeRuntime.Python,
      Code = "print(\"2 + 2 =\", 2 + 2)",
  });

  Console.WriteLine($"{run.CodeResult!.Runtime} {run.CodeResult.ExitCode} {run.CodeResult.Stdout}");
  ```

  ```python Python SDK theme={null}
  run = sdk.sandbox.run_code(
      session_id_param=session_id,
      session_id=session_id,
      session_token=session_token,
      runtime="python",
      code='print("2 + 2 =", 2 + 2)',
  )

  print(run.runtime, run.exit_code, run.stdout)
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/sandbox/sessions/docs-demo-session/code \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --data '{
      "session_id": "docs-demo-session",
      "session_token": "<session-token>",
      "runtime": "python",
      "code": "print(\"2 + 2 =\", 2 + 2)"
    }'
  ```
</CodeGroup>

`runtime` accepts these values:

| Language          | `runtime` values      |
| ----------------- | --------------------- |
| Python            | `python`, `python3`   |
| JavaScript (Node) | `javascript`, `node`  |
| Go                | `go`                  |
| Shell             | `bash`, `shell`, `sh` |

`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](#archives).

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  await sdk.sandbox.uploadFile({
    sessionId,
    sessionToken,
    filePath: 'input.csv',
    requestBody: new TextEncoder().encode('id,value\n1,42\n'),
  });

  const download = await sdk.sandbox.downloadFile({
    sessionId,
    sessionToken,
    filePath: 'input.csv',
  });
  console.log(await new Response(download).text());
  ```

  ```csharp C# SDK theme={null}
  await sdk.Sandbox.UploadFileAsync(new MeetKai.MKA1.Types.Requests.UploadWorkspaceFileRequest
  {
      SessionId = sessionId,
      SessionToken = sessionToken,
      FilePath = "input.csv",
      Body = Encoding.UTF8.GetBytes("id,value\n1,42\n"),
  });

  var download = await sdk.Sandbox.DownloadFileAsync(sessionId, "input.csv", sessionToken: sessionToken);
  Console.WriteLine(Encoding.UTF8.GetString(download.Bytes!));
  ```

  ```python Python SDK theme={null}
  sdk.sandbox.upload_file(
      session_id=session_id,
      session_token=session_token,
      file_path="input.csv",
      body=b"id,value\n1,42\n",
  )

  download = sdk.sandbox.download_file(
      session_id=session_id,
      session_token=session_token,
      file_path="input.csv",
  )
  print(download.read().decode())
  ```

  ```bash bash theme={null}
  printf 'id,value\n1,42\n' > input.csv

  curl "https://apigw.mka1.com/api/v1/sandbox/sessions/docs-demo-session/workspace/files/input.csv?session_token=<session-token>" \
    --request PUT \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --header 'Content-Type: application/octet-stream' \
    --data-binary @input.csv

  curl "https://apigw.mka1.com/api/v1/sandbox/sessions/docs-demo-session/workspace/files/input.csv?session_token=<session-token>" \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --output input-copy.csv
  ```
</CodeGroup>

Uploads answer with `{"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.

| Operation         | Endpoint                                                                                                   | What it does                                   |
| ----------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `uploadArchive`   | `PUT .../workspace/archive?session_token=...` with `Content-Type: application/zip` and the zip as the body | Extracts the archive into the workspace.       |
| `downloadArchive` | `POST .../workspace/archive?session_token=...` with `{"paths": ["input.csv", "out"]}`                      | Returns a zip of the selected workspace paths. |

## Step 6 - Inspect the workspace

The manifest lists every file in the workspace with its size and etag.

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const manifest = await sdk.sandbox.getWorkspace({ sessionId, sessionToken });
  for (const file of manifest.files ?? []) {
    console.log(file.path, file.size, file.etag);
  }
  ```

  ```csharp C# SDK theme={null}
  var manifest = await sdk.Sandbox.GetWorkspaceAsync(sessionId, sessionToken: sessionToken);
  foreach (var file in manifest.WorkspaceManifest!.Files ?? new List<WorkspaceFile>())
  {
      Console.WriteLine($"{file.Path} {file.Size} {file.Etag}");
  }
  ```

  ```python Python SDK theme={null}
  manifest = sdk.sandbox.get_workspace(session_id=session_id, session_token=session_token)
  for file in manifest.files or []:
      print(file.path, file.size, file.etag)
  ```

  ```bash bash theme={null}
  curl "https://apigw.mka1.com/api/v1/sandbox/sessions/docs-demo-session/workspace?session_token=<session-token>" \
    --header 'Authorization: Bearer <mka1-api-key>'
  ```
</CodeGroup>

## 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.

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const terminated = await sdk.sandbox.terminate({
    sessionId,
    terminateSessionRequest: { sessionId, sessionToken },
  });
  console.log(terminated.session.status);
  ```

  ```csharp C# SDK theme={null}
  var terminated = await sdk.Sandbox.TerminateAsync(sessionId, new TerminateSessionRequest
  {
      SessionId = sessionId,
      SessionToken = sessionToken,
  });
  Console.WriteLine(terminated.TerminateSessionResponseValue!.Session.Status);
  ```

  ```python Python SDK theme={null}
  terminated = sdk.sandbox.terminate(
      session_id_param=session_id,
      session_id=session_id,
      session_token=session_token,
  )
  print(terminated.session.status)
  ```

  ```bash bash theme={null}
  curl https://apigw.mka1.com/api/v1/sandbox/sessions/docs-demo-session/terminate \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer <mka1-api-key>' \
    --data '{
      "session_id": "docs-demo-session",
      "session_token": "<session-token>"
    }'
  ```
</CodeGroup>

Termination releases the backing resources and returns the record with `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 with `session_kind: "browser"` and the session runs headless Chrome instead of a command sandbox.
Three operations are specific to it:

| Operation                                                    | What it returns                                                                                                                             | When to use it                                                                                                                                                                          |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `getBrowserUrl` (`GET .../browser-url`)                      | The public gateway URL of the session's Chrome DevTools Protocol endpoint on port `9222`, with `session_token` already in the query string. | Point Playwright, or any CDP client that accepts an HTTP endpoint, at this URL. Send your MKA1 API-key `Authorization` header on the HTTP and WebSocket requests alike.                 |
| `proxyBrowserPortRequest` (`GET .../ports/{port}/{subpath}`) | The proxied response from an exposed port, for example `json/version` or `json/list` on port `9222`.                                        | Fetch a single CDP document through the SDK without building the URL yourself. Clients that need a WebSocket endpoint read `json/version` and use its rewritten `webSocketDebuggerUrl`. |
| `getUrl` (`GET .../url`)                                     | The same as `getBrowserUrl`, with an optional `port` query parameter.                                                                       | Deprecated alias kept for older clients; use `getBrowserUrl`.                                                                                                                           |

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.

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const browser = await sdk.sandbox.create({
    createSessionRequest: { sessionId: 'docs-demo-browser', sessionKind: 'browser' },
  });

  const endpoint = await sdk.sandbox.getBrowserUrl({
    sessionId: browser.session.sessionId,
    sessionToken: browser.sessionToken,
  });
  console.log(endpoint.url);

  const version = await sdk.sandbox.proxyBrowserPortRequest({
    sessionId: browser.session.sessionId,
    sessionToken: browser.sessionToken,
    port: 9222,
    subpath: 'json/version',
  });
  console.log(version);
  ```

  ```csharp C# SDK theme={null}
  var browser = await sdk.Sandbox.CreateAsync(new CreateSessionRequest
  {
      SessionId = "docs-demo-browser",
      SessionKind = SessionKind.Browser,
  });
  var browserSession = browser.CreateSessionResponseValue!;

  var endpoint = await sdk.Sandbox.GetBrowserUrlAsync(
      browserSession.Session.SessionId,
      sessionToken: browserSession.SessionToken
  );
  Console.WriteLine(endpoint.SessionUrlResponse!.Url);
  ```

  ```python Python SDK theme={null}
  browser = sdk.sandbox.create(session_id="docs-demo-browser", session_kind="browser")

  endpoint = sdk.sandbox.get_browser_url(
      session_id=browser.session.session_id,
      session_token=browser.session_token,
  )
  print(endpoint.url)

  version = sdk.sandbox.proxy_browser_port_request(
      session_id=browser.session.session_id,
      session_token=browser.session_token,
      port=9222,
      subpath="json/version",
  )
  print(version)
  ```

  ```bash bash theme={null}
  curl "https://apigw.mka1.com/api/v1/sandbox/sessions/docs-demo-browser/browser-url?session_token=<session-token>" \
    --header 'Authorization: Bearer <mka1-api-key>'

  curl "https://apigw.mka1.com/api/v1/sandbox/sessions/docs-demo-browser/ports/9222/json/version?session_token=<session-token>" \
    --header 'Authorization: Bearer <mka1-api-key>'
  ```
</CodeGroup>

Wait for the session to reach `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 the `shell` 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`, or `runtime_profile`.
* You need a browser session.

The two can share a session.
Create it here, upload its inputs, then hand the id to a response's `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`.

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  const usage = await sdk.usage.sandbox({
    startTime: Math.floor(Date.now() / 1000) - 7 * 24 * 3600,
    bucketWidth: '1d',
    groupBy: ['operation'],
  });

  for (const bucket of usage.data ?? []) {
    for (const row of bucket.results ?? []) {
      console.log(bucket.startTime, row.operation, row.numOperations, row.durationMs);
    }
  }
  ```

  ```csharp C# SDK theme={null}
  var usage = await sdk.Usage.SandboxAsync(new MeetKai.MKA1.Types.Requests.GetSandboxUsageRequest
  {
      StartTime = DateTimeOffset.UtcNow.AddDays(-7).ToUnixTimeSeconds(),
      BucketWidth = SandboxUsageBucketWidth.Oned,
      GroupBy = new List<string> { "operation" },
  });

  foreach (var bucket in usage.SandboxUsagePage!.Data ?? new List<SandboxUsageBucket>())
  {
      foreach (var row in bucket.Results ?? new List<SandboxUsageResult>())
      {
          Console.WriteLine($"{bucket.StartTime} {row.Operation} {row.NumOperations} {row.DurationMs}");
      }
  }
  ```

  ```python Python SDK theme={null}
  import time

  usage = sdk.usage.sandbox(
      start_time=int(time.time()) - 7 * 24 * 3600,
      bucket_width="1d",
      group_by=["operation"],
  )

  for bucket in usage.data or []:
      for row in bucket.results or []:
          print(bucket.start_time, row.operation, row.num_operations, row.duration_ms)
  ```

  ```bash bash theme={null}
  curl "https://apigw.mka1.com/api/v1/sandbox/usage?start_time=<unix-seconds>&bucket_width=1d&group_by=operation" \
    --header 'Authorization: Bearer <mka1-api-key>'
  ```
</CodeGroup>

Each result row carries `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

<Note>
  **Cluster-admin bearer required.**

  `getPricing` and `setPricing` answer `403` to any other caller.
  Spend for your own organization comes from `sdk.usage.costs` instead.
</Note>

The budgeting service bills sandbox time as `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.

<CodeGroup>
  ```ts MKA1 SDK theme={null}
  // Cluster admins only.
  const pricing = await sdk.sandbox.getPricing();
  console.log(pricing.rates, pricing.updatedAt);
  ```

  ```bash bash theme={null}
  # Cluster admins only.
  curl https://apigw.mka1.com/api/v1/sandbox/pricing \
    --header 'Authorization: Bearer <mka1-api-key>'
  ```
</CodeGroup>

`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

| Symptom                                            | Cause                                                                                                                            | Fix                                                                                                                             |
| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `403` with `detail.required_scope` in the body     | The key lacks `read:sandbox` or `write:sandbox`.                                                                                 | Mint a key that carries both.                                                                                                   |
| `503 sandbox_capacity_exhausted` on create         | Runner capacity is exhausted.                                                                                                    | Retry later, or create with `queue_if_full: true` and poll the `queued` session.                                                |
| `409` on create                                    | A session with that id already exists and was not reused in place.                                                               | Read it with `get`. If it is `running`, `idle`, or `stopped`, use it as is; if it is still provisioning, poll until it settles. |
| `410` with `session inactive`                      | The session is `failed` or `terminated`.                                                                                         | Create again with the same `session_id` to purge and recreate it.                                                               |
| `501 browser_sessions_not_enabled` on create       | The deployment is not Firecracker-backed, so it cannot run browser sessions.                                                     | Use a `standard` session, or a deployment that supports browser sessions.                                                       |
| `501` from `GET .../browser-url`                   | The session is a `standard` one; only browser sessions expose a public URL.                                                      | Use the command, code, and workspace operations.                                                                                |
| A command returns `exit_code` other than `0`       | The program failed, not the API; the call itself returns `200`.                                                                  | Read `stderr`.                                                                                                                  |
| A command stops at exactly `timeout_seconds`       | The request's timeout ran out; the default is `60`.                                                                              | Raise `timeout_seconds` on that request.                                                                                        |
| The first command after a pause is slow            | The idle timeout reclaimed the container and the workspace was rehydrated.                                                       | Raise `ttl_seconds` on sessions with long gaps between commands.                                                                |
| A session you created is not found by a later call | The create carried `X-On-Behalf-Of` and this call does not, or the reverse. Sessions are scoped to the caller that created them. | Send the same header on every call for that session.                                                                            |

## 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](/api-reference/introduction).

## See also

* [Build an agent with memory stores](/docs/build-an-agent-with-memory-stores) - the `shell` tool with memory stores mounted into the sandbox.
* [Python graders](/docs/evals-python-graders) - grading code the evals service runs in a sandbox.
* [Usage auditing](/docs/usage-auditing) - the sandbox command dashboards and log views in SigNoz.
