Guides
Sandboxes
Short-lived machines for one task each. A sandbox starts from a template, runs commands and holds files, and is removed at its deadline.
A sandbox is a machine with a deadline. Create it from a template, run commands in it, read and write its files, and let it go: at its deadline Rytiv removes the machine and everything on its disk. Each sandbox has an id like sbx-1a2b3c4d5e6f and its own address.
Create a sandbox
Section titled “Create a sandbox”ry sandbox create app:web --ttl 30mcurl -X POST https://api.rytiv.app/v1/sandboxes \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"template": "app:web", "ttlSeconds": 1800}'const res = await fetch("https://api.rytiv.app/v1/sandboxes", { method: "POST", headers: { Authorization: `Bearer ${process.env.RYTIV_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ template: "app:web", ttlSeconds: 1800 }),});const sandbox = await res.json();console.log(sandbox.id, sandbox.state, sandbox.url);POST /v1/sandboxes answers when the machine runs, with 201 and the sandbox. The answer also carries createMs, the time from your request to the running machine, and, for a sandbox with signed access, an accessToken.
Templates
Section titled “Templates”A sandbox boots the image of a template you already have:
| Template | Boots |
|---|---|
app:<name> |
The image an app of your organization runs right now. The app must run a machine. |
build:<id> |
A build of your organization that produced a machine image (not a static site). |
Nothing else is inherited from the template: no environment variables and no secrets of the app. Pass what the sandbox needs in env.
Request
Section titled “Request”| Field | Type | Default | Notes |
|---|---|---|---|
template |
string | required | app:<name> or build:<id> |
ttlSeconds |
integer | 300 |
Lifetime from now, 10 to 86400 |
vcpus |
integer | 1 |
1 to 16 |
memoryMb |
integer | 512 |
128 to 16384 |
diskMb |
integer | 512 to 131072 | |
env |
object | {} |
Up to 128 variables, names like MY_VAR. A few RYTIV_ names are reserved. |
ports |
integer[] | [] |
Up to 8 more ports to reach from outside |
internalPort |
integer | the template app’s port, else 3000 |
The port url points at |
readiness |
boot or port |
boot |
Ready when the machine boots, or when internalPort answers |
access |
signed or public |
signed |
Who can open the sandbox’s URLs, see Access |
idleSeconds |
integer | 300 |
10 to 86400, see the note below |
metadata |
object | {} |
Up to 16 key/value pairs for your own bookkeeping |
egress |
object | allow all | Outbound network, see Network |
Unknown fields are refused with 400.
The sandbox object
Section titled “The sandbox object”| Field | Meaning |
|---|---|
id |
sbx- and 12 hex digits |
template |
The template it was created from |
state |
starting, running, sleeping or ended |
createdAt, expiresAt |
When it was created, and its deadline |
endedAt, endReason |
When and why it ended: expired, killed, failed or app-deleted |
machineId |
The machine behind the sandbox |
vcpus, memoryMb, readiness, access, metadata, egress |
As requested |
url |
https://<id>.rytiv.app |
ports |
One {port, url} per extra port, with url like https://<port>-<id>.rytiv.app |
Access
Section titled “Access”With access: "signed" (the default), the sandbox’s URLs only answer requests that carry a token. The create answer contains one:
"accessToken": { "token": "...", "expiresAt": "...", "header": "x-rytiv-access" }Send it in the header named by header. It is valid for the lifetime the sandbox was created with, at most 24 hours. With access: "public", anyone with the URL can open it.
Run a command
Section titled “Run a command”ry sandbox exec sbx-1a2b3c4d5e6f -- uname -acurl -X POST https://api.rytiv.app/v1/sandboxes/sbx-1a2b3c4d5e6f/exec \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"cmd": ["uname", "-a"]}'const res = await fetch(`https://api.rytiv.app/v1/sandboxes/${id}/exec`, { method: "POST", headers: { Authorization: `Bearer ${process.env.RYTIV_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ cmd: ["uname", "-a"] }),});const { exitCode, stdout, stderr } = await res.json();POST /v1/sandboxes/:id/exec waits for the command and answers with its result.
| Field | Type | Default | Notes |
|---|---|---|---|
cmd |
string or string[] | required | A string runs through /bin/sh -c, an array is the argument list (no shell) |
cwd |
string | An absolute path | |
env |
object | More environment for this command | |
timeoutMs |
integer | 60000 |
1000 to 600000, and never past the sandbox’s deadline |
stdin |
string | Up to 1 MiB |
The answer has exitCode, signal, timedOut, durationMs, stdout and stderr. Each output stream is cut at 1 MiB, and stdoutTruncated or stderrTruncated say so.
ry sandbox exec exits with the command’s own exit code, 124 on a timeout, 128 + n when signal n ended it, and 255 for an error of its own.
| Method | Path | Body and answer |
|---|---|---|
GET |
/v1/sandboxes/:id/files?path=/abs/path |
The file’s bytes |
PUT |
/v1/sandboxes/:id/files?path=/abs/path |
Send the bytes. Optional mode (default 0644) and mkdirs=1. Answers {path, bytes, sha256}. |
GET |
/v1/sandboxes/:id/files/list?path=/abs/dir |
{path, entries}, each entry {name, type, size, mode, mtimeMs} |
Paths are absolute and may not contain ... One file is at most 64 MiB.
curl -X PUT "https://api.rytiv.app/v1/sandboxes/sbx-1a2b3c4d5e6f/files?path=/tmp/hello.txt" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ --data-binary "hello"Network
Section titled “Network”egress decides what the sandbox may reach:
mode |
Meaning |
|---|---|
allow-all |
The default |
deny-all |
Nothing, not even DNS |
allowlist |
Only the listed domains (up to 100, a leading *. allowed) and IPv4 cidrs (up to 10), on ports 80 and 443 |
The policy is fixed for the sandbox’s lifetime.
{ "template": "app:web", "egress": { "mode": "allowlist", "domains": ["api.github.com", "*.npmjs.org"] } }Extend, list, end
Section titled “Extend, list, end”| Method | Path | What it does |
|---|---|---|
POST |
/v1/sandboxes/:id/timeout |
{ttlSeconds}: the new deadline is now plus ttlSeconds (10 to 86400). Calling it twice does not add up. |
GET |
/v1/sandboxes |
Running sandboxes, newest first. ?state=all adds the ones that ended in the last 14 days. |
GET |
/v1/sandboxes/:id |
One sandbox |
DELETE |
/v1/sandboxes/:id |
Ends it now. 204, or 202 if it is already ending. Its machine and disk are gone. |
ry sandbox timeout sbx-1a2b3c4d5e6f 1hry sandbox list --allry sandbox kill sbx-1a2b3c4d5e6fLimits and errors
Section titled “Limits and errors”An organization can run 100 sandboxes at a time by default; the next create answers 429 with too-many-sandboxes.
| Status | Code | When |
|---|---|---|
404 |
template-not-found |
No such app or build in your organization |
409 |
template-not-runnable |
The build is a static site, or the app runs no machine |
404 |
sandbox-not-found |
No such sandbox in your organization |
409 |
sandbox-ended, sandbox-starting |
The sandbox has ended, or is not running yet |
429 |
too-many-sandboxes |
The organization’s limit is reached |
503 |
no-capacity |
No room right now, try again |
See the CLI reference for every flag of ry sandbox.