# Sandbox

A Sandbox is an isolated execution environment where you can run code, manage files, and control network access. Each sandbox is created from a [Template](/docs/template) and provides a secure, ephemeral workspace.

For shell access and file copy over standard SSH clients, see [SSH](/docs/sandbox/ssh).

<Callout variant="info">
Sandbox0 Cloud clients should use `https://api.sandbox0.ai`. Set `SANDBOX0_BASE_URL` only when you are connecting to a self-hosted or private deployment.
</Callout>

## Sandbox Model

### Key Identifiers

| Field | Description |
|-------|-------------|
| `id` | Unique sandbox identifier (e.g., `sb_abc123`) |
| `template_id` | Template used to create this sandbox |
| `team_id` | Team that owns this sandbox |
| `user_id` | User who claimed this sandbox |
| `pod_name` | Kubernetes pod name (internal) |
| `cluster_id` | Cluster where sandbox runs in multi-cluster deployments. Present on claim and list responses, not on sandbox detail responses. |

### Lifecycle States

| Status | Description |
|--------|-------------|
| `starting` | Sandbox is being initialized |
| `running` | Sandbox is active and ready to use |
| `failed` | Sandbox encountered an error |
| `completed` | Sandbox has finished execution |

<Callout variant="info">
Pause state is modeled separately from `status`. Use the `paused` boolean when you need to distinguish a paused sandbox from a running one. For async pause/resume flows, use `power_state` to inspect desired vs observed state plus generation counters.
</Callout>

---

## Claim a Sandbox

Claim a sandbox from a template. The sandbox is created from a pre-warmed pool for fast startup (&lt;200ms cold start).

<Endpoint method="POST">
/api/v1/sandboxes
</Endpoint>

### Request Body

| Field | Type | Description |
|-------|------|-------------|
| `template` | string | Template ID to use |
| `config` | object | Optional sandbox configuration |

### Sandbox Configuration

| Field | Type | Description |
|-------|------|-------------|
| `env_vars` | object | Environment variables |
| `ttl` | integer | Time to live in seconds (soft limit, triggers auto-pause; default: `0`, disabled) |
| `hard_ttl` | integer | Hard runtime lifetime in seconds (cleans compute while preserving sandbox identity and services; default: `0`, disabled) |
| `network` | object | `SandboxNetworkPolicy`. Controls traffic rules, protocol controls, credential bindings, and destination-scoped egress auth |
| `webhook` | object | Webhook configuration |
| `auto_resume` | boolean | Auto-resume when accessed (default: true) |
| `services` | array | Sandbox Services for public HTTP routes |

See [Network](/docs/sandbox/network) and [Protocol Controls](/docs/sandbox/protocol-controls) for outbound control, [Sandbox Services](/docs/sandbox/services) for public HTTP controls, and [Credential](/docs/credential) for outbound auth and secret handling.

### TTL vs Hard TTL

Sandbox0 uses two-tier TTL to balance resource efficiency and flexibility:

| Field | Behavior | Use Case |
|-------|----------|----------|
| `ttl` | **Soft pause**: When expired, sandbox is automatically paused. Processes stop but state is preserved. Can be extended via refresh API. | Keep sandboxes alive during active use while freeing compute resources during idle periods. |
| `hard_ttl` | **Hard clean**: When expired, Sandbox0 deletes the runtime pod and marks the sandbox `cleaned`. Identity, configuration, services, and public URLs remain available until explicit delete. | Enforce compute cost limits while keeping a durable sandbox service that can be restored on demand. |

<Callout variant="info">
The relationship: <code>ttl {'<='} hard_ttl</code>. When <code>ttl</code> expires first, sandbox pauses but can be resumed.
When <code>hard_ttl</code> expires, sandbox compute is cleaned and the sandbox can be restored by resume-capable access. Both can be extended via the refresh API.
</Callout>

**Example timeline (sandbox created with `ttl=300` and `hard_ttl=3600`):**
- `t=0`: Sandbox created
- `t=300`: TTL expires → sandbox auto-pauses
- `t=310`: User calls refresh → TTL reset to 300, Hard TTL reset to 3600 (new hard deadline at `t=3910`)
- `t=610`: TTL expires again → sandbox auto-pauses
- `t=3910`: Hard TTL expires → runtime pod cleaned, sandbox status becomes `cleaned`

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `ctx := context.Background()
client, err := sandbox0.NewClient(
    sandbox0.WithToken(os.Getenv("SANDBOX0_TOKEN")),
    sandbox0.WithBaseURL(os.Getenv("SANDBOX0_BASE_URL")),
)
if err != nil {
    log.Fatal(err)
}

// Claim a sandbox from the "default" template
sandbox, err := client.ClaimSandbox(ctx, "default",
    sandbox0.WithSandboxHardTTL(300),
    sandbox0.WithSandboxEnvVars(map[string]string{
        "APP_ENV": "development",
    }),
)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Sandbox ID: %s\\n", sandbox.ID)
defer client.DeleteSandbox(ctx, sandbox.ID)`
    },
    {
      label: "Python",
      language: "python",
      code: `import os
from sandbox0 import Client
from sandbox0.apispec.models.sandbox_config import SandboxConfig

client = Client(
    token=os.environ["SANDBOX0_TOKEN"],
    base_url=os.environ.get("SANDBOX0_BASE_URL", "https://api.sandbox0.ai"),
)

# Claim a sandbox from the "default" template
sandbox = client.claim_sandbox(
    template="default",
    config=SandboxConfig.from_dict({
        "hard_ttl": 300,
        "env_vars": {"APP_ENV": "development"},
    }),
)
print(f"Sandbox ID: {sandbox.id}")

# Cleanup
client.delete_sandbox(sandbox.id)`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `import { Client } from 'sandbox0';

const client = new Client({
    token: process.env.SANDBOX0_TOKEN!,
    baseUrl: process.env.SANDBOX0_BASE_URL || 'https://api.sandbox0.ai',
});

// Claim a sandbox from the "default" template
const sandbox = await client.sandboxes.claim('default', {
    hardTtl: 300,
    envVars: { APP_ENV: 'development' },
});
console.log('Sandbox ID:', sandbox.id);

// Cleanup
await client.sandboxes.delete(sandbox.id);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `# Claim a sandbox from the "default" template
s0 sandbox create --template default --hard-ttl 300`
    }
  ]}
/>

---

## Get Sandbox Details

Retrieve full details about a sandbox.

<Endpoint method="GET">
/api/v1/sandboxes/{'{id}'}
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `sb, err := client.GetSandbox(ctx, sandbox.ID)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Status: %s\\n", sb.Status)
fmt.Printf("Template: %s\\n", sb.TemplateID)
fmt.Printf("Expires at: %s\\n", sb.ExpiresAt)`
    },
    {
      label: "Python",
      language: "python",
      code: `sb = client.get_sandbox(sandbox.id)
print(f"Status: {sb.status}")
print(f"Template: {sb.template_id}")
print(f"Expires at: {sb.expires_at}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const sb = await client.sandboxes.get(sandbox.id);
console.log('Status:', sb.status);
console.log('Template:', sb.templateId);
console.log('Expires at:', sb.expiresAt);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox get sb_abc123`
    }
  ]}
/>

---

## Get Sandbox Status

Get the current status of a sandbox (lighter weight than full details).

<Endpoint method="GET">
/api/v1/sandboxes/{'{id}'}/status
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `status, err := client.StatusSandbox(ctx, sandbox.ID)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Status: %s\\n", status.Status.Value)`
    },
    {
      label: "Python",
      language: "python",
      code: `status = client.sandboxes.status(sandbox.id)
print(f"Status: {status.status}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const status = await client.sandboxes.status(sandbox.id);
console.log('Status:', status.status);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox status sb_abc123`
    }
  ]}
/>

---

## Get Sandbox Logs

Read sandbox process output. Procd service logs are filtered out of this API and remain available from Kubernetes pod logs. Snapshot and streaming requests return `text/plain`.

<Endpoint method="GET">
/api/v1/sandboxes/{'{id}'}/logs
</Endpoint>

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `container` | string | Pod container name (default: `procd`) |
| `tail_lines` | integer | Number of Kubernetes log lines read from the end of the log before procd service log filtering (default: 200, max: 5000) |
| `limit_bytes` | integer | Maximum Kubernetes log bytes read before procd service log filtering (default: 1048576 for snapshot responses, max: 8388608) |
| `follow` | boolean | Stream logs as `text/plain` until the client disconnects |
| `previous` | boolean | Return logs from the previously terminated container instance |
| `timestamps` | boolean | Include Kubernetes log timestamps when available |
| `since_seconds` | integer | Return logs newer than this many seconds |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `tailLines := int64(200)
logs, err := sandbox.GetLogs(ctx, &sandbox0.SandboxLogsOptions{
    TailLines: &tailLines,
})
if err != nil {
    log.Fatal(err)
}
fmt.Print(logs.Logs)`
    },
    {
      label: "Python",
      language: "python",
      code: `from sandbox0.sandbox_logs import SandboxLogsOptions

logs = sandbox.get_logs(SandboxLogsOptions(tail_lines=200))
print(logs.logs, end="")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const logs = await sandbox.getLogs({ tailLines: 200 });
process.stdout.write(logs.logs);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox logs sb_abc123 --tail 200`
    }
  ]}
/>

<Callout variant="info">
For live streaming, use `sandbox.StreamLogs(...)`, `sandbox.stream_logs(...)`, `sandbox.streamLogs(...)`, or `s0 sandbox logs --follow`.
</Callout>

---

## List Sandboxes

List all sandboxes with optional filters.

<Endpoint method="GET">
/api/v1/sandboxes
</Endpoint>

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Filter by status (`starting`, `running`, `failed`, `completed`, `terminating`) |
| `template_id` | string | Filter by template ID |
| `paused` | boolean | Filter by paused state independently of `status` |
| `limit` | integer | Max results per page (default: 50, max: 200) |
| `offset` | integer | Pagination offset (default: 0) |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `limit := 10
sandboxes, err := client.ListSandboxes(ctx, &sandbox0.ListSandboxesOptions{
    Status:     "running",
    TemplateID: "default",
    Limit:      &limit,
})
if err != nil {
    log.Fatal(err)
}
for _, sb := range sandboxes.Sandboxes {
    fmt.Printf("- %s (status: %s)\\n", sb.ID, sb.Status)
}`
    },
    {
      label: "Python",
      language: "python",
      code: `sandboxes = client.sandboxes.list(
    status="running",
    template_id="default",
    limit=10,
)
for sb in sandboxes:
    print(f"- {sb.id} (status: {sb.status})")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const { sandboxes } = await client.sandboxes.list({
    status: 'running',
    templateId: 'default',
    limit: 10,
});
for (const sb of sandboxes) {
    console.log(\`- \${sb.id} (status: \${sb.status})\`);
}`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox list --status running --template-id default`
    }
  ]}
/>

---

## Update Sandbox

Update sandbox configuration at runtime without restarting the sandbox.

<Endpoint method="PUT">
/api/v1/sandboxes/{'{id}'}
</Endpoint>

### Updatable Fields

Only the following fields can be updated at runtime:

| Field | Type | Description |
|-------|------|-------------|
| `ttl` | integer | Time to live in seconds (soft limit) |
| `hard_ttl` | integer | Hard runtime lifetime in seconds |
| `network` | object | Network policy configuration |
| `auto_resume` | boolean | Auto-resume when accessed |
| `services` | array | Sandbox Services for public HTTP routes |

<Callout variant="warning">
<code>env_vars</code> and <code>webhook</code> cannot be updated at runtime. These fields only take effect when the sandbox is created. To use different environment variables, create a new sandbox.
</Callout>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `_, err = client.UpdateSandbox(ctx, sandbox.ID, apispec.SandboxUpdateRequest{
    Config: apispec.NewOptSandboxUpdateConfig(apispec.SandboxUpdateConfig{
        TTL:        apispec.NewOptInt32(600),
        AutoResume: apispec.NewOptBool(false),
    }),
})
if err != nil {
    log.Fatal(err)
}`
    },
    {
      label: "Python",
      language: "python",
      code: `from sandbox0.apispec.models.sandbox_update_config import SandboxUpdateConfig
from sandbox0.apispec.models.sandbox_update_request import SandboxUpdateRequest

client.sandboxes.update(
    sandbox.id,
    SandboxUpdateRequest(config=SandboxUpdateConfig.from_dict({
        "ttl": 600,
        "auto_resume": False,
    })),
)`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `await client.sandboxes.update(sandbox.id, {
    config: { ttl: 600, autoResume: false },
});`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox update sb_abc123 --ttl 600 --auto-resume false`
    }
  ]}
/>

---

## Pause And Resume

Pause and resume is covered in a dedicated page because it affects TTL, `auto_resume`, service routes, SSH, and webhook behavior.

See [Pause And Resume](/docs/sandbox/pause-resume) for explicit pause, state inspection, resume, and auto-resume behavior.

---

## Refresh Sandbox TTL

Extend the sandbox time-to-live. This resets both `ttl` and `hard_ttl` (if configured) from the current time while the sandbox has a runtime.

<Endpoint method="POST">
/api/v1/sandboxes/{'{id}'}/refresh
</Endpoint>

### Request Body

| Field | Type | Description |
|-------|------|-------------|
| `duration` | integer | Duration to extend TTL in seconds (optional, defaults to original TTL) |

<Callout variant="info">
If <code>duration</code> is not specified, both <code>ttl</code> and <code>hard_ttl</code> are reset to their original configured values. Use this to keep a sandbox alive as long as the user is actively using it.
</Callout>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `// Refresh with default duration (original TTL)
resp, err := client.RefreshSandbox(ctx, sandbox.ID, nil)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("New expires at: %s\\n", resp.ExpiresAt)

// Refresh with custom duration (e.g., 1 minute)
resp, err = client.RefreshSandbox(ctx, sandbox.ID, &apispec.SandboxRefreshRequest{
    Duration: apispec.NewOptInt32(60),
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("New expires at: %s\\n", resp.ExpiresAt)`
    },
    {
      label: "Python",
      language: "python",
      code: `from sandbox0.apispec.models.sandbox_refresh_request import SandboxRefreshRequest

# Refresh with default duration (original TTL)
resp = client.sandboxes.refresh(sandbox.id)
print(f"New expires at: {resp.expires_at}")

# Refresh with custom duration (e.g., 1 minute)
resp = client.sandboxes.refresh(sandbox.id, SandboxRefreshRequest(duration=60))
print(f"New expires at: {resp.expires_at}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `// Refresh with default duration (original TTL)
const resp = await client.sandboxes.refresh(sandbox.id);
console.log('New expires at:', resp.expiresAt);

// Refresh with custom duration (e.g., 1 minute)
const resp2 = await client.sandboxes.refresh(sandbox.id, { duration: 60 });
console.log('New expires at:', resp2.expiresAt);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `# Refresh with default duration
s0 sandbox refresh sb_abc123

# Output includes the new expiry timestamp`
    }
  ]}
/>

---

## Delete Sandbox

Terminate and delete a sandbox. This action is irreversible.

<Endpoint method="DELETE">
/api/v1/sandboxes/{'{id}'}
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `_, err = client.DeleteSandbox(ctx, sandbox.ID)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Sandbox deleted")`
    },
    {
      label: "Python",
      language: "python",
      code: `client.sandboxes.delete(sandbox.id)
print("Sandbox deleted")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `await client.sandboxes.delete(sandbox.id);
console.log('Sandbox deleted');`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox delete sb_abc123`
    }
  ]}
/>

---

## Next Steps

<CardGroup>
  <Card title="Pause And Resume" href="/docs/sandbox/pause-resume" cta="Continue">
    Control sandbox power state and resume behavior before wiring long-lived workflows.
  </Card>

  <Card title="Contexts" href="/docs/sandbox/contexts" cta="Continue">
    Run REPL and command contexts inside a sandbox and stream process output.
  </Card>
</CardGroup>
