PandaStack

Execute Python in an isolated microVM via one API call

How to execute Python in an isolated microVM with one API call — create a Firecracker sandbox, run code, and read stdout from the Python or TypeScript SDK or raw curl.

The fastest way to run untrusted or LLM-generated Python safely: create a PandaStack sandbox, exec your code, read stdout. Every sandbox is a real Firecracker microVM with its own kernel — not a container, not a shared interpreter — and it boots in 179ms at p50 because every create restores a pre-baked snapshot. This page shows the minimum: a couple of SDK lines, or one POST if you're calling the HTTP API directly.

Setup

Install an SDK and export your API key. Never hardcode the key — the SDKs read PANDASTACK_API_KEY from the environment by default.

export PANDASTACK_API_KEY=pds_...

# Python
pip install pandastack

# TypeScript
npm install @pandastack/sdk

Get a key from the dashboard. The public API base is https://api.pandastack.ai; the SDKs target it automatically and you can override the base URL via config if you self-host.

The minimum: create, exec, read stdout

Create a sandbox on the code-interpreter template, run python -c, and read the result off the returned object. The exec call runs a shell command inside the guest and returns stdout, stderr, exit code, and wall-clock duration.

from pandastack import Sandbox

sandbox = Sandbox.create(template="code-interpreter")

result = sandbox.exec("python -c 'print(2 ** 10)'")
print(result.stdout)      # "1024\n"
print(result.exit_code)   # 0

sandbox.kill()
import { Sandbox } from "@pandastack/sdk";

const sandbox = await Sandbox.create({ template: "code-interpreter" });

const result = await sandbox.exec("python -c 'print(2 ** 10)'");
console.log(result.stdout);     // "1024\n"
console.log(result.exit_code);  // 0

await sandbox.kill();

That's the whole loop. Sandbox.create does the snapshot-restore boot, exec runs your command, and you read stdout. Call kill() when you're done — or pass ttl_seconds / ttlSeconds and let it expire on its own.

ExecResult carries everything you need to act on the run:

FieldTypeMeaning
stdoutstrStandard output of the command
stderrstrStandard error
exit_codeintProcess exit status (0 = success)
duration_msintWall-clock execution time

Pass check=True to raise CommandFailed on a non-zero exit instead of inspecting exit_code yourself.

Capturing rich results: run_code

exec("python -c ...") is fine for plain stdout. But if you're running LLM-generated snippets and want typed results — a returned value, a DataFrame as HTML, a chart as a PNG — use a code context instead. It's a persistent Jupyter-style kernel: state survives across calls, and rich output is captured automatically (no savefig, no stdout scraping).

from pandastack import Sandbox

sandbox = Sandbox.create(template="code-interpreter")
ctx = sandbox.create_code_context()  # persistent Python kernel

ex = ctx.run_code("sum(range(101))")
print(ex.text)        # "5050" — the kernel's repr of the last expression
print(ex.stdout)      # anything the code printed

ctx.close()
sandbox.kill()
import { Sandbox } from "@pandastack/sdk";

const sandbox = await Sandbox.create({ template: "code-interpreter" });
const ctx = await sandbox.createCodeContext(); // persistent Python kernel

const ex = await ctx.runCode("sum(range(101))");
console.log(ex.text);     // "5050"
console.log(ex.stdout);

await ctx.close();
await sandbox.kill();

The Execution object exposes .results (a list of typed Results), plus convenience accessors .text, .png, .stdout, .stderr, .error, and .exit_code. The code-interpreter template bakes IPython, matplotlib, plotly, kaleido, and pandas, so charts and tables come back as objects ready to hand to a model. See the code interpreter guide for the full result surface.

Raw HTTP (no SDK)

The SDKs are thin wrappers over a REST API. If you're calling from a language without an SDK, or wiring this into an existing service, hit the endpoints directly. Authenticate every request with Authorization: Bearer $PANDASTACK_API_KEY.

First, create a sandbox:

curl -sS https://api.pandastack.ai/v1/sandboxes \
  -H "Authorization: Bearer $PANDASTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template": "code-interpreter"}'

The response includes the sandbox id:

{ "id": "sbx_3f9a...", "template": "code-interpreter", "status": "running" }

Then exec a command against that id and read stdout off the JSON response:

curl -sS https://api.pandastack.ai/v1/sandboxes/sbx_3f9a.../exec \
  -H "Authorization: Bearer $PANDASTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cmd": "python -c \"print(2 ** 10)\""}'
{ "stdout": "1024\n", "stderr": "", "exit_code": 0, "duration_ms": 41 }

When you're finished, delete the sandbox so it stops counting against your quota:

curl -sS -X DELETE https://api.pandastack.ai/v1/sandboxes/sbx_3f9a... \
  -H "Authorization: Bearer $PANDASTACK_API_KEY"
StepMethod + pathBody
CreatePOST /v1/sandboxes{"template": "code-interpreter"}
RunPOST /v1/sandboxes/{id}/exec{"cmd": "python -c '...'"}
DeleteDELETE /v1/sandboxes/{id}

Why a microVM, not a container

Each sandbox is a full Firecracker guest: its own Linux kernel, its own page tables, hardware-virtualized isolation via KVM. A container shares the host kernel; a microVM does not, which is what makes it safe to run code you don't trust — model output, user submissions, arbitrary pip-installed packages. The cost of that isolation used to be slow boots; PandaStack pays it once at bake time and restores a frozen snapshot on every create, so the strong-isolation path stays sub-200ms.

Next steps

On this page