Sandboxes reference
Find the current beta API calls and constraints for creating, running, and managing Sandboxes.
Open beta. Sandboxes are available on Pro plans and above. The TypeScript and REST contracts can change without a compatibility period. Use this reference to evaluate the current API. See the Limits page for resource and output limits.
Before you call the beta API
- Use a trusted server-side Node.js 20+ runtime with the standard Fetch API and
inngest@4.21.1or newer. Version 4.21.1 includes fixes for snapshot and list response validation required by these guides. - Use an Inngest Cloud account on Pro or above and set
INNGEST_SIGNING_KEY. A403 access_deniedresponse means the account's plan does not include Sandboxes; upgrade your plan. - Keep the signing key and Sandbox client out of browser code.
- New Sandboxes use the default image unless you choose another. Fresh Sandboxes accept 1, 2, or 4 vCPU with 1, 2, or 4 GiB of memory per vCPU (for example
2 / 2048,2 / 4096, or2 / 8192); see supported sizes. A clone inherits its snapshot's image and resources.
Choose the TypeScript surface
| Surface | Use it for | Behavior |
|---|---|---|
step.sandbox | Sandbox work inside an Inngest function | Each call is a durable step with a stable step ID. It memoizes the API result and applies step retry classification. |
inngest.sandboxes | API routes, workers, and trusted server scripts | Calls run immediately. The caller owns retries, cancellation, and cleanup. |
Configure the middleware to add step.sandbox:
import { Inngest } from "inngest";
import { sandboxMiddleware } from "inngest/experimental";
export const inngest = new Inngest({
id: "my-app",
middleware: [sandboxMiddleware()],
});
The direct inngest.sandboxes client does not require middleware. Both surfaces use the client's environment and signing-key configuration.
Durable call shape
Every step.sandbox operation takes a stable step ID first. Keep IDs stable across replays and deployments.
const sandbox = await step.sandbox.create("create-sandbox", {
name: `job-${event.data.jobId}`,
vcpu: 1,
memoryMb: 1024,
});
const result = await sandbox.commands.run(
"run-tests",
["npm", "test"],
{ cwd: "/workspace/repo", timeout: "5m" },
);
await sandbox.destroy("destroy-sandbox");
Use a stable job-derived name. A matching active name and configuration returns the existing Sandbox; a conflicting configuration returns sandbox_name_taken. Completed calls replay from memoized step results. Step memoization does not preserve guest processes or files after the Sandbox ends.
Operation index
| Task | Direct client | Durable step |
|---|---|---|
| Create, list, get | inngest.sandboxes.create(options), .list(options?), .get(id) | step.sandbox.create(id, options), .list(id, options?), .get(id, sandboxId) |
| Wait, destroy | sandbox.waitUntilRunning(options?), .destroy() | sandbox.waitUntilRunning(id, options?), .destroy(id) |
| Pause, resume | sandbox.pause(options?), .resume(options?) | sandbox.pause(id, options?), .resume(id, options?) |
| Captured command | sandbox.commands.run(command, options?) | sandbox.commands.run(id, command, options?) |
| Processes | sandbox.processes.start/list/get; process getOutput/signal/wait | The same operations with a stable step ID first |
| Snapshots | sandbox.snapshot(options?, waitOptions?) (options.name names it); snapshots.list({ name }) filters by name; snapshot.clone(options); snapshot list/get/delete | The same operations with a stable step ID first |
| One-call runners | Unavailable | step.sandbox.python(id, code, options?), step.sandbox.typescript(id, code, options?) (experimental) |
| Files and live streams | sandbox.files.upload/download; sandbox.logs.stream; process.streamOutput | Unavailable through step.sandbox |
Sandbox lifecycle
PENDING → STARTING → RUNNING → PAUSING → PAUSED → RESUMING → RUNNING is the normal pause path. Destroy moves a Sandbox through TERMINATING to TERMINATED. A Sandbox can also enter FAILED.
Commands, files, logs, and process operations require RUNNING. Create waits for RUNNING by default, up to 120 seconds. Set runningTimeout: false to get the Sandbox ID immediately, then call waitUntilRunning separately. A readiness timeout stops the wait; it does not cancel Create or destroy the Sandbox. SDK objects show the state observed by that request; Get or a lifecycle call returns a new object.
Pause and Resume keep the same Sandbox ID, filesystem, memory, environment, and remaining runtime. The current beta allows one hour of active runtime per Sandbox; time spent paused does not consume it. The runtime timeout requests an automatic Pause; see Runtime expiry for the state transition and effects on running commands and processes. Pause can interrupt external connections. There is no in-place restore from a user snapshot.
Commands, processes, and files
commands.runwaits for one command and returnsstdout,stderr,exitCode, and output-truncation metadata. A nonzero exit code is a result, not an SDK exception, unless you passexitCodes(see Commands). A string command runs through/bin/sh -c; an argv array preserves argument boundaries. The observation timeout defaults to 30 seconds and caps at five minutes.processes.startstarts a background process. The returned process has a public UUID. Uselist,get,getOutput,signal, andwaitto manage it. Wait observes the process; its timeout does not stop it. Retained output is bounded, and live streams can miss or replay chunks.- The direct client uploads or downloads one complete regular file at an absolute path, up to 100 MiB. Upload replaces the file. The beta has no directory-list, append, rename, or recursive-transfer API. Files persist across Pause and Resume but end when the Sandbox is destroyed.
- Fresh Sandbox Create can select existing workspace secrets with
secrets: ["SECRET_NAME"]. The SDK and REST API send names, and Inngest injects their values under the same environment variable names. Snapshot clones restore captured values and cannot select new secrets. See Secrets.
Snapshots and clones
sandbox.snapshot() captures memory, disk, environment, image, and resources while the source Sandbox remains running. The SDK waits for READY by default, up to five minutes. A timeout stops polling; snapshot creation may continue. snapshot.clone() creates a new Sandbox ID from a READY snapshot. A clone inherits the captured resources, filesystem, memory, and environment. Snapshots are crash-consistent and expire at a server-defined time. Flush application state before taking one.
A private Pause checkpoint serves only Resume of the same Sandbox. It is not a user snapshot and cannot be cloned. The planned S3-backed persistent filesystem mount shares its backing path across Sandboxes and clones.
Durations
Every Sandbox duration option takes the same inputs: runningTimeout, command and process timeout, snapshot and wait timeouts, and toolOptions.
await step.sandbox.create("build", {
name: "build",
vcpu: 2,
memoryMb: 2048,
runningTimeout: "4m30s", // compound string, v4.24.0+
});
await sandbox.commands.run("test", "npm test", { timeout: "5m" }); // ms string
await sandbox.commands.run("test", "npm test", { timeout: 90_000 }); // milliseconds
await sandbox.commands.run("test", "npm test", {
timeout: Temporal.Duration.from({ minutes: 5 }), // Temporal.Duration
});
- A number is milliseconds.
- A string is an
msvalue such as"30s"or"2h", or several of them joined:"4m30s","1h 30m". Every part needs a unit. - A
Temporal.Durationworks, without calendar years, months, or weeks. - The result must be a positive whole number of milliseconds, and within the limit for that option (see Limits).
Experimental surface v4.25.0+
These can change. Versions are noted where they differ.
| API | Use it for |
|---|---|
step.sandbox.python, step.sandbox.typescript | Create a Sandbox, run code, return a result, and destroy the Sandbox. Options: input, schema.output, tools, cwd, timeout, and any create option. See Managed lifecycle. |
sandbox.$ v4.26.0+, getCommandError, exitCodes on commands.run | Run commands as tagged templates with .text(), .lines() and .json(), and read failures. See Commands. |
tools, toolOptions on commands.run and the runners | Let Sandbox code call your functions as durable steps. See Tools. |
tool.step(), tool.group() from inngest/experimental | Declare a tool with typed input and output, or a tool whose body is a group of steps. |
toolDefinitions(tools) v4.26.0+ | Turn tools into { name, description, input_schema } for a model API. See Tools. |
image.inngest(), image.custom() from inngest/experimental | Choose the image with from. See Base images. |
group.sandbox(id, callback) v4.24.0+ | Run a callback with a fresh Sandbox and destroy it afterwards. |
Stable additions v4.24.0+: await using scopes a durable Sandbox to a block, and durations accept compound strings such as "4m30s".
Errors and cleanup
SandboxError exposes action, code, ambiguous, retryable, requestId, and relevant resource IDs. Branch on these fields rather than message text. The direct client never retries an operation automatically. Durable middleware maps retryable failures to retriable step errors and invalid or unsafe failures to non-retriable errors.
operation_ambiguous means an operation might have completed without a confirmed response. Reconcile by action before repeating it:
exec: inspect the command's external effects or a completion marker.process.start: list processes and compare command and start time.process.signal: get or wait for the process before sending another signal.snapshot.create: list recent snapshots before creating another.
Captured-command timeout and output-limit errors may also mean the command ran. Create, Destroy, Pause, Resume, snapshot Delete, and identical file upload are safe to repeat after an unconfirmed availability failure under the beta contract. Let unexpected durable step errors escape unless the function has a deliberate reconciliation path.
The caller owns cleanup in this beta. Use finally for direct calls. Destroy the Sandbox on a function's successful path and use an onFailure cleanup path when permanent failure must release it. A JavaScript finally block around durable steps does not guarantee cleanup after permanent failure.
Limits
Check Limits for resource, file, output, and quota limits.