Control
Counts the daemon's non-terminal processes that hold the sandbox idle clock (PLAN-DAEMON-PROCESS-ACTIVITY): every non-PTY process a caller started — a standalone kind=exec process, a session member, or a kind=code cell, including a cell still queued in starting. PTYs, session shells, language kernels and system processes (the entrypoint, dockerd) never count, and neither do records reconciled lost at boot. The runner polls this route and keeps the sandbox from auto-stopping while busy is true. Read-only and immediate; it never waits.
application/json- response
GET /rlp/activity body: whether caller-started processes are running
(PLAN-DAEMON-PROCESS-ACTIVITY). The runner polls it and keeps the sandbox's
idle clock from expiring while busy is true.
busy*booleantrue when processes > 0.
processes*integerThe number of non-terminal processes that hold the idle clock:
non-PTY processes a caller started (standalone kind=exec, session
members, kind=code cells, including cells still queued in
starting). PTYs, session and language kernels, and system processes
(the entrypoint, dockerd) never count.
int320 <= valuecurl -X GET 'http://sandbox:2281/rlp/activity'{ "busy": true, "processes": 0}Releases the runner-held entrypoint gate. Once released, the Linux daemon runs its one-per-boot startup coordinator: Docker Engine startup (reported on GET /docker/status), then the image entrypoint. fnReady=1 marks the release of a function-ready template build, which skips Docker startup (status deferred) but still starts the entrypoint. Idempotent — repeat calls answer 200 without spawning a second entrypoint. A platform without an image entrypoint (Windows) answers 200 as a no-op.
fnReady?string1 (or true) marks the release of a function-ready template build
(the runner's template builder releases the gate before capture). The
daemon then skips Docker startup and reports Docker as deferred;
the image entrypoint still starts. Any other value, or omitted, is an
ordinary release.
Plain-text ok\n.
text/plaincurl -X POST 'http://sandbox:2281/rlp/entrypoint-start'Issues FIFREEZE on the guest's scratch ext4 filesystem — the mount whose mountinfo root is /runtime/containerd (or /runtime/docker), bind-mounted by guest init — which checkpoints the journal and blocks new writers, so the runner's host-side trim can trust the captured copy's block bitmaps. The runner freezes right before pausing the VM and thaws after resume. An auto-thaw fires timeoutMs (default 10000, clamped to 1000..=30000) after the request arrives, so a lost thaw cannot wedge the guest. Idempotent: a repeat call while frozen re-arms the auto-thaw and answers 200. While frozen, other requests that write to that filesystem block until the thaw.
application/json- body
POST /rlp/fs-freeze — freeze the guest's scratch filesystem around a
disk snapshot (PLAN-DOCKER-AUTOMATIC-SUPPORT). FIFREEZE checkpoints the
ext4 journal, so the host reads exact block bitmaps from the captured copy.
timeoutMs?|Auto-thaw deadline in milliseconds, counted from when the daemon receives the request. Omitted means 10000; values are clamped to 1000..=30000. A repeat freeze while frozen re-arms the deadline.
int320 <= valueapplication/json- response
POST /rlp/fs-freeze success body.
autoThawMs*integerThe effective auto-thaw window in milliseconds.
int320 <= valuedevice*stringThe frozen filesystem's device as major:minor (mountinfo field 3).
frozen*booleanAlways true on success.
mountpoint*stringThe mount point the freeze was issued through (the scratch
filesystem's /runtime/containerd or /runtime/docker bind mount).
curl -X POST 'http://sandbox:2281/rlp/fs-freeze' \ -H 'Content-Type: application/json' \ -d '{}'{ "autoThawMs": 0, "device": "string", "frozen": true, "mountpoint": "string"}Issues FITHAW on the filesystem POST /rlp/fs-freeze froze and cancels its auto-thaw. Idempotent: nothing frozen, or a filesystem already thawed, answers 200. Windows sandboxes answer 200 as a no-op.
application/json- response
POST /rlp/fs-thaw success body.
frozen*booleanAlways false on success.
curl -X POST 'http://sandbox:2281/rlp/fs-thaw'{ "frozen": true}Returns the daemon's redacted bucket mount state. Credentials never appear in this response.
application/json- response
[index: integer]?Redacted mount status returned by GET /rlp/mounts.
curl -X GET 'http://sandbox:2281/rlp/mounts'[ { "bucket": "string", "error": "string", "helper": "string", "mount_path": "string", "name": "string", "status": "string" }]Applies the desired bucket mount set. Gateway credentials are write-only and never echoed back.
application/json- body
POST /rlp/mounts — the full desired mount set for this sandbox.
mounts?array<>The mounts the daemon should ensure are present.
Plain-text ok\n.
text/plaincurl -X POST 'http://sandbox:2281/rlp/mounts' \ -H 'Content-Type: application/json' \ -d '{}'Unmounts one bucket mount by absolute path. The legacy name alias is still accepted for runner compatibility.
path?stringAbsolute mount path to unmount.
name?stringLegacy alias for the mount name.
Plain-text ok\n.
text/plaincurl -X DELETE 'http://sandbox:2281/rlp/mounts'Pushes placeholder environment variables plus an optional PEM CA bundle fragment before the runner releases the entrypoint gate. Reserved keys are refused with 400 naming the key, and nothing from that request is stored: keys starting with RLP_, any DAYTONA_* key other than DAYTONA_SANDBOX_TOKEN, DAYTONA_API_URL, DAYTONA_OTEL_ENDPOINT, DAYTONA_DOCKER_START, DAYTONA_FN_PORT and DAYTONA_FN_HANDLER (the last two are platform-injected by the API's functions service for the tenant's handler process), and keys that are not valid variable names (empty, or containing =, NUL, CR or LF). The sandbox's create env travels on this route too, so PATH, LD_* and the CA-bundle variables (SSL_CERT_FILE, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE, NODE_EXTRA_CA_CERTS, GIT_SSL_CAINFO, DENO_CERT) are admitted; once an egress CA is installed, the daemon's own bundle paths replace any pushed value under the CA names. No size limit applies here: the API bounds the create env, and the tenant POST /env caps do not apply.
application/json- body
POST /rlp/secrets — placeholder env injection plus an optional egress CA.
ca_pem?stringPEM-encoded CA bundle fragment to trust for egress interception. Omitted or empty when no additional CA should be installed.
env?Environment variables to inject into future child processes.
These values are placeholders (rlps_…), the sandbox's create env, or
runner-delivered internal credentials, and are write-only on this
surface. Keys starting with RLP_, any DAYTONA_* key other than
DAYTONA_SANDBOX_TOKEN, DAYTONA_API_URL, DAYTONA_OTEL_ENDPOINT,
DAYTONA_DOCKER_START, DAYTONA_FN_PORT and DAYTONA_FN_HANDLER
(the last two are platform-injected by the API's functions service
for the tenant's handler), and keys that are not valid variable
names (empty, or containing =, NUL, CR or LF) are refused with
400 BAD_REQUEST naming the key; nothing from such a request is
stored. The CA-bundle variables (SSL_CERT_FILE,
REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE, NODE_EXTRA_CA_CERTS,
GIT_SSL_CAINFO, DENO_CERT) may be pushed, but once an egress CA
is installed the daemon's own bundle paths replace any pushed value.
Plain-text ok\n.
text/plaincurl -X POST 'http://sandbox:2281/rlp/secrets' \ -H 'Content-Type: application/json' \ -d '{}'Arms one process-bound lifecycle action (delete, stop, or pause) for this sandbox.
application/json- body
POST /rlp/self-delete — arm one process-bound lifecycle action.
action?stringLifecycle action to fire on exit: delete, stop, or pause.
Empty means delete.
delay_secs?integerGrace delay in seconds before firing the lifecycle action after the PID exits.
int640 <= valuepid?integerPID to watch. The request is rejected unless the process is currently alive.
int32Plain-text ok\n.
text/plaincurl -X POST 'http://sandbox:2281/rlp/self-delete' \ -H 'Content-Type: application/json' \ -d '{}'The runner's first control call on every boot path (PLAN-DAEMON-LISTENER-HARDENING). Accepted without a bearer only while the daemon holds no token (bootstrap); once a token is set, the request must carry it as Authorization: Bearer … and the body's token replaces it (rotation — a clone resumed from a template rotates the template's token to a per-clone one). Re-pushing the token the daemon already holds is a 200 no-op, so a retry after a lost 200 is idempotent. The token lives in daemon memory only and is never echoed, logged or exported. Like every control route this one sits behind the gateway-peer guard (403); unlike the others it bypasses the bearer layer and checks the bearer itself (401). A daemon that predates control tokens answers the plain-text 404 page not found fallback.
application/json- body
POST /rlp/token — set (bootstrap) or rotate the control bearer
(PLAN-DAEMON-LISTENER-HARDENING, Layer 2).
The runner's first :2281 call on every boot path. Accepted without a
bearer only while the daemon holds no token; once set, the request must
carry the current token as Authorization: Bearer … and replaces it.
Re-pushing the token the daemon already holds is a 200 no-op, which is
what makes the runner's retry after a lost 200 idempotent.
token*string32 to 128 bytes of printable ASCII (no whitespace); the runner sends 32 random bytes base64url-encoded (43 characters). Write-only: the daemon never echoes, logs or exports it.
32 <= length <= 128Plain-text ok\n; the token is now the bearer every other control route requires.
text/plaincurl -X POST 'http://sandbox:2281/rlp/token' \ -H 'Content-Type: application/json' \ -d '{ "token": "stringstringstringstringstringst"}'Sets the OS user every later exec, session, SSH login and git operation runs as, creating the account inside the guest if the image lacks it (issue #167). Pushed by the runner before the entrypoint gate releases. One-shot per boot: re-pushing the same name is an idempotent no-op; a different name is refused.
application/json- body
POST /rlp/user — the sandbox default OS user (issue #167,
PLAN-SANDBOX-OS-USER.md). Pushed by the runner before the entrypoint gate
releases; the daemon creates the account if the image lacks it and runs
every later exec/session/ssh/git as it. Idempotent.
user*stringPOSIX user name ([a-z_][a-z0-9_.-]{0,31}). Required — an omitted
name is a DTO shape error (422), not a validation 400. Validated by
the daemon; a name that could smuggle a passwd field separator is
refused.
Plain-text ok\n.
text/plaincurl -X POST 'http://sandbox:2281/rlp/user' \ -H 'Content-Type: application/json' \ -d '{ "user": "string"}'application/json- body
Flat operation binding for GET status query and POST apply JSON.
generation*integerint640 <= valueoperation_id*stringprotocol*integerint320 <= valuerequest_id*stringreservation_id*stringsession_id*stringvolume_id*stringapplication/json- response
operation*Flat operation binding for GET status query and POST apply JSON.
publication_blocked*booleanAlways true: this response never releases/implements the host fence.
state*VolumeApplyStateLocal progress only, never full-tree verification or host durability evidence. In-flight requests can instead return a generic 409 (busy).
"staging""applying""recovery_required""awaiting_host_confirmation""host_confirmed"curl -X POST 'http://sandbox:2281/rlp/volume-apply/apply' \ -H 'Content-Type: application/json' \ -d '{ "generation": 0, "operation_id": "string", "protocol": 0, "request_id": "string", "reservation_id": "string", "session_id": "string", "volume_id": "string"}'{ "operation": { "generation": 0, "operation_id": "string", "protocol": 0, "request_id": "string", "reservation_id": "string", "session_id": "string", "volume_id": "string" }, "publication_blocked": true, "state": "staging"}application/json- body
binding*Internal correlation only, never credentials or ownership evidence. A trusted runner identity/transport and a host-owned publication fence are prerequisites. The runner must derive this binding from its own attachment records, never adopt a binding reported by a guest. Only control port 2281 serves this DTO.
grant*instructions*array<>At most 256; exact duplicate begin returns state, never reruns effects. The trusted runner must persist the grant, block all host publication (including automatic/final commits) and prevent concurrent guest writes, renames and mount changes before beginning. Guest stat guards do not enforce these prerequisites.
receipt_ids*application/json- response
operation*Flat operation binding for GET status query and POST apply JSON.
publication_blocked*booleanAlways true: this response never releases/implements the host fence.
state*VolumeApplyStateLocal progress only, never full-tree verification or host durability evidence. In-flight requests can instead return a generic 409 (busy).
"staging""applying""recovery_required""awaiting_host_confirmation""host_confirmed"curl -X POST 'http://sandbox:2281/rlp/volume-apply/begin' \ -H 'Content-Type: application/json' \ -d '{ "binding": { "generation": 1, "protocol": 0, "reservation_id": "string", "session_id": "string", "volume_id": "string" }, "grant": { "operation_id": "string", "request_id": "string", "target_manifest": "string" }, "instructions": [ { "change": { "digest_hex": "string", "length": 0, "metadata": { "mode": 0, "mtime_nanos": 0, "mtime_seconds": 0 }, "type": "replace" }, "path": "string" } ], "receipt_ids": { "baseline_id": "string", "receipt_id": "string" }}'{ "operation": { "generation": 0, "operation_id": "string", "protocol": 0, "request_id": "string", "reservation_id": "string", "session_id": "string", "volume_id": "string" }, "publication_blocked": true, "state": "staging"}application/json- body
binding*Internal correlation only, never credentials or ownership evidence. A trusted runner identity/transport and a host-owned publication fence are prerequisites. The runner must derive this binding from its own attachment records, never adopt a binding reported by a guest. Only control port 2281 serves this DTO.
paths*array<string>At most 256 relative paths. Missing paths and descendants of a missing or non-directory ancestor return null without following symlink ancestry. "." captures the pinned root after reserve; this is a stat guard, not a base/clean-tree proof. Other dot components remain forbidden.
application/json- response
binding*Internal correlation only, never credentials or ownership evidence. A trusted runner identity/transport and a host-owned publication fence are prerequisites. The runner must derive this binding from its own attachment records, never adopt a binding reported by a guest. Only control port 2281 serves this DTO.
paths*array<>curl -X POST 'http://sandbox:2281/rlp/volume-apply/capture' \ -H 'Content-Type: application/json' \ -d '{ "binding": { "generation": 1, "protocol": 0, "reservation_id": "string", "session_id": "string", "volume_id": "string" }, "paths": [ "string" ]}'{ "binding": { "generation": 1, "protocol": 0, "reservation_id": "string", "session_id": "string", "volume_id": "string" }, "paths": [ { "path": "string", "snapshot": null } ]}application/json- body
Trusted runner assertion AFTER full-tree verification and durable host base/baseline + receipt persistence. Guest recording is not proof of either.
baseline_digest_hex*stringPost-verification record digests, each exactly 64 lowercase hex.
binding*Internal correlation only, never credentials or ownership evidence. A trusted runner identity/transport and a host-owned publication fence are prerequisites. The runner must derive this binding from its own attachment records, never adopt a binding reported by a guest. Only control port 2281 serves this DTO.
grant*receipt_digest_hex*stringreceipt_ids*application/json- response
confirmation*Immutable acknowledgement of local recording; exact retries remain stable even after a new operation starts. Not verification evidence.
curl -X POST 'http://sandbox:2281/rlp/volume-apply/confirm' \ -H 'Content-Type: application/json' \ -d '{ "baseline_digest_hex": "string", "binding": { "generation": 1, "protocol": 0, "reservation_id": "string", "session_id": "string", "volume_id": "string" }, "grant": { "operation_id": "string", "request_id": "string", "target_manifest": "string" }, "receipt_digest_hex": "string", "receipt_ids": { "baseline_id": "string", "receipt_id": "string" }}'{ "confirmation": { "baseline_digest_hex": "string", "binding": { "generation": 1, "protocol": 0, "reservation_id": "string", "session_id": "string", "volume_id": "string" }, "grant": { "operation_id": "string", "request_id": "string", "source_manifest": "string", "target_manifest": "string" }, "receipt_digest_hex": "string", "receipt_ids": { "baseline_id": "string", "receipt_id": "string" } }}application/json- body
binding*Internal correlation only, never credentials or ownership evidence. A trusted runner identity/transport and a host-owned publication fence are prerequisites. The runner must derive this binding from its own attachment records, never adopt a binding reported by a guest. Only control port 2281 serves this DTO.
mount_path*stringTrusted runner input only: canonical absolute non-root directory, no symlink components. Mount crossings allowed TO this root, never BELOW it. Pinned once; duplicate reserve must match exactly. No adoption or reopen.
application/json- response
Internal correlation only, never credentials or ownership evidence. A trusted runner identity/transport and a host-owned publication fence are prerequisites. The runner must derive this binding from its own attachment records, never adopt a binding reported by a guest. Only control port 2281 serves this DTO.
generation*integerPositive, matched against the current broker registration atomically.
int641 <= valueprotocol*integerint320 <= valuereservation_id*stringsession_id*stringEvery ID is 1..128 ASCII alphanumeric, underscore or hyphen.
volume_id*stringcurl -X POST 'http://sandbox:2281/rlp/volume-apply/reserve' \ -H 'Content-Type: application/json' \ -d '{ "binding": { "generation": 1, "protocol": 0, "reservation_id": "string", "session_id": "string", "volume_id": "string" }, "mount_path": "string"}'{ "generation": 1, "protocol": 0, "reservation_id": "string", "session_id": "string", "volume_id": "string"}protocol*integerint320 <= valuesession_id*stringvolume_id*stringgeneration*integerint640 <= valuereservation_id*stringoperation_id*stringrequest_id*stringindex*integer0 <= valuebody*stringapplication/octet-streamapplication/json- response
operation*Flat operation binding for GET status query and POST apply JSON.
publication_blocked*booleanAlways true: this response never releases/implements the host fence.
state*VolumeApplyStateLocal progress only, never full-tree verification or host durability evidence. In-flight requests can instead return a generic 409 (busy).
"staging""applying""recovery_required""awaiting_host_confirmation""host_confirmed"curl -X POST 'http://sandbox:2281/rlp/volume-apply/stage?protocol=0&session_id=string&volume_id=string&generation=0&reservation_id=string&operation_id=string&request_id=string&index=0' \ -H 'Content-Type: application/octet-stream' \ -d 'string'{ "operation": { "generation": 0, "operation_id": "string", "protocol": 0, "request_id": "string", "reservation_id": "string", "session_id": "string", "volume_id": "string" }, "publication_blocked": true, "state": "staging"}protocol*integerint320 <= valuesession_id*stringvolume_id*stringgeneration*integerint640 <= valuereservation_id*stringoperation_id*stringrequest_id*stringapplication/json- response
operation*Flat operation binding for GET status query and POST apply JSON.
publication_blocked*booleanAlways true: this response never releases/implements the host fence.
state*VolumeApplyStateLocal progress only, never full-tree verification or host durability evidence. In-flight requests can instead return a generic 409 (busy).
"staging""applying""recovery_required""awaiting_host_confirmation""host_confirmed"curl -X GET 'http://sandbox:2281/rlp/volume-apply/status?protocol=0&session_id=string&volume_id=string&generation=0&reservation_id=string&operation_id=string&request_id=string'{ "operation": { "generation": 0, "operation_id": "string", "protocol": 0, "request_id": "string", "reservation_id": "string", "session_id": "string", "volume_id": "string" }, "publication_blocked": true, "state": "staging"}Protocol 1 runner-only correlation state, not a credential or proof of ownership. The first session is sealed for the daemon's lifetime: a different session is refused with 409; repeating the session requires identical bindings.
application/json- body
attachments*array<>At most 64 distinct volume bindings. A repeat registration must carry the sealed session's exact bindings.
protocol*integerint320 <= valuesession_id*stringOne session per daemon lifetime: the first is sealed and a different id is refused.
application/json- response
protocol*integerint320 <= valuesession_id*stringcurl -X POST 'http://sandbox:2281/rlp/volume-sync/register' \ -H 'Content-Type: application/json' \ -d '{ "attachments": [ { "generation": 0, "volume_id": "string" } ], "protocol": 0, "session_id": "string"}'{ "protocol": 0, "session_id": "string"}Returns the oldest outstanding delivery without removing it. Waits at most 30000 ms. A missing request is null, not proof of execution failure.
session_id*stringwait_ms?integerLong-poll duration in milliseconds, from 0 to 30000.
int640 <= valueapplication/json- response
protocol*integerint320 <= valuerequest?null|session_id*stringcurl -X GET 'http://sandbox:2281/rlp/volume-sync/requests?session_id=string'{ "protocol": 0, "request": null, "session_id": "string"}Requires the submitted session, request, volume, generation, operation and delivery id. Identical completions replay; contradictory results refuse. Pending, unavailable and rate_limited finish this delivery but allow internal resubmission with the same effect identity and a new delivery id. Rate admission is enforced by the runner across reconnects.
application/json- body
delivery_id*integerEcho the exact polled delivery counter, including on transport retries.
int640 <= valuegeneration*integerint640 <= valuemanifest_id?|Pulled requires exactly 64 lowercase hex digits. Unchanged permits a valid manifest or null and describes remote observation, not local writes. Every other status carries null.
message*stringBounded to 1024 UTF-8 bytes. Must not contain provider credentials.
operation*VolumeSyncOperation"probe""pull"protocol*integerint320 <= valuerequest_id*stringretry_after_ms?|Only rate_limited carries a delay, from 1 to 1000 ms. Otherwise null. The runner enforces the per-attachment rate limit across reconnects.
int640 <= valuesession_id*stringstatus*VolumeSyncResultStatus"probed""pulled""unchanged""failed""pending""unavailable""rejected""rate_limited"volume_id*stringapplication/json- response
protocol*integerint320 <= valuesession_id*stringcurl -X POST 'http://sandbox:2281/rlp/volume-sync/results' \ -H 'Content-Type: application/json' \ -d '{ "delivery_id": 0, "generation": 0, "message": "string", "operation": "probe", "protocol": 0, "request_id": "string", "session_id": "string", "status": "probed", "volume_id": "string"}'{ "protocol": 0, "session_id": "string"}