Skip to main content
Every error raised by the SDK extends a common base class - SandboxError in TypeScript, SandboxError in Python - so you can catch it broadly or narrow down to a specific case.

Error hierarchy

Python renames TimeoutError to SandboxTimeoutError so it doesn’t shadow Python’s built-in TimeoutError.

Catching a specific error

Catch the typed subclass you care about and fall back to SandboxError for everything else.

Base error properties

Every SDK error extends SandboxError and exposes:
  • statusCode?: number / status_code: int | None - HTTP status, if the error originated from an API response
  • code?: string / code: str | None - API-provided error code, when present

Idempotent operations

kill() / kill_by_id() swallow 404 internally - it’s safe to call on a sandbox that was already deleted by another process. Every other method surfaces NotFoundError to the caller.

Retries

The SDK automatically retries transient failures on GET and DELETE requests with exponential backoff and jitter:
  • 429 Too Many Requests
  • 5xx server errors
  • Network errors (connection reset, DNS failure)
POST / PATCH requests are not retried automatically because they aren’t safely idempotent - the SDK surfaces the error so you can decide.

RateLimitError

Raised on 429 responses. Branch on code to distinguish the variant: The error message already includes the cap and a contact-support hint, so surfacing it directly to users is usually fine.

Billing errors (402)

Creating or resuming a sandbox fails with 402 Payment Required when the team’s billing does not allow new compute. This includes the automatic resume when you connect to a paused sandbox. There is no 402 subclass: the SDK raises the base SandboxError with statusCode / status_code set to 402. Branch on code: The API response for free_allowance_exhausted also includes resets_at, the UTC time (RFC 3339) when sandboxes can start again. The SDK error does not carry resets_at, so read it from the raw API response if you need it.

Unpaid invoices

An unpaid invoice also returns billing_ineligible, on every plan. There is no separate code. When a card payment for an invoice is declined:
  1. Billing members get one email for that invoice. It shows when sandboxes will be paused and has links to pay the invoice and to update the payment method.
  2. A temporary decline, such as insufficient funds, is retried once. A permanent decline, such as an expired, lost, or fraudulent card, is not retried.
  3. If the invoice is still unpaid 24 hours after the first payment attempt, running sandboxes are paused, and new creates and resumes fail with billing_ineligible.
Pay the invoice from the link in the email. Sandboxes can start again once the invoice is paid.

BuildError

Raised by Template.waitUntilReady() / wait_until_ready() when the awaited build lands on status failed. Fields:
  • code - stable error prefix (image_pull_failed, unsupported_base, step_failed, boot_failed, snapshot_failed, start_cmd_failed, ready_cmd_failed, capacity_unavailable, build_timed_out, infrastructure_failed, build_failed)
  • buildId / build_id - the build that failed
  • templateId / template_id - the template id
  • message - human-readable detail. The backend’s error_message follows a "<code>: <detail>" convention (e.g. "image_too_large: image is too large for the requested disk_mib"); the SDK splits it so code holds the prefix and message holds the detail. When the backend doesn’t include a prefix or message, message falls back to "Template build failed".
See BuildSpec reference: build error codes for a full list of codes and their meanings.