Integrate as a platform
Run omg sandboxes for your own users from your backend, with your own sign-in.
This guide is for a product that gives its own users a cloud sandbox. For example: an app that moves a user's coding agent into the cloud and lets them control it from a phone.
Your users sign in to your product. They do not need omg accounts. Your backend holds one omg API key and creates, wakes, and puts to sleep one sandbox for each of your users.
Your app ──your sign-in──▶ Your backend ──omg_sk_ key──▶ infra.omg.dev
(user → sandbox id) │
▼
sandbox (your agent and your code)
│ outbound
Your app ◀────────── your relay or API ◀─────────────────────┘| You own | omg owns |
|---|---|
| Your users, their sign-in, and their sessions | The VM, its files, and its isolation |
| Which user has which sandbox | Sleep, wake, snapshots, and forks |
| When a sandbox should stay awake or sleep | The 2-hour safety rule and cleanup |
| How your app talks to code in the sandbox | Outbound internet from the sandbox |
1. One account, one key, on your server
- Make an omg account for your product. Sandbox creation needs a paid Computer plan. The plan sets the machine size for every sandbox.
- Create an API key. See Authentication.
- Keep the key on your backend only. Never put it in your app, a browser, or a sandbox.
The key can reach every sandbox on the account. All of your users' sandboxes are on this one account. Your backend must check which of your users is calling before it acts on a sandbox.
import { SandboxClient } from "@omg-dev/sandbox"
const omg = new SandboxClient({
baseUrl: "https://infra.omg.dev",
token: process.env.OMG_API_KEY!,
})2. Map your users to sandboxes
Store the sandbox id with your user record. omg does not store your user ids.
// first time a user needs a sandbox: fork your base machine (step 3)
const sandbox = await omg.fork(baseSnapshotId, {
idempotencyKey: `user-${user.id}-v1`,
})
await db.users.update(user.id, { sandboxId: sandbox.id })
// later
const sandbox = omg.sandbox(user.sandboxId)The idempotencyKey makes a retry safe. If the response is lost and you
retry with the same key, you get the same sandbox back.
3. Start every user from the same base
Choose one:
- The omg agent preset.
{ "template": "omg" }comes with Claude Code, Codex, opencode, and the omg web UI. See Templates. - Your own base machine. Create one sandbox, install your agent and plugin, take a snapshot, then fork that snapshot for each new user. A fork boots in seconds with everything installed.
# once: snapshot your base sandbox
curl -X POST https://infra.omg.dev/v1/sandboxes/$BASE_ID/snapshot \
-H "Authorization: Bearer $OMG_API_KEY"
# for each new user: fork it
curl -X POST https://infra.omg.dev/v1/sandboxes/fork \
-H "Authorization: Bearer $OMG_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "snapshotId": "'$SNAPSHOT_ID'", "skipAppProcesses": true, "idempotencyKey": "user-123-v1" }'A snapshot that no sandbox depends on is deleted after 7 days. Rebuild your base snapshot at least once a week, and point new forks at the new id.
4. Sleep and wake
A sandbox sleeps 2 hours after it starts unless something renews its keep-awake lease. Read Lifecycle and keep-awake for the full rules. A typical session:
const sandbox = omg.sandbox(user.sandboxId)
// user opens your app
const info = await omg.get(sandbox.id)
if (info.status === "hibernated") await sandbox.wake()
// while the agent works: renew every few minutes
await sandbox.keepAlive(900)
// work is done and the user has left
await sandbox.hibernate()If the code that knows "I am still busy" runs inside the sandbox, let it renew the lease itself with no key:
curl -X POST http://169.254.0.1:9090/v1/sandbox/keepalive -d '{"seconds":900}'After a wake, open connections are gone. Your code in the sandbox must reconnect to your relay or API.
5. Talk to the code in the sandbox
- Outbound connections work. Code in the sandbox can reach the public internet. The usual pattern is for your agent or plugin to connect out to your relay.
- Commands and files. Your backend can call
exec, write files, and read files. See the Reference. - Shell. The shell WebSocket needs your API key. Connect from your backend and forward the stream to your user. Do not give the key to your app.
- Preview URLs open only for you. A preview URL opens only for the omg account owner, so your users cannot open it. Use an outbound connection instead.
6. Your users' secrets
Pass a user's own credentials (for example their model provider login) into
their sandbox only, with env on exec or by writing a file. Values in
env on create are stored as plain text with the sandbox. Do not reuse one
user's secret in another user's sandbox.
Limits today
| Topic | Today |
|---|---|
| API keys | Up to 10 active keys per account. Each key reaches every sandbox on the account. |
| Per-user limits | None. Enforce limits for your own users in your backend. |
| Labels | Sandboxes have no field for your user id. Keep the mapping in your database. |
| Events | No self-serve webhooks. Poll GET /v1/sandboxes. |
| Billing | Usage is recorded per omg account. It is not split by your users. |
| Sleep | 2 hours after start without a lease. A sleeping sandbox is deleted after 14 days. |
| Snapshots | Deleted after 7 days when no sandbox depends on them. |
Checklist
- The API key exists only in your backend's secret store.
- Your backend checks the caller owns the sandbox id before each call.
- Creates and forks send an
idempotencyKey. - Busy sandboxes renew their lease. Idle ones hibernate.
- Your code in the sandbox reconnects after a wake.
- You rebuild your base snapshot every week.
- Users who leave have their sandbox deleted.