Skip to content

API reference

Sandboxes

Markdown

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.

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).

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.
curl -X POST "https://api.rytiv.app/v1/sandboxes" \
-H "Authorization: Bearer $RYTIV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"template":"app:my-app"}'

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
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.

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.

Name Description
state all adds the sandboxes that ended in the last 14 days.
curl -X GET "https://api.rytiv.app/v1/sandboxes" \
-H "Authorization: Bearer $RYTIV_TOKEN"

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
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.

One sandbox.

Auth: API key with full access or the scope read. Every class of agent token may call it.

Name Description
id The sandbox’s id (sbx-...).
curl -X GET "https://api.rytiv.app/v1/sandboxes/<id>" \
-H "Authorization: Bearer $RYTIV_TOKEN"

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
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.

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).

Name Description
id The sandbox’s id (sbx-...).
curl -X DELETE "https://api.rytiv.app/v1/sandboxes/<id>" \
-H "Authorization: Bearer $RYTIV_TOKEN"

202 with a JSON object carrying id, state.

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.

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).

Name Description
id The sandbox’s id.

JSON, the shape SetSandboxTimeoutRequest of the contract.

Field Type Required Description
ttlSeconds integer required Lifetime from now, in seconds. 10 to 86400.
curl -X POST "https://api.rytiv.app/v1/sandboxes/<id>/timeout" \
-H "Authorization: Bearer $RYTIV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"ttlSeconds":10}'

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
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.

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.

Name Description
id The sandbox’s id.

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.
curl -X POST "https://api.rytiv.app/v1/sandboxes/<id>/exec" \
-H "Authorization: Bearer $RYTIV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"cmd":"<cmd>"}'

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.
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.

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.

Name Description
id The sandbox’s id.
curl -X GET "https://api.rytiv.app/v1/sandboxes/<id>/files" \
-H "Authorization: Bearer $RYTIV_TOKEN"

200 with a JSON body.

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.

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).

Name Description
id The sandbox’s id.
Name Description
mode The file mode, default 0644.
mkdirs 1 creates missing directories.

The raw bytes of the file.

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.txt

200 with a JSON object carrying path, bytes, sha256.

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.

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.

Name Description
id The sandbox’s id.
curl -X GET "https://api.rytiv.app/v1/sandboxes/<id>/files/list" \
-H "Authorization: Bearer $RYTIV_TOKEN"

200 with a JSON object carrying path, entries.

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.