# Sandbox lifecycle

Create a sandbox, reconnect to it from somewhere else, find it again in a list, and kill it when you are done.

## Create a sandbox

`Sandbox.create()` starts a new sandbox from a template and resolves once the sandbox has been created. The default template is `base`. Both SDKs read your key from `IMPELLO_API_KEY`.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base', {
  timeoutMs: 600_000,
  metadata: { run: 'nightly-42' },
  envs: { NODE_ENV: 'production' },
})

console.log(sandbox.sandboxId)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create(
    "base",
    timeout=600,
    metadata={"run": "nightly-42"},
    envs={"NODE_ENV": "production"},
)

print(sandbox.sandbox_id)
```

</Tab>
</CodeTabs>

The timeout is in milliseconds in TypeScript and in seconds in Python. A sandbox that reaches its timeout is killed unless you set `lifecycle`. See [Timeouts](/docs/sandbox/timeouts).

Every running sandbox counts against your plan's concurrency limit: 3 at once on Micro, 10 on Base, 100 on Scale. Paused sandboxes do not count, and there is no limit on how many you keep paused. See [Limits and pricing](/docs/limits-and-pricing).

### Options

<table>
  <thead>
    <tr><th>TypeScript</th><th>Python</th><th>Default</th><th>What it does</th></tr>
  </thead>
  <tbody>
    <tr><td>`template`</td><td>`template`</td><td>`'base'`</td><td>Template name or ID. See [Templates](/docs/templates).</td></tr>
    <tr><td>`timeoutMs`</td><td>`timeout`</td><td>`300_000` / `300`</td><td>How long the sandbox lives. The maximum depends on your plan; see [Limits and pricing](/docs/limits-and-pricing).</td></tr>
    <tr><td>`metadata`</td><td>`metadata`</td><td>`{}`</td><td>String key-value pairs stored with the sandbox. Filterable in `list`.</td></tr>
    <tr><td>`envs`</td><td>`envs`</td><td>`{}`</td><td>Environment variables set for every command run in the sandbox.</td></tr>
    <tr><td>`secure`</td><td>`secure`</td><td>`true`</td><td>Require a per-sandbox access token on every command, file and terminal call (the sandbox's control channel). See [Secured sandboxes](#secured-sandboxes).</td></tr>
    <tr><td>`allowInternetAccess`</td><td>`allow_internet_access`</td><td>`true`</td><td>Outbound internet. `false` is the same as denying all egress in `network`.</td></tr>
    <tr><td>`network`</td><td>`network`</td><td>—</td><td>Egress rules and public traffic. See [Network and public URLs](/docs/sandbox/network).</td></tr>
    <tr><td>`lifecycle`</td><td>`lifecycle`</td><td>`onTimeout: 'kill'`</td><td>Pause instead of kill on timeout. See [Timeouts](/docs/sandbox/timeouts).</td></tr>
  </tbody>
</table>

## Metadata and environment variables

Metadata is for you: tag a sandbox with the user, job or run it belongs to, then find it again with [`list`](#list-sandboxes). Keys and values are strings. Anyone with one of your team's API keys can read them through `getInfo` and `list`.

Environment variables are for the sandbox: every command you run through the SDK sees them. They are ordinary process environment, not a secret store. A command can override them with its own `envs`.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create({
  metadata: { userId: 'u_123', job: 'index' },
  envs: { LOG_LEVEL: 'info' },
})

const a = await sandbox.commands.run('echo $LOG_LEVEL')
console.log(a.stdout) // info

const b = await sandbox.commands.run('echo $LOG_LEVEL', {
  envs: { LOG_LEVEL: 'debug' },
})
console.log(b.stdout) // debug
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create(
    metadata={"userId": "u_123", "job": "index"},
    envs={"LOG_LEVEL": "info"},
)

a = sandbox.commands.run("echo $LOG_LEVEL")
print(a.stdout)  # info

b = sandbox.commands.run("echo $LOG_LEVEL", envs={"LOG_LEVEL": "debug"})
print(b.stdout)  # debug
```

</Tab>
</CodeTabs>

## Secured sandboxes

A sandbox is created with `secure: true` unless you say otherwise. The SDK receives a token scoped to that one sandbox when it creates or connects to it, and sends the token with every command, file and terminal request. Upload and download URLs from `uploadUrl()` and `downloadUrl()` (`upload_url()` and `download_url()` in Python) are signed with the same token, and only a secured sandbox accepts a signature expiry. `secure` covers the control channel only — a server you start on a port is still reachable by anyone who knows the sandbox ID unless you also set `network.allowPublicTraffic: false`. See [Network and public URLs](/docs/sandbox/network).

Set `secure: false` only when something other than the SDK has to call the sandbox directly and cannot carry the token. Such a sandbox has no access token, and its file URLs are unsigned.

## Connect to a running sandbox

`Sandbox.connect(sandboxId)` returns a client for a sandbox that already exists. Use it to reach the same sandbox from another process, a queue worker or a serverless function. If the sandbox is paused, `connect` resumes it. In the snippets from here on, `sandboxId` (`sandbox_id` in Python) is the ID of a sandbox you already have, and `sandbox` is the client the previous snippet created.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.connect(sandboxId, { timeoutMs: 600_000 })
const result = await sandbox.commands.run('uptime')
console.log(result.stdout)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.connect(sandbox_id, timeout=600)
result = sandbox.commands.run("uptime")
print(result.stdout)
```

</Tab>
</CodeTabs>

The sandbox must be running or paused. Otherwise `connect` throws `SandboxNotFoundError` (`SandboxNotFoundException` in Python). See [Errors](/docs/errors).

<Callout kind="note">
On a running sandbox, the `timeoutMs` you pass to `connect` only takes effect when it is longer than the time the sandbox already has left. It never shortens a sandbox.
</Callout>

An instance you already hold has the same method: `await sandbox.connect()` in TypeScript and `sandbox.connect()` in Python. It is the shortest way to resume a sandbox you paused a moment ago. See [Pause, resume and snapshots](/docs/sandbox/pause-and-snapshots).

## Get sandbox information

`getInfo()` (`get_info()` in Python) fetches the current record for a sandbox: its state, template, metadata and when it will time out.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const info = await sandbox.getInfo()
console.log(info.state, info.endAt, info.metadata)

// Or by ID, without a client
const same = await Sandbox.getInfo(sandboxId)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

info = sandbox.get_info()
print(info.state, info.end_at, info.metadata)

# Or by ID, without a client
same = Sandbox.get_info(sandbox_id)
```

</Tab>
</CodeTabs>

### Common fields

<table>
  <thead>
    <tr><th>TypeScript</th><th>Python</th><th>Meaning</th></tr>
  </thead>
  <tbody>
    <tr><td>`sandboxId`</td><td>`sandbox_id`</td><td>The sandbox ID.</td></tr>
    <tr><td>`templateId`</td><td>`template_id`</td><td>ID of the template it was created from.</td></tr>
    <tr><td>`name`</td><td>`name`</td><td>Template name, when the template has one.</td></tr>
    <tr><td>`metadata`</td><td>`metadata`</td><td>The metadata you set at create.</td></tr>
    <tr><td>`startedAt`</td><td>`started_at`</td><td>When the sandbox started.</td></tr>
    <tr><td>`endAt`</td><td>`end_at`</td><td>When the current timeout expires.</td></tr>
    <tr><td>`state`</td><td>`state`</td><td>`running` or `paused`.</td></tr>
    <tr><td>`cpuCount`</td><td>`cpu_count`</td><td>vCPUs.</td></tr>
    <tr><td>`memoryMB`</td><td>`memory_mb`</td><td>Memory in MiB.</td></tr>
    <tr><td>`allowInternetAccess`</td><td>`allow_internet_access`</td><td>Whether outbound internet was allowed at create.</td></tr>
    <tr><td>`network`</td><td>`network`</td><td>The egress and public traffic settings.</td></tr>
    <tr><td>`lifecycle`</td><td>`lifecycle`</td><td>What happens on timeout, and whether it auto-resumes.</td></tr>
    <tr><td>`sandboxDomain`</td><td>`sandbox_domain`</td><td>Host the sandbox is served from.</td></tr>
  </tbody>
</table>

`isRunning()` (`is_running()` in Python) asks the sandbox itself rather than the control API. It returns `false` when the sandbox is no longer answering and raises on any other transport or auth failure.

## List sandboxes

`Sandbox.list()` returns a paginator over your team's sandboxes. With no filter it includes both running and paused sandboxes, 100 per page; 100 is also the largest page the API will return. Pages come back from `nextItems()` (`next_items()` in Python) while `hasNext` (`has_next`) is true.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const paginator = Sandbox.list({
  query: {
    metadata: { userId: 'u_123', job: 'index' },
    state: ['running'],
  },
  limit: 50,
})

while (paginator.hasNext) {
  const page = await paginator.nextItems()
  for (const s of page) {
    console.log(s.sandboxId, s.state, s.endAt)
  }
}
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox, SandboxQuery, SandboxState

paginator = Sandbox.list(
    query=SandboxQuery(
        metadata={"userId": "u_123", "job": "index"},
        state=[SandboxState.RUNNING],
    ),
    limit=50,
)

while paginator.has_next:
    for s in paginator.next_items():
        print(s.sandbox_id, s.state, s.end_at)
```

</Tab>
</CodeTabs>

Metadata filters are combined with AND: a sandbox must match every pair. `state` takes any of `running` and `paused`. Listed entries carry the same fields as `getInfo` minus `network`, `lifecycle`, `allowInternetAccess` and `sandboxDomain`; call `getInfo` on one entry when you need those.

The dashboard shows the same list at [dashboard.impello.ai/sandboxes](https://dashboard.impello.ai/sandboxes).

## Kill a sandbox

`kill()` stops the sandbox and frees it. It returns `true` when the sandbox was killed and `false` when no such sandbox exists, so killing twice is safe.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

await sandbox.kill()

// Or by ID, without a client
await Sandbox.kill(sandboxId)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox.kill()

# Or by ID, without a client
Sandbox.kill(sandbox_id)
```

</Tab>
</CodeTabs>

In Python, `Sandbox` is a context manager that kills the sandbox on exit:

```python
from impello import Sandbox

with Sandbox.create() as sandbox:
    print(sandbox.commands.run("echo hello").stdout)
# killed here
```

<Callout kind="warning">
A killed sandbox cannot be connected to again. To keep its state for later, [pause it](/docs/sandbox/pause-and-snapshots) instead.
</Callout>

## Async Python

Every call on this page has an `AsyncSandbox` form with the same name, awaited: `await AsyncSandbox.create()`, `await AsyncSandbox.connect(sandbox_id)`, `await sandbox.get_info()`, `await sandbox.kill()`. `AsyncSandbox.list()` returns an `AsyncSandboxPaginator` whose `next_items()` is awaited, and the context manager is `async with await AsyncSandbox.create() as sandbox:`.

Next: [Run commands](/docs/sandbox/commands).
