JS SDK

JS SDK reference

The orkestr npm package is the official client for the sandbox API. TypeScript-first with a flat .d.ts. Ships dual ESM and CJS builds and works on any runtime with native fetch - Node 18+, Bun, Deno, Cloudflare Workers, browser.

Private beta
Sandboxes are in private beta and not yet publishable to npm. Request access and we'll send install instructions.

Install

terminal
npm install orkestr
# or
pnpm add orkestr
# or
yarn add orkestr

Authentication

Every request is authenticated with a Bearer API token. The SDK looks for it in three places, in order:

  • The apiKey option on Sandbox.create() etc.
  • A pre-built default client set via setDefaultClient()
  • The ORKESTR_API_KEY environment variable
auth.ts
import { Sandbox, OrkestrClient } from "orkestr";

// 1) Implicit: SDK reads ORKESTR_API_KEY from process.env
const sbx = await Sandbox.create({ template: "python-3.12" });

// 2) Explicit: per-call apiKey
const sbx2 = await Sandbox.create({
  template: "python-3.12",
  apiKey: "ork_...",
});

// 3) Reusable client (good for long-running agents)
import { setDefaultClient } from "orkestr";
setDefaultClient(new OrkestrClient({
  apiKey: "ork_...",
  baseUrl: "https://api.orkestr.eu",
}));
Scoped tokens
Mint tokens scoped only to sandboxes:read and sandboxes:write for agent runtimes. A scoped token cannot reach the rest of the orkestr platform if it leaks.

Sandbox class

Sandbox is the main public class. Static methods create / fetch / list / resume sandboxes; instance methods drive exec, files, and lifecycle. All async.

Sandbox.create

javascript
import { Sandbox } from "orkestr";

const sbx = await Sandbox.create({
  template: "python-3.12",
  cpu: 1.0,
  memoryMb: 2048,
  network: "off",
  timeoutSeconds: 600,
  env: { OPENAI_API_KEY: "sk-..." },
  metadata: { agentRun: "r_42" },
  region: "fsn1",
});
console.log(sbx.id, sbx.status);

Field names are camelCase. The SDK converts to snake_case on the wire so request bodies match the REST schema.

ParameterTypeDescription
templaterequiredTemplateOne of python-3.12, python-3.12-bare, node-22, ubuntu-24.04.
cpunumbervCPU allocation. Default 1.0. Plan-capped.
memoryMbnumberRAM in MB. Default 2048. Plan-capped.
networkNetworkMode"off" (default), "restricted", or "open".
timeoutSecondsnumberSandbox auto-terminates after this many seconds. Default 600.
envRecord<string, string>Environment variables exposed to processes in the sandbox.
metadataRecord<string, string>Caller-defined tags echoed back on every response. Max 16 keys, 256 chars per value.
regionstringRegion preference. "fsn1" or "hel1". Omit to let the control plane pick.
apiKeystringOverride the API key for this call. Falls back to default client / env var.
baseUrlstringOverride the API base URL for this call.

Sandbox.get and Sandbox.list

javascript
// Reattach from another process
const sbx = await Sandbox.get("sbx_01HXYZ...");

// List your sandboxes
const running = await Sandbox.list({ status: "running" });
for (const sbx of running) {
  console.log(sbx.id, sbx.template, sbx.createdAt);
}

sbx.exec

Run a shell command and resolve with the buffered result. Throws ExecTimeout if the command exceeds timeoutSeconds.

javascript
const result = await sbx.exec("python /workspace/main.py", {
  timeoutSeconds: 60,
});
result.stdout;      // string
result.stderr;      // string
result.exitCode;    // number
result.durationMs;  // number

sbx.execStream

Async iterable that yields chunks as they arrive. The final chunk carries exitCode and durationMs; chunks before it carry process output. Iterate to completion - breaking early leaves the in-VM process running until its own timeout fires.

javascript
for await (const chunk of sbx.execStream("python long_task.py")) {
  if (chunk.stream === "stdout") {
    process.stdout.write(chunk.data);
  } else {
    process.stderr.write(chunk.data);
  }
  if (chunk.exitCode !== undefined) {
    console.log(`exit=${chunk.exitCode} duration=${chunk.durationMs}ms`);
  }
}

Files namespace

sbx.files is the file operations namespace. All methods are async. Writes go to writable roots; reads work anywhere readable inside the sandbox.

javascript
// Text I/O
await sbx.files.write("/workspace/script.py", "console.log('hi')");
const content = await sbx.files.read("/workspace/output.txt");

// Binary I/O
await sbx.files.writeBytes(
  "/workspace/blob.bin",
  new Uint8Array([0, 1, 2]),
);
const raw = await sbx.files.readBytes("/workspace/blob.bin");

// Directory listing
for (const entry of await sbx.files.list("/workspace")) {
  console.log(entry.name, entry.isDir, entry.size, entry.mode);
}

// Remove
await sbx.files.delete("/workspace/output.txt");

sbx.pause and Sandbox.resume

pause() snapshots the sandbox and stops the compute meter. Returns the sandbox id - persist it wherever your agent keeps state and pass to Sandbox.resume() when you want to come back.

javascript
// Pause: returns the sandbox id, stops compute meter
const sandboxId = await sbx.pause();

// ... later ...
const resumed = await Sandbox.resume(sandboxId);

Sandbox.withTemp

Convenience wrapper that creates a sandbox, runs your callback, and terminates the sandbox - even if the callback throws. Use this for short-lived "do one thing" loops; otherwise call terminate() yourself.

javascript
import { Sandbox } from "orkestr";

await Sandbox.withTemp({ template: "python-3.12" }, async (sbx) => {
  const result = await sbx.exec("python -c 'print(2+2)'");
  console.log(result.stdout);
});
// terminate() is awaited automatically on return, including on thrown errors

Result types

All result types are flat objects. ExecResult has stdout, stderr, exitCode, durationMs. ExecChunk has stream, data, plus exitCode and durationMs on the final chunk only. FileEntry has name, isDir, size, mode, modifiedAt (a Date).

Errors

Every error the SDK throws extends OrkestrError. Use instanceof to dispatch.

javascript
import {
  Sandbox,
  OrkestrError,
  AuthError,
  ExecTimeout,
  SandboxNotFound,
  SnapshotCapReached,
} from "orkestr";

try {
  const sbx = await Sandbox.create({ template: "python-3.12" });
  await sbx.exec("sleep 9999", { timeoutSeconds: 5 });
} catch (err) {
  if (err instanceof ExecTimeout) {
    console.log("Command timed out; sandbox is still running.");
  } else if (err instanceof AuthError) {
    console.error(`Auth failed (${err.statusCode}): ${err.detail}`);
  } else if (err instanceof OrkestrError) {
    console.error(`Other failure: ${err.message} requestId=${err.requestId}`);
  } else {
    throw err;
  }
}
ParameterTypeDescription
AuthErrorOrkestrErrorAPI key missing, invalid, expired, or scope insufficient. 401 / 403.
RateLimitErrorOrkestrErrorPlan rate limit hit. retryAfter attribute carries the recommended wait.
PlanLimitErrorOrkestrErrorPlan tier limit hit.
SnapshotCapReachedPlanLimitErrorpause() blocked because the snapshot retention cap is full.
SandboxNotFoundOrkestrErrorSandbox id doesn't exist or doesn't belong to the caller. 404.
SandboxNotReadyOrkestrErrorOperation called on a sandbox in the wrong state (e.g. exec on paused). 409.
ExecTimeoutOrkestrErrorexec exceeded timeoutSeconds.
ExecKilledOrkestrErrorExec process exited via a signal. signal attribute carries the signal number.
NetworkPolicyErrorOrkestrErrorIn-sandbox code tried to reach a host blocked by the network policy.
ConnectionErrorOrkestrErrorTransport-level failure reaching api.orkestr.eu (DNS, TCP, timeout before bytes).

Field naming

The REST API uses snake_case; this SDK exposes camelCase to feel native to JS code. The client converts at the boundary - request bodies and query params, response fields, and error details are all rewritten for you. Field names in this reference match what you see in your IDE.

Versioning

SDK versioning follows Semantic Versioning. The SDK targets the v1 sandbox API; non-breaking additions to the API ship as SDK patch / minor bumps. A future v2 API will require an SDK major.