omg/docs
Sandbox API

Templates

Start from a baked snapshot, pick a coding-agent preset, or bake your own template with the typed SDK baker.

Two template fields on POST /v1/sandboxes, for two different things:

  • templateId — fork from a baked snapshot in the template registry (e.g. "react-ts"). The sandbox boots with files and node_modules already in place.
  • template — a coding-agent preset slug (opencode | pi | codex | claude | lfg). The sandbox comes up with that CLI agent installed and ready in the terminal.

They are mutually exclusive — sending both returns 400.

Snapshot templates (templateId)

curl -sS https://infra.omg.dev/v1/sandboxes \
  -H "Authorization: Bearer $OMG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "templateId": "react-ts", "ports": [5173] }'

react-ts gives you a ready-to-run web app at /home/user/project:

  • Vite + React 19 + TypeScript, Tailwind CSS v4
  • @omg-dev/* SDK preinstalled (sdk, schema, server, auth, ai, media, pwa, db-collection) with functions/ and server/ scaffolding
  • Dependencies already installed — bun run dev serves on :5173 immediately, no bun install wait

Because the sandbox is restored from a snapshot instead of cold-booting and installing, create-to-serving is seconds, not minutes.

Agent presets (template)

A preset creates a raw sandbox (skipAppProcesses is forced true) with one or more coding-agent CLIs installed. Unknown slugs fail with 400.

SlugInstallsServes
opencodeopencode CLITUI only
pipi coding agentTUI only
codexOpenAI Codex CLITUI only
claudeClaude CodeTUI only
lfgall four agents + the lfg web UIweb UI on :8766
curl -sS https://infra.omg.dev/v1/sandboxes \
  -H "Authorization: Bearer $OMG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "template": "claude" }'

The create response adds preset fields on top of the normal sandbox shape:

{
  "id": "abc123def456",
  "status": "running",
  "template": "claude",
  "templateStatus": "ready",
  "installLogPath": "/home/user/.omg/agent-install.log"
}
  • templateStatus is ready or failed (install/verify runs before the response returns). On failed, bootstrapError says why and the sandbox is kept so you can inspect the install log.
  • lfg also returns servePort: 8766 and serveLogPath; its web UI is reachable via GET /v1/sandboxes/{id}/url/8766. For lfg, ports defaults to [8766] when you don't pass any.
  • TUI presets have no web server — connect over the shell WebSocket and run the agent by name (claude, codex, …).

What the default image has

Every sandbox (no templateId) boots from the same base rootfs:

OSUbuntu 22.04
RuntimeBun (system-wide, /usr/local/bin/bun)
Toolsgit, curl, wget, unzip, python3, openssh-server
Useruser (uid 1000, passwordless sudo), home + cwd /home/user
Package cachepre-warmed Bun cache for the @omg-dev/* SDK and the shadcn/Radix/Tailwind stack — bun install of those is near-instant

There is no Node.js binary — Bun stands in (agent presets shim node → bun for #!/usr/bin/env node CLIs). exec also injects ANTHROPIC_BASE_URL, OPENAI_BASE_URL, and OMG_AI_URL pointing at the per-sandbox LLM proxy, so AI SDKs inside the sandbox work without keys.

Bake your own template

@omg-dev/sandbox ships a typed template baker: declare install steps, checks, and a start command, and bakeTemplate runs the whole create → apply → verify → snapshot → publish sequence.

import {
  SandboxClient,
  defineTemplate,
  bakeTemplate,
  apt,
  run,
  check,
} from "@omg-dev/sandbox"

const client = new SandboxClient({
  baseUrl: "https://infra.omg.dev",
  token: process.env.OMG_API_KEY!,
})

const template = defineTemplate({
  id: "my-agent",
  version: "1",
  title: "My agent",
  ports: [3000],
  install: [
    apt.packages(["tmux"]),
    run({ user: "user", command: "bun install -g my-agent@1.2.3" }),
  ],
  checks: [check.command("tmux"), check.command("my-agent", { user: "user" })],
  start: {
    user: "user",
    command: "my-agent serve --host 0.0.0.0 --port 3000",
    readiness: { port: 3000 },
  },
})

await bakeTemplate(client, template)

It publishes both my-agent-v1 and my-agent (latest); create from either with { "templateId": "my-agent" }. Direct service clients mark the system version immutable. User template names are private to their account, so two users can publish the same name without replacing each other. If an account has no private template with that name, create falls back to the global system catalogue.

The registry records the start command, ports, and readiness probe. Creation copies that runtime contract onto the sandbox row, so hibernate/resume restores the service without the caller resending options and a later publish cannot change an existing sandbox. Re-bake by bumping version.

install steps run as root unless you pass user; apt.packages and download.archive are the other typed steps. checks fail the bake early (check.command / check.file), before anything is published.

Raw HTTP: snapshot + fork

No SDK? Snapshot + fork gives you the same instant-boot behavior: set up a sandbox once, snapshot it, then fork the snapshot for every fresh instance.

const BASE = "https://infra.omg.dev"
const auth = { Authorization: `Bearer ${process.env.OMG_API_KEY}` }
const json = { ...auth, "Content-Type": "application/json" }

// 1. create a base sandbox (and poll it to "running" — see Quickstart)
const sb = await fetch(`${BASE}/v1/sandboxes`, {
  method: "POST",
  headers: json,
  body: JSON.stringify({ size: "medium", ports: [3000] }),
}).then((r) => r.json())

// 2. write your files — body is a JSON ARRAY, content is base64
await fetch(`${BASE}/v1/sandboxes/${sb.id}/files`, {
  method: "POST",
  headers: json,
  body: JSON.stringify([
    {
      path: "/home/user/project/package.json",
      content: btoa(JSON.stringify({ name: "my-template", type: "module" })),
      mode: 0o644,
    },
  ]),
})

// 3. run setup (exec runs as root; returns stdout/stderr/exitCode)
await fetch(`${BASE}/v1/sandboxes/${sb.id}/exec`, {
  method: "POST",
  headers: json,
  body: JSON.stringify({
    cmd: "bash",
    args: ["-lc", "cd /home/user/project && bun install"],
    timeoutMs: 300_000,
  }),
}).then((r) => r.json())

// 4. snapshot — this is your "template"
const snap = await fetch(`${BASE}/v1/sandboxes/${sb.id}/snapshot`, {
  method: "POST",
  headers: auth,
}).then((r) => r.json())

// 5. fork the snapshot whenever you need a fresh instance (~sub-second boot)
const fresh = await fetch(`${BASE}/v1/sandboxes/fork`, {
  method: "POST",
  headers: json,
  body: JSON.stringify({
    snapshotId: snap.id,
    ports: [3000],
    skipAppProcesses: true,
  }),
}).then((r) => r.json())

// Or publish/update a name in your private template registry.
await fetch(`${BASE}/v1/templates/my-template/latest`, {
  method: "POST",
  headers: json,
  body: JSON.stringify({
    snapshotId: snap.id,
    startCommand: "cd /home/user/project && exec bun run start",
    readinessPort: 3000,
    ports: [3000],
  }),
})

Notes:

  • The snapshot captures the full VM state — filesystem and memory — so a fork resumes with your processes' output already on disk.
  • Forks are owner-checked and automatically routed to the node that holds the snapshot; you don't need an X-Vibes-Node pin for POST /v1/sandboxes/fork.
  • Pass skipAppProcesses: true on the fork unless your snapshot is a dashboard dev-editor sandbox — it keeps the fork a raw sandbox that only runs what you set up.
  • Keep the source sandbox or delete it after snapshotting — the snapshot stays forkable either way. Re-baking = run the steps again and switch your code to the new snapshotId.
  • A user may publish only snapshots produced by their own sandbox. GET, POST, and DELETE /v1/templates/{id}/latest are scoped to that user's namespace. Global system templates and immutable version refs can be changed only by a service principal.
  • The start command, readiness port, and ports are copied onto each sandbox when it is created. Updating or deleting the registry row cannot change what an existing sandbox executes after wake.

Underneath, the typed baker makes exactly these calls — the raw flow and bakeTemplate produce the same registry entries that templateId resolves against. First-party templates such as react-ts are published into the service-owned global catalogue.