# Errors

Both SDKs map every HTTP status and RPC code they recognise to a typed error.
Transport failures and a few internal invariants surface as the language's
plain `Error` / `Exception`, so keep a branch that rethrows anything you do
not recognise. The names are identical in the two languages except for the
suffix: `Error` in TypeScript, `Exception` in Python.

## Catch an error

Import the class you care about and catch it by type. `SandboxError` is the
base for most of them, so catch it last.

<CodeTabs>
<Tab label="TypeScript">

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

const sandbox = await Sandbox.create('base')

try {
  await sandbox.commands.run('sleep 120', { timeoutMs: 5000 })
} catch (err) {
  if (err instanceof TimeoutError) {
    console.error('the command ran out of time', err.message)
  } else if (err instanceof SandboxError) {
    console.error('the sandbox refused the call', err.message)
  } else {
    throw err
  }
} finally {
  await sandbox.kill()
}
```

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

```python
from impello import Sandbox, SandboxException, TimeoutException

sandbox = Sandbox.create("base")

try:
    sandbox.commands.run("sleep 120", timeout=5)
except TimeoutException as e:
    print("the command ran out of time", e)
except SandboxException as e:
    print("the sandbox refused the call", e)
finally:
    sandbox.kill()
```

</Tab>
</CodeTabs>

<Callout kind="warning">
`AuthenticationError` and `BuildError` do **not** extend `SandboxError`. They
extend the language's own `Error` / `Exception`, so a single
`instanceof SandboxError` branch will miss both, and their subclasses
`GitAuthError` and `FileUploadError` with them.
</Callout>

## The error classes

<div className="docs-table-wrap">
<table>
  <thead>
    <tr><th>TypeScript</th><th>Python</th><th>Extends</th><th>Thrown when</th></tr>
  </thead>
  <tbody>
    <tr><td>`SandboxError`</td><td>`SandboxException`</td><td>`Error`</td><td>The base class, and the fallback for any status or RPC code with no mapping of its own</td></tr>
    <tr><td>`TimeoutError`</td><td>`TimeoutException`</td><td>`SandboxError`</td><td>The sandbox timed out, the request timed out, or a long-running call passed its own timeout</td></tr>
    <tr><td>`InvalidArgumentError`</td><td>`InvalidArgumentException`</td><td>`SandboxError`</td><td>The sandbox rejected an argument, or the SDK rejected one before sending it</td></tr>
    <tr><td>`NotEnoughSpaceError`</td><td>`NotEnoughSpaceException`</td><td>`SandboxError`</td><td>The sandbox's disk is full</td></tr>
    <tr><td>`NotFoundError`</td><td>`NotFoundException`</td><td>`SandboxError`</td><td>Deprecated. A resource was not found. Catch the two classes below instead</td></tr>
    <tr><td>`FileNotFoundError`</td><td>`FileNotFoundException`</td><td>`NotFoundError`</td><td>A file or directory inside the sandbox does not exist</td></tr>
    <tr><td>`SandboxNotFoundError`</td><td>`SandboxNotFoundException`</td><td>`NotFoundError`</td><td>The sandbox does not exist, or is no longer running</td></tr>
    <tr><td>`RateLimitError`</td><td>`RateLimitException`</td><td>`SandboxError`</td><td>You are being rate limited</td></tr>
    <tr><td>`TemplateError`</td><td>`TemplateException`</td><td>`SandboxError`</td><td>The template is too old for the call, or an alias or tag call was rejected</td></tr>
    <tr><td>`GitUpstreamError`</td><td>`GitUpstreamException`</td><td>`SandboxError`</td><td>`git push` or `git pull` ran on a branch with no upstream. Deprecated with the git module; run git through `commands.run` instead</td></tr>
    <tr><td>`CommandExitError`</td><td>`CommandExitException`</td><td>`SandboxError`</td><td>A command exited non-zero</td></tr>
    <tr><td>`AuthenticationError`</td><td>`AuthenticationException`</td><td>`Error`</td><td>The key is missing, malformed, or the server rejected it</td></tr>
    <tr><td>`GitAuthError`</td><td>`GitAuthException`</td><td>`AuthenticationError`</td><td>`git clone`, `push` or `pull` failed to authenticate with the remote. Deprecated with the git module; run git through `commands.run` instead</td></tr>
    <tr><td>`BuildError`</td><td>`BuildException`</td><td>`Error`</td><td>A template build failed. TypeScript only in practice</td></tr>
    <tr><td>`FileUploadError`</td><td>`FileUploadException`</td><td>`BuildError`</td><td>Uploading the build context failed. TypeScript only in practice</td></tr>
  </tbody>
</table>
</div>

`VolumeError` / `VolumeException` is exported too, but nothing throws it:
neither SDK ships a volume client. `BuildException` and `FileUploadException`
are exported by the Python SDK for parity in the same way. The Python SDK
covers sandboxes only and has no template builder, so nothing raises either of
them there; the template builder is TypeScript-only.

No error carries a retryable flag. `RateLimitError` is the only one whose
message tells you a retry is worth trying.

## HTTP and RPC mapping

Three transports produce errors, and each has its own map.

#### The control plane

Calls to `https://api.sandbox.impello.ai` — create, connect, list, kill,
`setTimeout`, pause, snapshots, metrics, template builds.

<div className="docs-table-wrap">
<table>
  <thead>
    <tr><th>Status</th><th>Error</th></tr>
  </thead>
  <tbody>
    <tr><td>`401`</td><td>`AuthenticationError`. The message contains `Unauthorized, please check your credentials.` (prefixed with the status code in Python), and the server's own sentence is appended after a hyphen when it sends one</td></tr>
    <tr><td>`429`</td><td>`RateLimitError`. The message contains `Rate limit exceeded, please try again later` (prefixed with the status code in Python), and the server's own sentence is appended after a hyphen when it sends one</td></tr>
    <tr><td>`404`</td><td>`SandboxNotFoundError` from `getInfo`, `getMetrics`, `setTimeout`, `updateNetwork`, `pause`, `createSnapshot` and `connect`</td></tr>
    <tr><td>anything else</td><td>`SandboxError`, and its message is the status code, a colon and the server's own sentence. The template-build calls raise `BuildError` in its place, and the alias and tag calls raise `TemplateError`</td></tr>
  </tbody>
</table>
</div>

`kill` and `deleteSnapshot` are the exceptions: a `404` from either returns
`false` rather than throwing, because the thing you asked to delete is gone
either way.

#### The sandbox over HTTP

Filesystem reads, writes and uploads go to the sandbox itself.

<div className="docs-table-wrap">
<table>
  <thead>
    <tr><th>Status</th><th>Error</th></tr>
  </thead>
  <tbody>
    <tr><td>`400`</td><td>`InvalidArgumentError`</td></tr>
    <tr><td>`401`</td><td>`AuthenticationError`</td></tr>
    <tr><td>`404`</td><td>`NotFoundError`, and `FileNotFoundError` on filesystem calls</td></tr>
    <tr><td>`429`</td><td>`RateLimitError`</td></tr>
    <tr><td>`502`</td><td>`TimeoutError` — this is what a sandbox that has already timed out looks like</td></tr>
    <tr><td>`507`</td><td>`NotEnoughSpaceError`</td></tr>
    <tr><td>anything else</td><td>`SandboxError`</td></tr>
  </tbody>
</table>
</div>

#### The sandbox over RPC

Commands, the terminal and directory watches run over an RPC stream, so they
carry status codes rather than HTTP statuses.

<div className="docs-table-wrap">
<table>
  <thead>
    <tr><th>Code</th><th>Error</th></tr>
  </thead>
  <tbody>
    <tr><td>`InvalidArgument`</td><td>`InvalidArgumentError`</td></tr>
    <tr><td>`Unauthenticated`</td><td>`AuthenticationError`</td></tr>
    <tr><td>`NotFound`</td><td>`NotFoundError`, and `FileNotFoundError` on filesystem calls</td></tr>
    <tr><td>`ResourceExhausted`</td><td>`RateLimitError`</td></tr>
    <tr><td>`Unavailable`</td><td>`TimeoutError` — the sandbox timed out</td></tr>
    <tr><td>`Canceled`</td><td>`TimeoutError` — the request was cancelled in flight. In TypeScript the request timeout fired, or you aborted the `signal` you passed in. In Python the server or a proxy cancelled it, for example a sandbox that was paused or shut down</td></tr>
    <tr><td>`DeadlineExceeded`</td><td>`TimeoutError` — the call's own timeout was exceeded</td></tr>
    <tr><td>anything else</td><td>`SandboxError`</td></tr>
  </tbody>
</table>
</div>

Three different failures therefore arrive as `TimeoutError`: the sandbox is
gone, your request timeout fired, or the call's own timeout fired. Read the
message — the TypeScript message names the option to change. In Python a
request timeout arrives as `DeadlineExceeded` over RPC, and as a
`TimeoutException` naming `request_timeout` on the HTTP calls. If the
connection is dropped mid-request, the SDK probes the sandbox's health. It
raises `TimeoutError` only once the probe confirms the sandbox is gone;
otherwise the transport error reaches you unchanged. See
[Timeouts](/docs/sandbox/timeouts).

Commands and directory watches are the exception to the `Unavailable` row
above. An `Unavailable` on the first stream event raises
`SandboxNotFoundError` in TypeScript, with the message `Sandbox is probably
not running anymore`. Catch that class as well as `TimeoutError` around
`commands.run` and `files.watchDir`. The Python SDK maps `UNAVAILABLE` to
`TimeoutException` in every case, so the two SDKs differ here.

## Command exits

`commands.run` waits for the process and throws when it exits non-zero. The
error carries the whole result, so you do not need to re-run anything to see
what happened.

<CodeTabs>
<Tab label="TypeScript">

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

const sandbox = await Sandbox.create('base')

try {
  await sandbox.commands.run('cat /home/user/missing.txt')
} catch (err) {
  if (err instanceof CommandExitError) {
    console.error(err.exitCode, err.stderr, err.stdout)
  }
} finally {
  await sandbox.kill()
}
```

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

```python
from impello import Sandbox, CommandExitException

sandbox = Sandbox.create("base")

try:
    sandbox.commands.run("cat /home/user/missing.txt")
except CommandExitException as e:
    print(e.exit_code, e.stderr, e.stdout)
finally:
    sandbox.kill()
```

</Tab>
</CodeTabs>

`CommandExitError` exposes `exitCode`, `error`, `stdout` and `stderr`
(`exit_code`, `error`, `stdout`, `stderr` in Python). A background command
throws the same error from its handle's `wait()`. See
[Run commands](/docs/sandbox/commands).

## Authentication

The SDKs check the shape of your key before any request leaves the process. A
key the SDK cannot recognise as a key at all raises `AuthenticationError`
immediately, without a round trip. The current prefix is `imp_` followed by
hex characters. Whether the key is a *good* key is the server's answer, and
that arrives as `401`.

<div className="docs-table-wrap">
<table>
  <thead>
    <tr><th>Cause</th><th>Error</th></tr>
  </thead>
  <tbody>
    <tr><td>No key in `IMPELLO_API_KEY` and none passed in</td><td>`AuthenticationError`, before any request</td></tr>
    <tr><td>Key is not `imp_` plus hex</td><td>`AuthenticationError`, before any request</td></tr>
    <tr><td>An older key, minted under the prefix Impello used before it moved to its own</td><td>Python raises `AuthenticationException` before any request, with a message that names the old prefix. TypeScript accepts the shape and the server answers `401`. See [Migrate](/docs/migrate-from-e2b)</td></tr>
    <tr><td>Key is well-formed but the server rejects it</td><td>`AuthenticationError` from the `401`</td></tr>
  </tbody>
</table>
</div>

Keys are created and read at
[dashboard.impello.ai/keys](https://dashboard.impello.ai/keys). See
[API keys](/docs/api-keys). In TypeScript, set `IMPELLO_VALIDATE_API_KEY=false`
to skip the local shape check. The Python SDK always performs it, and its
`validate_api_key` option has no effect.

## Refused creates

A create can be refused for reasons that have nothing to do with your code.
No credit left, a spend ceiling you set on your own team, or a plan limit. The
plan limits are how many sandboxes you may run at once, how fast you may start
them, and how long a sandbox may live. A sandbox bigger than your plan allows
— vCPU, memory or disk — is refused the same way. A create is also refused,
never queued, when there is no capacity free right now.

There is no dedicated error class for any of these, and they all surface the
same way. Read the message rather than branching on the type: the class you
get depends on the status the API returns. A `429` from any cause becomes
`RateLimitError`, and a status with no mapping of its own becomes
`SandboxError`, whose message is the status and the server's own sentence.

<Callout kind="note">
A team that has run out of credit can still list its sandboxes and kill them.
Only `create` is refused, and sandboxes already running get a 24-hour grace
period before they are stopped.
</Callout>

Top the team up or raise the ceiling at
[dashboard.impello.ai/billing](https://dashboard.impello.ai/billing); the plan
limits themselves are on [Limits and pricing](/docs/limits-and-pricing).
Retrying does not help while the reason is your credit, your ceiling or a plan
limit. Back off and retry a capacity refusal or a
`RateLimitError`: both clear on their own. Impello publishes one address,
[legal@impello.ai](mailto:legal@impello.ai). Write there when a refusal is not
explained by your plan or your credit; there is no other support channel yet.

## Old templates

`TemplateError` on a working call means the sandbox's agent is older than the
feature you asked for. Rebuild the template and the error goes away.

<div className="docs-table-wrap">
<table>
  <thead>
    <tr><th>Call</th><th>Needs</th></tr>
  </thead>
  <tbody>
    <tr><td>Creating a sandbox</td><td>`0.1.0`</td></tr>
    <tr><td>Recursive `files.watchDir`</td><td>`0.1.4`</td></tr>
    <tr><td>`getMetrics`</td><td>`0.1.5`</td></tr>
    <tr><td>`metadata` on `files.write`</td><td>`0.6.2`</td></tr>
    <tr><td>`includeEntry` on `files.watchDir`</td><td>`0.6.3`</td></tr>
    <tr><td>`allowNetworkMounts` on `files.watchDir`</td><td>`0.6.4`</td></tr>
  </tbody>
</table>
</div>

A sandbox created from a template older than `0.1.0` is killed for you before
the error is raised. See
[Build a custom template](/docs/templates/build).

## Python names

Every class above that the Python SDK raises is exported from the top-level
`impello` package, with the same name and the same base class, ending in
`Exception` instead of `Error`:

```python
from impello import (
    AuthenticationException,
    CommandExitException,
    FileNotFoundException,
    GitAuthException,
    GitUpstreamException,
    InvalidArgumentException,
    NotEnoughSpaceException,
    NotFoundException,
    RateLimitException,
    SandboxException,
    SandboxNotFoundException,
    TemplateException,
    TimeoutException,
)
```

The async API raises exactly the same exceptions as the sync one.

Next: [Limits and pricing](/docs/limits-and-pricing).
