Direct sandbox-agent API
Advanced direct integration guide for calling the sandbox agent over the sandbox domain, with the protobuf schema included as an appendix.
This is an advanced API reference. For most production integrations, we strongly recommend the official Python, Go, or JavaScript SDKs instead of talking to the sandbox agent directly. The SDKs already encapsulate authentication, ConnectRPC calling patterns, and streaming behavior.
What this page covers
This page documents the direct sandbox-agent integration path that becomes available after a sandbox is created and running.
ScaleBox has two relevant layers:
- the backend API as the control plane for lifecycle and metadata
- the sandbox agent as the runtime API surface exposed on the sandbox domain
Use the backend API for create, list, get, pause, resume, terminate, templates, projects, usage, and billing. Use this page when you want to call the running sandbox directly for files, processes, PTY-style interaction, or code-execution services.
Prerequisites
Create or fetch a sandbox through the backend API, CLI, or SDK. The sandbox object can include:
sandbox_domainenvd_access_token
Example:
{
"sandbox_id": "sbx-abc123def456789",
"sandbox_domain": "sbx-abc123def456789.example.scalebox.dev",
"envd_access_token": "envd-abc123def456789"
}The envd_access_token is the sandbox-scoped token expected by the sandbox agent in the X-Access-Token header.
Base URL and authentication
Direct calls use the sandbox domain as the host:
export SANDBOX_DOMAIN="sbx-abc123def456789.example.scalebox.dev"
export ENVD_ACCESS_TOKEN="envd-abc123def456789"For most direct sandbox-agent requests, send:
Authorization: Bearer root
X-Access-Token: <envd_access_token>Example:
curl -X POST "https://${SANDBOX_DOMAIN}/sandboxagent.ContextService/CreateContext" \
-H "Authorization: Bearer root" \
-H "X-Access-Token: ${ENVD_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"language":"python","cwd":"/home/user"}'Integration model
There are two transport shapes:
| Shape | Examples | Notes |
|---|---|---|
| Plain HTTP endpoints | /health, /upload, /download/... | Simple HTTP handlers |
| ConnectRPC-over-HTTP procedure endpoints | /sandboxagent.Process/Start, /sandboxagent.Filesystem/ListDir, /sandboxagent.ExecutionService/Execute | Procedure-based RPC contract backed by the published protobuf schema |
That means many sandbox-agent operations are still HTTP-callable, but they are not free-form REST endpoints. They depend on the RPC contract defined by the protobuf schema.
Endpoint inventory
The current sandbox-agent surface exposes 22 HTTP-callable endpoints:
- 3 plain HTTP endpoints
- 19 ConnectRPC procedure endpoints
Plain HTTP endpoints
| Method | Path | Auth | Notes |
|---|---|---|---|
GET | /health | none | Returns OK |
POST | /upload | headers or signed URL | Multipart upload endpoint |
GET | /download/{path} | headers or signed URL | File download endpoint with range support |
ConnectRPC procedure endpoints
Filesystem
| Procedure path | Type | Purpose |
|---|---|---|
/sandboxagent.Filesystem/Stat | unary | Stat a file or directory |
/sandboxagent.Filesystem/MakeDir | unary | Create a directory |
/sandboxagent.Filesystem/Move | unary | Move or rename a path |
/sandboxagent.Filesystem/ListDir | unary | List directory contents |
/sandboxagent.Filesystem/Remove | unary | Remove a file or directory |
/sandboxagent.Filesystem/CreateWatcher | unary | Create a watcher and return watcher_id |
/sandboxagent.Filesystem/GetWatcherEvents | unary | Poll watcher events |
/sandboxagent.Filesystem/RemoveWatcher | unary | Remove a watcher |
/sandboxagent.Filesystem/WatchDir | server stream | Stream filesystem events |
Process
| Procedure path | Type | Purpose |
|---|---|---|
/sandboxagent.Process/List | unary | List tracked processes |
/sandboxagent.Process/Connect | server stream | Attach to an existing process or PTY |
/sandboxagent.Process/Start | server stream | Start a process and stream output |
/sandboxagent.Process/Update | unary | Update process state such as PTY size |
/sandboxagent.Process/StreamInput | client stream | Stream stdin or PTY input |
/sandboxagent.Process/SendInput | unary | Send one input payload |
/sandboxagent.Process/SendSignal | unary | Send SIGTERM or SIGKILL |
Execution and context
| Procedure path | Type | Purpose |
|---|---|---|
/sandboxagent.ExecutionService/Execute | server stream | Execute code and stream outputs or results |
/sandboxagent.ContextService/CreateContext | unary | Create an execution context |
/sandboxagent.ContextService/DestroyContext | unary | Destroy an execution context |
Plain HTTP examples
Health
curl "https://${SANDBOX_DOMAIN}/health"Upload
curl -X POST "https://${SANDBOX_DOMAIN}/upload" \
-H "Authorization: Bearer root" \
-H "X-Access-Token: ${ENVD_ACCESS_TOKEN}" \
-F "file=@./script.py" \
-F "path=/tmp/script.py"Download
curl -L "https://${SANDBOX_DOMAIN}/download/tmp/script.py" \
-H "Authorization: Bearer root" \
-H "X-Access-Token: ${ENVD_ACCESS_TOKEN}" \
-o ./script.pyUnary ConnectRPC examples
Create an execution context
curl -X POST "https://${SANDBOX_DOMAIN}/sandboxagent.ContextService/CreateContext" \
-H "Authorization: Bearer root" \
-H "X-Access-Token: ${ENVD_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"language": "python",
"cwd": "/home/user"
}'List a directory
curl -X POST "https://${SANDBOX_DOMAIN}/sandboxagent.Filesystem/ListDir" \
-H "Authorization: Bearer root" \
-H "X-Access-Token: ${ENVD_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"path": "/tmp",
"depth": 1
}'Start a command
Running a shell command such as python3 my.py goes through the Process service, not a free-form REST execution endpoint. In practice, direct command execution is an RPC-style POST to /sandboxagent.Process/Start using the protobuf-defined request shape.
curl -X POST "https://${SANDBOX_DOMAIN}/sandboxagent.Process/Start" \
-H "Authorization: Bearer root" \
-H "X-Access-Token: ${ENVD_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"process": {
"cmd": "python3",
"args": ["my.py"],
"cwd": "/home/user",
"envs": {}
}
}'Streaming note
For a real streaming experience such as process output, command execution, PTY attachment, or streamed code execution, downstreams should assume they need a ConnectRPC-capable integration. Even when the transport is HTTP, the stream structure is defined by the protobuf contract.
That is why the official SDKs are still the recommended path.
Official SDKs
Appendix: protobuf schema
The following schema is the current published sandbox-agent contract for advanced direct integrations.
syntax = "proto3";
package sandboxagent;
import "google/protobuf/timestamp.proto";
option go_package = "./pb";
service Filesystem {
rpc Stat(StatRequest) returns (StatResponse);
rpc MakeDir(MakeDirRequest) returns (MakeDirResponse);
rpc Move(MoveRequest) returns (MoveResponse);
rpc ListDir(ListDirRequest) returns (ListDirResponse);
rpc Remove(RemoveRequest) returns (RemoveResponse);
rpc WatchDir(WatchDirRequest) returns (stream WatchDirResponse);
// Non-streaming versions of WatchDir
rpc CreateWatcher(CreateWatcherRequest) returns (CreateWatcherResponse);
rpc GetWatcherEvents(GetWatcherEventsRequest) returns (GetWatcherEventsResponse);
rpc RemoveWatcher(RemoveWatcherRequest) returns (RemoveWatcherResponse);
}
message MoveRequest {
string source = 1;
string destination = 2;
}
message MoveResponse {
EntryInfo entry = 1;
}
message MakeDirRequest {
string path = 1;
}
message MakeDirResponse {
EntryInfo entry = 1;
}
message RemoveRequest {
string path = 1;
}
message RemoveResponse {}
message StatRequest {
string path = 1;
}
message StatResponse {
EntryInfo entry = 1;
}
message EntryInfo {
string name = 1;
FileType type = 2;
string path = 3;
int64 size = 4;
uint32 mode = 5;
string permissions = 6;
string owner = 7;
string group = 8;
google.protobuf.Timestamp modified_time = 9;
optional string symlink_target = 10;
}
enum FileType {
FILE_TYPE_UNSPECIFIED = 0;
FILE_TYPE_FILE = 1;
FILE_TYPE_DIRECTORY = 2;
}
message ListDirRequest {
string path = 1;
uint32 depth = 2;
}
message ListDirResponse {
repeated EntryInfo entries = 1;
}
message WatchDirRequest {
string path = 1;
bool recursive = 2;
}
message FilesystemEvent {
string name = 1;
EventType type = 2;
}
message WatchDirResponse {
oneof event {
StartEvent start = 1;
FilesystemEvent filesystem = 2;
KeepAlive keepalive = 3;
}
message StartEvent {}
message KeepAlive {}
}
message CreateWatcherRequest {
string path = 1;
bool recursive = 2;
}
message CreateWatcherResponse {
string watcher_id = 1;
}
message GetWatcherEventsRequest {
string watcher_id = 1;
}
message GetWatcherEventsResponse {
repeated FilesystemEvent events = 1;
}
message RemoveWatcherRequest {
string watcher_id = 1;
}
message RemoveWatcherResponse {}
enum EventType {
EVENT_TYPE_UNSPECIFIED = 0;
EVENT_TYPE_CREATE = 1;
EVENT_TYPE_WRITE = 2;
EVENT_TYPE_REMOVE = 3;
EVENT_TYPE_RENAME = 4;
EVENT_TYPE_CHMOD = 5;
}
service Process {
rpc List(ListRequest) returns (ListResponse);
rpc Connect(ConnectRequest) returns (stream ConnectResponse);
rpc Start(StartRequest) returns (stream StartResponse);
rpc Update(UpdateRequest) returns (UpdateResponse);
rpc StreamInput(stream StreamInputRequest) returns (StreamInputResponse);
rpc SendInput(SendInputRequest) returns (SendInputResponse);
rpc SendSignal(SendSignalRequest) returns (SendSignalResponse);
}
message PTY {
Size size = 1;
message Size {
uint32 cols = 1;
uint32 rows = 2;
}
}
message ProcessConfig {
string cmd = 1;
repeated string args = 2;
map<string, string> envs = 3;
optional string cwd = 4;
}
message ListRequest {}
message ProcessInfo {
ProcessConfig config = 1;
uint32 pid = 2;
optional string tag = 3;
}
message ListResponse {
repeated ProcessInfo processes = 1;
}
message StartRequest {
ProcessConfig process = 1;
optional PTY pty = 2;
optional string tag = 3;
}
message UpdateRequest {
ProcessSelector process = 1;
optional PTY pty = 2;
}
message UpdateResponse {}
message ProcessEvent {
oneof event {
StartEvent start = 1;
DataEvent data = 2;
EndEvent end = 3;
KeepAlive keepalive = 4;
}
message StartEvent {
uint32 pid = 1;
}
message DataEvent {
oneof output {
bytes stdout = 1;
bytes stderr = 2;
bytes pty = 3;
}
}
message EndEvent {
sint32 exit_code = 1;
bool exited = 2;
string status = 3;
optional string error = 4;
}
message KeepAlive {}
}
message StartResponse {
ProcessEvent event = 1;
}
message ConnectResponse {
ProcessEvent event = 1;
}
message SendInputRequest {
ProcessSelector process = 1;
ProcessInput input = 2;
}
message SendInputResponse {}
message ProcessInput {
oneof input {
bytes stdin = 1;
bytes pty = 2;
}
}
message StreamInputRequest {
oneof event {
StartEvent start = 1;
DataEvent data = 2;
KeepAlive keepalive = 3;
}
message StartEvent {
ProcessSelector process = 1;
}
message DataEvent {
ProcessInput input = 2;
}
message KeepAlive {}
}
message StreamInputResponse {}
enum Signal {
SIGNAL_UNSPECIFIED = 0;
SIGNAL_SIGTERM = 15;
SIGNAL_SIGKILL = 9;
}
message SendSignalRequest {
ProcessSelector process = 1;
Signal signal = 2;
}
message SendSignalResponse {}
message ConnectRequest {
ProcessSelector process = 1;
}
message ProcessSelector {
oneof selector {
uint32 pid = 1;
string tag = 2;
}
}
service ExecutionService {
rpc Execute(ExecuteRequest) returns (stream ExecuteResponse) {}
}
service ContextService {
rpc CreateContext(CreateContextRequest) returns (Context) {}
rpc DestroyContext(DestroyContextRequest) returns (DestroyContextResponse) {}
}
message ExecuteRequest {
string context_id = 1;
string code = 2;
string language = 3;
map<string, string> env_vars = 4;
}
message ExecuteResponse {
oneof event {
Output stdout = 1;
Output stderr = 2;
Result result = 3;
Error error = 4;
}
}
message Output {
string content = 1;
}
message Result {
int32 exit_code = 1;
google.protobuf.Timestamp started_at = 2;
google.protobuf.Timestamp finished_at = 3;
string text = 4;
string html = 5;
string markdown = 6;
string svg = 7;
string png = 8;
string jpeg = 9;
string pdf = 10;
string latex = 11;
string json = 12;
string javascript = 13;
string data = 14;
Chart chart = 15;
int32 execution_count = 16;
bool is_main_result = 17;
map<string, string> extra = 18;
}
message Error {
string name = 1;
string value = 2;
string traceback = 3;
}
message Chart {
string type = 1;
string title = 2;
repeated ChartElement elements = 3;
map<string, string> extra = 4;
}
message ChartElement {
oneof element {
PointData point_data = 1;
BarData bar_data = 2;
PieData pie_data = 3;
BoxAndWhiskerData box_whisker_data = 4;
Chart nested_chart = 5;
}
}
message PointData {
string label = 1;
repeated Point points = 2;
}
message Point {
oneof x_value {
string x_str = 1;
double x_num = 2;
}
oneof y_value {
string y_str = 3;
double y_num = 4;
}
}
message BarData {
string label = 1;
string group = 2;
string value = 3;
}
message PieData {
string label = 1;
double angle = 2;
double radius = 3;
}
message BoxAndWhiskerData {
string label = 1;
double min = 2;
double first_quartile = 3;
double median = 4;
double third_quartile = 5;
double max = 6;
repeated double outliers = 7;
}
message Chart2D {
string x_label = 1;
string y_label = 2;
string x_unit = 3;
string y_unit = 4;
Chart chart = 5;
}
message PointChart {
repeated string x_ticks = 1;
repeated string x_tick_labels = 2;
string x_scale = 3;
repeated string y_ticks = 4;
repeated string y_tick_labels = 5;
string y_scale = 6;
Chart2D chart_2d = 7;
}
message CreateContextRequest {
string language = 1;
string cwd = 2;
}
message Context {
string id = 1;
string language = 2;
string cwd = 3;
google.protobuf.Timestamp created_at = 4;
map<string, string> env_vars = 5;
}
message DestroyContextRequest {
string context_id = 1;
}
message DestroyContextResponse {
bool success = 1;
}