SandboxCreate, stop and delete

Create, stop, resume and delete sandboxes

A sandbox is a Linux machine you create, pause, wake and delete from code or the terminal. This page covers every step of that life: the create options, waiting, the timers that stop a sandbox for you, and cleanup.

Create a sandbox and delete it

import { Sandbox } from "@tangle-network/sandbox";
 
const client = new Sandbox({
  apiKey: process.env.TANGLE_API_KEY!,
  baseUrl: "https://sandbox.tangle.tools",
});
 
// create() resolves once the sandbox is running.
const box = await client.create({ name: "my-project" });
try {
  const { stdout } = await box.exec("uname -a");
  console.log(box.id, box.status, stdout);
} finally {
  await box.delete();
}

tangle sandbox create waits until the sandbox runs and prints its id, SSH line and URLs. It has no --no-wait flag. Its --timeout flag is the HTTP request timeout in milliseconds, not the sandbox lifetime.

Choose the image, isolation and size

OptionWhat it sets
nameA label you can use in place of the id in the CLI.
metadataYour own key-value tags. Filter and bulk-delete by them.
environmentThe image: a catalog environment, a template, or an image reference. See Templates and images.
runtime"docker" runs a container on a shared kernel; "firecracker" runs a microVM with its own kernel. Omit it for the platform default.
resources{ cpuCores, memoryMB, diskGB }. Omit it for the default shape.
baretrue gives command execution and lifecycle only, without the agent runtime.
agentfalse keeps files, terminals and processes but provisions no coding agent, model credentials or startup scripts. An enabled startup script that applies to the create makes it fail.
const { presets, ceiling } = await client.shapes();
console.log(presets.map((p) => p.name), ceiling);
 
const box = await client.create({
  name: "build-runner",
  runtime: "firecracker",
  resources: { cpuCores: 4, memoryMB: 8192, diskGB: 20 },
  metadata: { project: "checkout", ci: "true" },
});

The CLI takes the same choices as flags: --environment, --runtime, --cpu, --memory (MB), --disk (GB) and --metadata KEY=VALUE. tangle sandbox shapes lists the presets your account can use. The size ceiling for your plan is on Limits.

Retry a create safely

Pass an idempotencyKey when your code may retry. A second create with the same key joins the first provision instead of starting another sandbox.

const box = await client.create({
  name: "nightly-eval",
  idempotencyKey: "nightly-eval-2026-11-09",
});

Both SDKs retry capacity and rate-limit refusals inside the create’s time budget. When the budget runs out, the timeout error names the key to retry with: retry create() with idempotencyKey="..." to join it in TypeScript, idempotency_key="..." in Python. Pass retry: { capacity: false } in TypeScript to get CAPACITY_EXHAUSTED at once and schedule the retry yourself.

Timers that stop or delete a sandbox

OptionWhat happens
maxLifetimeSecondsThe sandbox stops when the interval ends. Create and each resume start a new interval; activity does not extend it. The workspace is kept.
idleTimeoutSecondsThe sandbox stops after this much inactivity. The workspace is kept.
deleteAfterStoppedSecondsA stopped sandbox is deleted after this long. Omit it for the platform default.
const box = await client.create({
  maxLifetimeSeconds: 3600,
  idleTimeoutSeconds: 600,
  deleteAfterStoppedSeconds: 86_400,
});

The CLI flags are --lifetime and --idle-timeout, both in seconds. Defaults and allowed ranges are on Limits.

A stopped sandbox keeps its disk and does not bill compute until you resume it. See Pricing.

Stop and resume

import { Sandbox } from "@tangle-network/sandbox";
 
const client = new Sandbox({
  apiKey: process.env.TANGLE_API_KEY!,
  baseUrl: "https://sandbox.tangle.tools",
});
 
// Stopped: the disk stays. Resumed: every file is there.
const box = await client.create();
try {
  await box.exec("echo 'first draft' > notes.md");
  await box.stop();
  await box.resume();
  const { stdout } = await box.exec("cat notes.md");
  console.log(stdout.trim());
} finally {
  await box.delete();
}

In TypeScript, stop() waits until the sandbox has stopped. stop({ wait: false }) returns a SandboxOperation once the stop is accepted; call await op.wait() or read it later with box.getOperation(op.id). stop({ retain: { paths: ["reports"] } }) seals the coding agent’s sessions and the listed workspace paths before it answers, and needs the default wait: true.

The SDK does not resume for you: a command on a stopped sandbox throws INVALID_STATE, so call resume() first. The CLI does: tangle exec, ssh, fs, git, process, run and agent prompt resume a stopped sandbox before they run. tangle sandbox stop --no-wait prints the operation id and its status URL.

After a resume

A coding agent’s credential does not survive a stop. Pass backend again on the first prompt after resume():

const again = await client.get(boxId);
if (!again) throw new Error("sandbox not found");
await again.resume();
const run = await again.prompt("Implement step 1 of your plan.", {
  sessionId: "oauth",
  backend,
});

A resume can also be refused because the platform did not keep some environment values. Branch on the error code and pass the values back:

import { SandboxError } from "@tangle-network/sandbox";
 
try {
  await box.resume();
} catch (error) {
  if (error instanceof SandboxError && error.code === "RESUME_ENV_REQUIRED") {
    const env = Object.fromEntries(
      (error.missingEnvKeys ?? []).map((key) => [key, process.env[key] ?? ""]),
    );
    await box.resume({ env });
  } else {
    throw error;
  }
}

box.rebuild() takes the same options as resume() and provisions the sandbox again from its stored config.

Find sandboxes

const box = await client.get("sbx_123"); // null when it does not exist
const running = await client.list({ status: "running", limit: 20 });
const team = await client.list({ scope: "team" });

list() filters by status (one or several), scope ("personal", "team", "all" or `team:${id}`), limit, offset and run. In the CLI, tangle sandbox list and tangle sandbox get <id-or-name>.

Delete

const ack = await box.delete();
console.log(ack.outcome); // "destroyed", "already_absent" or "unknown"
 
await box.delete({ until: "removed" }); // also waits until storage is reclaimed

delete() resolves when the sandbox is gone and billing has stopped; removal reads "pending" until its storage is reclaimed. until: "removed" waits for that too.

A sandbox with live branch children is refused with 409 CHILDREN_EXIST. Delete the children first, or delete the whole lineage with cascade. box.delete() and tangle sandbox delete have no cascade option; use deleteMatching or the REST route:

const result = await client.deleteMatching(
  { metadataPrefix: { project: "checkout" } },
  { cascade: true },
);
curl -X DELETE "https://sandbox.tangle.tools/v1/sandboxes/$SANDBOX_ID?cascade=true" \
  -H "Authorization: Bearer $TANGLE_API_KEY"

tangle sandbox delete asks for confirmation; pass --force in scripts. Deleting a sandbox does not delete snapshots in your own storage. See Keep data after delete.

Python and REST

The Python SDK covers create, wait_for, get, list(ListSandboxOptions(status=...)), stop, resume and delete. It has no rebuild, lifecycle operations, env recovery or delete_matching, and its list has no scope or run filter. Its create options do not include delete_after_stopped_seconds, cold_timeout_seconds, retain or cwd. Send those through the REST API with client.fetch, which returns the raw response and does not raise on an error status:

response = client.fetch(
    "POST",
    "/v1/sandboxes",
    json={"name": "nightly", "deleteAfterStoppedSeconds": 86400},
)
print(response.status_code, response.json())
 
response = client.fetch("DELETE", f"/v1/sandboxes/{box.id}", params={"cascade": "true"})

The states a sandbox moves through are drawn in How sandboxes work.

Next