# Migrate from E2B

The Impello SDKs are a copy of E2B's clients, so most of your code does not
change. Swap the package, swap the key, rebuild your templates.

## Install the new packages

`@impello/sdk` replaces `e2b` on npm and `impello` replaces `e2b` on PyPI.
Neither package depends on the one it replaces.

<CodeTabs>
<Tab label="TypeScript">

```bash
npm uninstall e2b
npm install @impello/sdk
```

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

```bash
pip uninstall -y e2b
pip install impello
```

</Tab>
</CodeTabs>

The TypeScript SDK needs Node 20.18.1 or later on the 20 line, or Node 22 or
later. Node 21 is not supported. The Python SDK needs Python 3.10 or later.

Only the import line changes in most files. The class, the modules and the
method names are the same.

<CodeTabs>
<Tab label="TypeScript">

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

const sandbox = await Sandbox.create('base')
const result = await sandbox.commands.run('uname -a')

console.log(result.stdout)
await sandbox.kill()
```

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

```python
from impello import Sandbox

sandbox = Sandbox.create("base")
result = sandbox.commands.run("uname -a")

print(result.stdout)
sandbox.kill()
```

</Tab>
</CodeTabs>

## Change the key

An Impello key is `imp_` followed by hexadecimal characters. Make one at
[dashboard.impello.ai/keys](https://dashboard.impello.ai/keys); see
[API keys](/docs/api-keys).

A key minted under the older `e2b_` prefix cannot be made to work by any
client. The API compares the whole prefix before it hashes anything, so no
client-side flag or option makes one pass. Create a new key and replace the
old value everywhere it is used.

The two SDKs fail differently on an old key. The Python SDK recognises the
`e2b_` prefix and raises `AuthenticationException` before a request leaves the
process. The TypeScript SDK accepts both shapes on the client side and lets
the API answer, so you get HTTP 401 raised as `AuthenticationError`. See
[Errors](/docs/errors).

Check a new key against the API before you change anything else.

```bash
export IMPELLO_API_KEY=imp_...

curl "https://api.sandbox.impello.ai/v2/sandboxes" \
  -H "X-API-Key: $IMPELLO_API_KEY"
```

A `200` with a JSON list means the key is good. A `401` means it is not.

## Environment variables

Every connection setting is read under its `IMPELLO_` name first and the
matching `E2B_` name second. An environment that already exports the older
names keeps working, so you can rename on your own schedule.

<div className="docs-table-wrap">
<table>
  <thead>
    <tr><th>Impello name</th><th>Also read as</th><th>Default</th><th>What it does</th></tr>
  </thead>
  <tbody>
    <tr><td>`IMPELLO_API_KEY`</td><td>`E2B_API_KEY`</td><td>none</td><td>The key. Starts with `imp_`</td></tr>
    <tr><td>`IMPELLO_DOMAIN`</td><td>`E2B_DOMAIN`</td><td>`sandbox.impello.ai`</td><td>The domain the API and the sandboxes sit on</td></tr>
    <tr><td>`IMPELLO_API_URL`</td><td>`E2B_API_URL`</td><td>`https://api.` plus the domain</td><td>The API address</td></tr>
    <tr><td>`IMPELLO_VALIDATE_API_KEY`</td><td>`E2B_VALIDATE_API_KEY`</td><td>`true`</td><td>Set to `false` to skip the key shape check. TypeScript only</td></tr>
    <tr><td>`IMPELLO_DEBUG`</td><td>`E2B_DEBUG`</td><td>`false`</td><td>Talk to `http://localhost:3000` instead of the API, and address sandboxes at `localhost:<port>`</td></tr>
    <tr><td>`IMPELLO_SANDBOX_URL`</td><td>`E2B_SANDBOX_URL`</td><td>none</td><td>Forces every sandbox connection to one fixed host; leave it unset</td></tr>
  </tbody>
</table>
</div>

<Callout kind="warning">
Delete `E2B_DOMAIN`, `E2B_API_URL` and `E2B_SANDBOX_URL` from any environment
that used to point at another fleet. They are still read and they win over
the Impello defaults. The failure is a connection to somebody else's API or
sandbox host, authenticated with a key that host has never seen.
</Callout>

Two smaller groups follow different rules.

- The TypeScript connection-pool variables have no `IMPELLO_` name at all:
  `E2B_API_CONNECTIONS`, `E2B_API_INFLIGHT_REQUESTS`,
  `E2B_ENVD_RPC_CONNECTIONS`, `E2B_ENVD_INFLIGHT_REQUESTS` and
  `E2B_ENVD_RPC_INFLIGHT_REQUESTS`. Keep the old names; renaming them silently
  restores the defaults.
- The Python pool settings take both prefixes:
  `IMPELLO_CONNECTION_RETRIES` (`3`), `IMPELLO_KEEPALIVE_EXPIRY` (`300`
  seconds) and `IMPELLO_MAX_KEEPALIVE_CONNECTIONS` (`20`).

`IMPELLO_ACCESS_TOKEN` is read by the TypeScript SDK only, and it is
deprecated. Send the token as an `Authorization` header through `apiHeaders`
instead. The Python SDK never reads it.

## Rebuild your templates

Templates do not transfer. There is no import, and nothing copies an image
between fleets, so every custom template has to be built again here. Snapshots
and paused sandboxes do not transfer either: a paused sandbox exists only on
the fleet that paused it.

The default template names carry over. `base`, `code-interpreter`, `claude`,
`codex`, `opencode`, `pi`, `amp`, `droid`, `grok`, `openclaw`, `k3s`,
`desktop` and `omarchy` all exist here under those names. A
`Sandbox.create()` call that names one of them needs no edit. `claude` also
answers to `claude-code`, and `code-interpreter` to `code-interpreter-v1`.
`desktop` is the one to check: it exists under the same name, but it declares
no start command, so the graphical stack is not running when the sandbox
boots. See [Templates](/docs/templates).

For a custom template, rewrite the definition against the builder at
`@impello/sdk/template` and build it once. The builder reads your key from
`IMPELLO_API_KEY` (or `E2B_API_KEY`), or from an `apiKey` passed in the build
options.

```ts
import { Template } from '@impello/sdk/template'

const template = Template()
  .fromTemplate('base')
  .aptInstall(['ffmpeg'])

await Template.build(template, 'my-app:v1')
```

The builder is a subpath rather than a root export, so code that only creates
sandboxes never loads a Dockerfile parser. Full reference:
[Build a custom template](/docs/templates/build).

<Callout kind="note">
The template builder ships only in the TypeScript SDK. A Python project that
needs a custom template builds it once with the TypeScript builder, then
passes the name to `Sandbox.create`.
</Callout>

## What is the same

Both packages are E2B's clients vendored under the MIT licence, with the API
address, the key check and the differences listed below changed. The
TypeScript SDK tracks E2B's client at 2.32.0; `@impello/sdk` carries its own
version number. That means:

- The same class and the same modules: `Sandbox`, `sandbox.files`,
  `sandbox.commands`, `sandbox.pty`, `sandbox.git`.
- The same methods, including `create`, `connect`, `list`, `kill`, `pause`,
  `setTimeout` (`set_timeout`), `getInfo` (`get_info`) and `getMetrics`
  (`get_metrics`).
- The same error and exception class names, so existing `catch` and `except`
  blocks still match. See [Errors](/docs/errors).
- The same sandbox options: `template`, `timeoutMs` (`timeout`), `envs`,
  `metadata` and the network options.
- The same authentication header. HTTP callers send `X-API-Key`, now against
  `https://api.sandbox.impello.ai`.

## What is different

- **Package names.** `@impello/sdk` and `impello`.
- **Key prefix.** `imp_`, and old keys cannot be reused.
- **Default domain.** `sandbox.impello.ai`, not `e2b.app`. Leave the domain
  unset and the SDK resolves the right fleet on its own.
- **Sandbox hosts.** Every sandbox is reached at
  `https://<port>-<sandboxId>.sandbox.impello.ai`, routed by the `Host`
  header. There is no single stable `sandbox.<domain>` host, and
  `sandbox.impello.ai` is deliberately left out of the SDK's list of domains
  that serve one, because adding it would build
  `https://sandbox.sandbox.impello.ai`. Use `getHost(port)` (`get_host(port)`
  in Python); see [Network and public URLs](/docs/sandbox/network).
- **The template builder is a subpath.** `@impello/sdk/template`, not the root
  import.
- **Static calls honour `apiUrl`.** `Sandbox.list`, `kill`, `pause` and
  `setTimeout` take `SandboxApiOpts`, which now includes `apiUrl`. Upstream
  left it off the type while reading it at runtime. Passing one option bag to
  both `create` and `list` used to drop the URL silently and send the call to
  the default host.
- **Python has three things TypeScript does not.** `Impello` binds connection
  options instead of reading the environment. `Sandbox.fork` checkpoints a
  running sandbox and starts copies from the snapshot. `iam` types workload
  identity on `Sandbox.create`; it has no TypeScript counterpart and is not
  supported.

## What Impello does not ship

These exist upstream and have no counterpart here. If your integration uses
one, it has to be replaced before you move.

- **A CLI.** There is no `impello` command in any language, and no package
  installs a binary. Everything is done through the SDKs, the HTTP API or
  [the dashboard](https://dashboard.impello.ai).
- **Volumes.** No volume client in either SDK and no persistent volume to
  mount. Both SDKs still type a `volumeMounts` (`volume_mounts`) option on
  `Sandbox.create`. There is nothing to mount; do not use it. Use the sandbox
  filesystem and [snapshots](/docs/sandbox/pause-and-snapshots) instead.
- **Secrets.** No secrets client in either SDK. Pass values with `envs` on
  `Sandbox.create` and keep them in your own secret store.
- **A code-interpreter client package.** The `code-interpreter` template
  exists and starts Jupyter with the sandbox, behind an HTTP server on port
  49999, but neither SDK ships a client for it. Drive it with
  `sandbox.commands`, or reach that HTTP server with `getHost(49999)`
  (`get_host(49999)` in Python); Jupyter itself listens on `localhost:8888`
  inside the sandbox. See [Templates](/docs/templates).
- **A desktop client.** Neither SDK has screenshot, click or stream methods.
  The `omarchy` template serves a desktop over noVNC on port 6080, with no VNC
  password. The port is public unless you set
  `network: { allowPublicTraffic: false }`
  (`network={"allow_public_traffic": False}` in Python). See
  [Network and public URLs](/docs/sandbox/network). You reach the desktop
  yourself.
- **An MCP gateway.** The SDK types still carry MCP server names and an `mcp`
  option, but the gateway template they resolve to is not built here. With
  `mcp` and no template, `create` boots a template named `mcp-gateway` that
  was never built here, so the call fails. Do not use them.
- **Other names the types still carry.** `lifecycle.autoResume`
  (`auto_resume`), `network.maskRequestHost` (`mask_request_host`) and
  `getMcpUrl()` and `getMcpToken()` (`get_mcp_url()`, `get_mcp_token()`) are
  in the types but are not part of the supported surface. Leave them unset.
- **A template builder in Python.** TypeScript only, as above.

[Errors](/docs/errors) lists every class each SDK raises.
