PlatformSandboxes

Sandboxes reference

Find the current beta API calls and constraints for creating, running, and managing Sandboxes.

Access-gated beta. 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.20.0 or newer for the original beta API. The newer secret-selection contract is documented for SDK 4.21.
  • Enable Sandboxes beta access for the Inngest environment and set INNGEST_SIGNING_KEY. A 403 access_denied response means access is not enabled.
  • Keep the signing key and Sandbox client out of browser code.
  • The beta uses the default runtime image. Fresh Sandboxes accept exactly 1 vCPU / 1024 MiB, 2 / 2048, or 4 / 4096. A clone inherits its snapshot's resources.

Choose the TypeScript surface

SurfaceUse it forBehavior
step.sandboxSandbox work inside an Inngest functionEach call is a durable step with a stable step ID. It memoizes the API result and applies step retry classification.
inngest.sandboxesAPI routes, workers, and trusted server scriptsCalls 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

TaskDirect clientDurable step
Create, list, getinngest.sandboxes.create(options), .list(options?), .get(id)step.sandbox.create(id, options), .list(id, options?), .get(id, sandboxId)
Wait, destroysandbox.waitUntilRunning(options?), .destroy()sandbox.waitUntilRunning(id, options?), .destroy(id)
Pause, resumesandbox.pause(options?), .resume(options?)sandbox.pause(id, options?), .resume(id, options?)
Captured commandsandbox.commands.run(command, options?)sandbox.commands.run(id, command, options?)
Processessandbox.processes.start/list/get; process getOutput/signal/waitThe same operations with a stable step ID first
Snapshotssandbox.snapshot(options?, waitOptions?); snapshot.clone(options); snapshot list/get/deleteThe same operations with a stable step ID first
Files and live streamssandbox.files.upload/download; sandbox.logs.stream; process.streamOutputUnavailable 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. Pause can interrupt external connections. There is no in-place restore from a user snapshot.

Commands, processes, and files

  • commands.run waits for one command and returns stdout, stderr, exitCode, and output-truncation metadata. A nonzero exit code is a result, not an SDK exception. 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.start starts a background process. The returned process has a public UUID. Use list, get, getOutput, signal, and wait to 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.

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.