API reference
Sandboxes
Short-lived machines with a deadline: create from a template, run commands, read and write files, extend, end.
A sandbox is a machine with a deadline. It boots the image of a template you already have (app:<name> or build:<id>), runs commands, holds files, and is removed at its deadline together with everything on its disk. Each sandbox has an id like sbx-1a2b3c4d5e6f and its own address https://<id>.rytiv.app. See Sandboxes for the full description.
Endpoints
Section titled “Endpoints”POST /v1/sandboxes: Creates a sandbox from a template and answers when its machine runsGET /v1/sandboxes: Running sandboxes, newest first.?state=alladds the ones that ended in the last 14 daysGET /v1/sandboxes/:id: One sandboxDELETE /v1/sandboxes/:id: Ends the sandbox now. Its machine and disk are gonePOST /v1/sandboxes/:id/timeout: Sets a new deadline: now plusttlSecondsPOST /v1/sandboxes/:id/exec: Runs one command in the sandbox and answers with its exit code and outputGET /v1/sandboxes/:id/files: Reads one file (?path=), as raw bytesPUT /v1/sandboxes/:id/files: Writes one file (?path=, optionalmodeandmkdirs=1); the body is the file’s bytesGET /v1/sandboxes/:id/files/list: Lists a directory (?path=)
POST /v1/sandboxes
Section titled “POST /v1/sandboxes”Creates a sandbox from a template and answers when its machine runs.
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class sandbox or twin may call it; class production is refused (agent-token-production-readonly).
Request body
Section titled “Request body”JSON, the shape CreateSandboxRequest of the contract.
| Field | Type | Required | Description |
|---|---|---|---|
template |
string | required | app:<name> boots the image an app of yours runs now; build:<id> a build of yours. |
ttlSeconds |
integer | optional | Lifetime from now, in seconds. 10 to 86400, default 300. |
vcpus |
integer | optional | Virtual CPUs. 1 to 16. |
memoryMb |
integer | optional | Memory in MB. 128 to 16384. |
diskMb |
integer | optional | The machine’s disk in MB. Without it, the image’s size plus headroom. 512 to 131072. |
env |
object of string | optional | Plain environment variables. Use secrets for credentials. |
ports |
array of integer | optional | More ports to reach from outside, each at https://<port>-<id>.rytiv.app. 0 to 8 items. |
internalPort |
integer | optional | The port the app listens on. 1 to 65535. |
readiness |
boot | port |
optional | boot: ready when the machine boots. port: ready when internalPort answers. default "boot". |
access |
signed | public |
optional | signed: the sandbox’s URLs need the access token from the create answer. public: anyone with the URL. default "signed". |
idleSeconds |
integer | optional | Seconds without activity before the sandbox sleeps. 10 to 86400. |
metadata |
object of string | optional | Key/value pairs for your own bookkeeping. |
egress |
object | optional | Outbound network: allow-all, deny-all, or allowlist with domains and cidrs. |
egress.mode |
allow-all | deny-all | allowlist |
required | view to watch, control to take keyboard and mouse. |
egress.domains |
array of string | optional | 0 to 100 items. |
egress.cidrs |
array of string | optional | 0 to 10 items. |
Example request
Section titled “Example request”curl -X POST "https://api.rytiv.app/v1/sandboxes" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"template":"app:my-app"}'Response
Section titled “Response”201 with the sandbox:
{ "id": "<id>", "template": "app:my-app", "state": "starting", "createdAt": "2026-10-21T09:00:00Z", "expiresAt": "2026-10-21T09:00:00Z", "endedAt": "2026-10-21T09:00:00Z", "endReason": "expired", "machineId": "<machineId>", "vcpus": -9007199254740991, "memoryMb": -9007199254740991, "readiness": "boot", "access": "signed", "url": "<url>", "ports": [ { "port": -9007199254740991, "url": "<url>" } ], "metadata": { "<key>": "<value>" }, "egress": { "mode": "allow-all", "domains": [ "www.example.com" ], "cidrs": [ "<cidr>" ] }, "createMs": -9007199254740991}| Field | Type | Present | Description |
|---|---|---|---|
id |
string | always | |
template |
string | always | app:<name> boots the image an app of yours runs now; build:<id> a build of yours. |
state |
starting | running | sleeping | ended |
always | |
createdAt |
string (ISO 8601) | always | When it was created. |
expiresAt |
string (ISO 8601) | always | When the copy is deleted (a moment). Not together with ttlSeconds. |
endedAt |
string (ISO 8601) or null | always | |
endReason |
expired | killed | failed | app-deleted or null |
always | |
machineId |
string or null | always | |
vcpus |
integer | always | Virtual CPUs. -9007199254740991 to 9007199254740991. |
memoryMb |
integer | always | Memory in MB. -9007199254740991 to 9007199254740991. |
readiness |
boot | port |
always | boot: ready when the machine boots. port: ready when internalPort answers. |
access |
signed | public |
always | signed: the sandbox’s URLs need the access token from the create answer. public: anyone with the URL. |
url |
string | always | |
ports |
array of object | always | More ports to reach from outside, each at https://<port>-<id>.rytiv.app. |
metadata |
object of string | always | Key/value pairs for your own bookkeeping. |
egress |
object | always | Outbound network: allow-all, deny-all, or allowlist with domains and cidrs. |
egress.mode |
allow-all | deny-all | allowlist |
always | view to watch, control to take keyboard and mouse. |
egress.domains |
array of string | always | |
egress.cidrs |
array of string | always | |
createMs |
integer | always | -9007199254740991 to 9007199254740991. |
accessToken |
object | if set | With access: "signed": the token the sandbox’s URLs need, the header to send it in, and its expiry. |
accessToken.token |
string | always | |
accessToken.expiresAt |
string (ISO 8601) | always | When the copy is deleted (a moment). Not together with ttlSeconds. |
accessToken.header |
string | always |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request |
The request is malformed or a field is out of range. error names every field; issues lists them. |
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | build-not-found |
build:<id> is not a build of your organization. |
| 404 | template-not-found |
No such app or build in your organization. |
| 409 | conflict |
The request conflicts with the current state. |
| 409 | template-not-runnable |
The build is a static site, or the app runs no machine. |
| 429 | too-many-sandboxes |
The organization’s sandbox limit is reached. |
| 503 | no-capacity |
No host in the region has room for the machine right now; try again. |
GET /v1/sandboxes
Section titled “GET /v1/sandboxes”Running sandboxes, newest first. ?state=all adds the ones that ended in the last 14 days.
Auth: API key with full access or the scope read. Every class of agent token may call it.
Query parameters
Section titled “Query parameters”| Name | Description |
|---|---|
state |
all adds the sandboxes that ended in the last 14 days. |
Example request
Section titled “Example request”curl -X GET "https://api.rytiv.app/v1/sandboxes" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with a list of sandboxes, each:
[ { "id": "<id>", "template": "app:my-app", "state": "starting", "createdAt": "2026-10-21T09:00:00Z", "expiresAt": "2026-10-21T09:00:00Z", "endedAt": "2026-10-21T09:00:00Z", "endReason": "expired", "machineId": "<machineId>", "vcpus": -9007199254740991, "memoryMb": -9007199254740991, "readiness": "boot", "access": "signed", "url": "<url>", "ports": [ { "port": -9007199254740991, "url": "<url>" } ], "metadata": { "<key>": "<value>" }, "egress": { "mode": "allow-all", "domains": [ "www.example.com" ], "cidrs": [ "<cidr>" ] } }]| Field | Type | Present | Description |
|---|---|---|---|
id |
string | always | sbx- and 12 hex digits. |
template |
string | always | app:<name> boots the image an app of yours runs now; build:<id> a build of yours. |
state |
starting | running | sleeping | ended |
always | starting, running, sleeping or ended. |
createdAt |
string (ISO 8601) | always | When it was created. |
expiresAt |
string (ISO 8601) | always | The deadline. |
endedAt |
string (ISO 8601) or null | always | When it ended. |
endReason |
expired | killed | failed | app-deleted or null |
always | expired, killed, failed or app-deleted. |
machineId |
string or null | always | The machine behind it. |
vcpus |
integer | always | Virtual CPUs. -9007199254740991 to 9007199254740991. |
memoryMb |
integer | always | Memory in MB. -9007199254740991 to 9007199254740991. |
readiness |
boot | port |
always | boot: ready when the machine boots. port: ready when internalPort answers. |
access |
signed | public |
always | signed: the sandbox’s URLs need the access token from the create answer. public: anyone with the URL. |
url |
string | always | https://<id>.rytiv.app. |
ports |
array of object | always | More ports to reach from outside, each at https://<port>-<id>.rytiv.app. |
metadata |
object of string | always | Key/value pairs for your own bookkeeping. |
egress |
object | always | Outbound network: allow-all, deny-all, or allowlist with domains and cidrs. |
egress.mode |
allow-all | deny-all | allowlist |
always | view to watch, control to take keyboard and mouse. |
egress.domains |
array of string | always | |
egress.cidrs |
array of string | always |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request |
The request is malformed or a field is out of range. error names every field; issues lists them. |
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
GET /v1/sandboxes/:id
Section titled “GET /v1/sandboxes/:id”One sandbox.
Auth: API key with full access or the scope read. Every class of agent token may call it.
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
id |
The sandbox’s id (sbx-...). |
Example request
Section titled “Example request”curl -X GET "https://api.rytiv.app/v1/sandboxes/<id>" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with the sandbox:
{ "id": "<id>", "template": "app:my-app", "state": "starting", "createdAt": "2026-10-21T09:00:00Z", "expiresAt": "2026-10-21T09:00:00Z", "endedAt": "2026-10-21T09:00:00Z", "endReason": "expired", "machineId": "<machineId>", "vcpus": -9007199254740991, "memoryMb": -9007199254740991, "readiness": "boot", "access": "signed", "url": "<url>", "ports": [ { "port": -9007199254740991, "url": "<url>" } ], "metadata": { "<key>": "<value>" }, "egress": { "mode": "allow-all", "domains": [ "www.example.com" ], "cidrs": [ "<cidr>" ] }}| Field | Type | Present | Description |
|---|---|---|---|
id |
string | always | sbx- and 12 hex digits. |
template |
string | always | app:<name> boots the image an app of yours runs now; build:<id> a build of yours. |
state |
starting | running | sleeping | ended |
always | starting, running, sleeping or ended. |
createdAt |
string (ISO 8601) | always | When it was created. |
expiresAt |
string (ISO 8601) | always | The deadline. |
endedAt |
string (ISO 8601) or null | always | When it ended. |
endReason |
expired | killed | failed | app-deleted or null |
always | expired, killed, failed or app-deleted. |
machineId |
string or null | always | The machine behind it. |
vcpus |
integer | always | Virtual CPUs. -9007199254740991 to 9007199254740991. |
memoryMb |
integer | always | Memory in MB. -9007199254740991 to 9007199254740991. |
readiness |
boot | port |
always | boot: ready when the machine boots. port: ready when internalPort answers. |
access |
signed | public |
always | signed: the sandbox’s URLs need the access token from the create answer. public: anyone with the URL. |
url |
string | always | https://<id>.rytiv.app. |
ports |
array of object | always | More ports to reach from outside, each at https://<port>-<id>.rytiv.app. |
metadata |
object of string | always | Key/value pairs for your own bookkeeping. |
egress |
object | always | Outbound network: allow-all, deny-all, or allowlist with domains and cidrs. |
egress.mode |
allow-all | deny-all | allowlist |
always | view to watch, control to take keyboard and mouse. |
egress.domains |
array of string | always | |
egress.cidrs |
array of string | always |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
| 404 | sandbox-not-found |
No such sandbox in your organization. |
DELETE /v1/sandboxes/:id
Section titled “DELETE /v1/sandboxes/:id”Ends the sandbox now. Its machine and disk are gone.
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class sandbox or twin may call it; class production is refused (agent-token-production-readonly).
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
id |
The sandbox’s id (sbx-...). |
Example request
Section titled “Example request”curl -X DELETE "https://api.rytiv.app/v1/sandboxes/<id>" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”202 with a JSON object carrying id, state.
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
| 404 | sandbox-not-found |
No such sandbox in your organization. |
| 409 | sandbox-ended |
The sandbox has ended. |
POST /v1/sandboxes/:id/timeout
Section titled “POST /v1/sandboxes/:id/timeout”Sets a new deadline: now plus ttlSeconds.
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class sandbox or twin may call it; class production is refused (agent-token-production-readonly).
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
id |
The sandbox’s id. |
Request body
Section titled “Request body”JSON, the shape SetSandboxTimeoutRequest of the contract.
| Field | Type | Required | Description |
|---|---|---|---|
ttlSeconds |
integer | required | Lifetime from now, in seconds. 10 to 86400. |
Example request
Section titled “Example request”curl -X POST "https://api.rytiv.app/v1/sandboxes/<id>/timeout" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"ttlSeconds":10}'Response
Section titled “Response”200 with the sandbox with its new deadline:
{ "id": "<id>", "template": "app:my-app", "state": "starting", "createdAt": "2026-10-21T09:00:00Z", "expiresAt": "2026-10-21T09:00:00Z", "endedAt": "2026-10-21T09:00:00Z", "endReason": "expired", "machineId": "<machineId>", "vcpus": -9007199254740991, "memoryMb": -9007199254740991, "readiness": "boot", "access": "signed", "url": "<url>", "ports": [ { "port": -9007199254740991, "url": "<url>" } ], "metadata": { "<key>": "<value>" }, "egress": { "mode": "allow-all", "domains": [ "www.example.com" ], "cidrs": [ "<cidr>" ] }}| Field | Type | Present | Description |
|---|---|---|---|
id |
string | always | sbx- and 12 hex digits. |
template |
string | always | app:<name> boots the image an app of yours runs now; build:<id> a build of yours. |
state |
starting | running | sleeping | ended |
always | starting, running, sleeping or ended. |
createdAt |
string (ISO 8601) | always | When it was created. |
expiresAt |
string (ISO 8601) | always | The deadline. |
endedAt |
string (ISO 8601) or null | always | When it ended. |
endReason |
expired | killed | failed | app-deleted or null |
always | expired, killed, failed or app-deleted. |
machineId |
string or null | always | The machine behind it. |
vcpus |
integer | always | Virtual CPUs. -9007199254740991 to 9007199254740991. |
memoryMb |
integer | always | Memory in MB. -9007199254740991 to 9007199254740991. |
readiness |
boot | port |
always | boot: ready when the machine boots. port: ready when internalPort answers. |
access |
signed | public |
always | signed: the sandbox’s URLs need the access token from the create answer. public: anyone with the URL. |
url |
string | always | https://<id>.rytiv.app. |
ports |
array of object | always | More ports to reach from outside, each at https://<port>-<id>.rytiv.app. |
metadata |
object of string | always | Key/value pairs for your own bookkeeping. |
egress |
object | always | Outbound network: allow-all, deny-all, or allowlist with domains and cidrs. |
egress.mode |
allow-all | deny-all | allowlist |
always | view to watch, control to take keyboard and mouse. |
egress.domains |
array of string | always | |
egress.cidrs |
array of string | always |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request |
The request is malformed or a field is out of range. error names every field; issues lists them. |
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
| 404 | sandbox-not-found |
No such sandbox in your organization. |
| 409 | sandbox-ended |
The sandbox has ended. |
POST /v1/sandboxes/:id/exec
Section titled “POST /v1/sandboxes/:id/exec”Runs one command in the sandbox and answers with its exit code and output.
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class sandbox or twin may call it; class production is refused.
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
id |
The sandbox’s id. |
Request body
Section titled “Request body”JSON, the shape SandboxExecRequest of the contract.
| Field | Type | Required | Description |
|---|---|---|---|
cmd |
string or array of string | required | A string runs through /bin/sh -c; an array is the argument list, no shell. |
cwd |
string | optional | The working directory, an absolute path. at most 4096 characters. |
env |
object of string | optional | Plain environment variables. Use secrets for credentials. |
timeoutMs |
integer | optional | How long the command may run. 1000 to 600000, default 60000. |
stdin |
string | optional | Text handed to the command’s standard input. at most 1048576 characters. |
Example request
Section titled “Example request”curl -X POST "https://api.rytiv.app/v1/sandboxes/<id>/exec" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"cmd":"<cmd>"}'Response
Section titled “Response”200 with the result:
{ "exitCode": -9007199254740991, "signal": "<signal>", "timedOut": true, "durationMs": -9007199254740991, "stdout": "<stdout>", "stderr": "<stderr>", "stdoutTruncated": true, "stderrTruncated": true}| Field | Type | Present | Description |
|---|---|---|---|
exitCode |
integer or null | always | The command’s exit code. |
signal |
string or null | always | The signal that ended it, if one did. |
timedOut |
boolean | always | Whether timeoutMs ended it. |
durationMs |
integer | always | How long it ran. -9007199254740991 to 9007199254740991. |
stdout |
string | always | |
stderr |
string | always | |
stdoutTruncated |
boolean | always | Whether stdout was cut at 1 MiB. |
stderrTruncated |
boolean | always | Whether stderr was cut at 1 MiB. |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request |
The request is malformed or a field is out of range. error names every field; issues lists them. |
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
| 404 | sandbox-not-found |
No such sandbox in your organization. |
| 409 | sandbox-ended |
The sandbox has ended. |
| 409 | sandbox-starting |
The sandbox is not running yet. |
| 503 | audit-unavailable |
The audit record of the session could not be written, so it was not opened. |
GET /v1/sandboxes/:id/files
Section titled “GET /v1/sandboxes/:id/files”Reads one file (?path=), as raw bytes.
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class production needs prodDataRead for it; sandbox and twin may call it.
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
id |
The sandbox’s id. |
Example request
Section titled “Example request”curl -X GET "https://api.rytiv.app/v1/sandboxes/<id>/files" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with a JSON body.
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
| 404 | sandbox-not-found |
No such sandbox in your organization. |
| 409 | sandbox-ended |
The sandbox has ended. |
| 409 | sandbox-starting |
The sandbox is not running yet. |
| 502 | transport |
|
| 503 | audit-unavailable |
The audit record of the session could not be written, so it was not opened. |
PUT /v1/sandboxes/:id/files
Section titled “PUT /v1/sandboxes/:id/files”Writes one file (?path=, optional mode and mkdirs=1); the body is the file’s bytes.
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class sandbox or twin may call it; class production is refused (agent-token-production-readonly).
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
id |
The sandbox’s id. |
Query parameters
Section titled “Query parameters”| Name | Description |
|---|---|
mode |
The file mode, default 0644. |
mkdirs |
1 creates missing directories. |
Request body
Section titled “Request body”The raw bytes of the file.
Example request
Section titled “Example request”curl -X PUT "https://api.rytiv.app/v1/sandboxes/<id>/files" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/octet-stream" \ --data-binary @hello.txtResponse
Section titled “Response”200 with a JSON object carrying path, bytes, sha256.
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request |
The request is malformed or a field is out of range. error names every field; issues lists them. |
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
| 404 | sandbox-not-found |
No such sandbox in your organization. |
| 409 | sandbox-ended |
The sandbox has ended. |
| 409 | sandbox-starting |
The sandbox is not running yet. |
| 413 | too-large |
The body is larger than the route accepts. |
| 502 | transport |
|
| 503 | audit-unavailable |
The audit record of the session could not be written, so it was not opened. |
GET /v1/sandboxes/:id/files/list
Section titled “GET /v1/sandboxes/:id/files/list”Lists a directory (?path=).
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class production needs prodDataRead for it; sandbox and twin may call it.
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
id |
The sandbox’s id. |
Example request
Section titled “Example request”curl -X GET "https://api.rytiv.app/v1/sandboxes/<id>/files/list" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with a JSON object carrying path, entries.
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
| 404 | sandbox-not-found |
No such sandbox in your organization. |
| 409 | sandbox-ended |
The sandbox has ended. |
| 409 | sandbox-starting |
The sandbox is not running yet. |
| 502 | transport |
|
| 503 | audit-unavailable |
The audit record of the session could not be written, so it was not opened. |