ScaleBox Docs

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_domain
  • envd_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:

ShapeExamplesNotes
Plain HTTP endpoints/health, /upload, /download/...Simple HTTP handlers
ConnectRPC-over-HTTP procedure endpoints/sandboxagent.Process/Start, /sandboxagent.Filesystem/ListDir, /sandboxagent.ExecutionService/ExecuteProcedure-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

MethodPathAuthNotes
GET/healthnoneReturns OK
POST/uploadheaders or signed URLMultipart upload endpoint
GET/download/{path}headers or signed URLFile download endpoint with range support

ConnectRPC procedure endpoints

Filesystem

Procedure pathTypePurpose
/sandboxagent.Filesystem/StatunaryStat a file or directory
/sandboxagent.Filesystem/MakeDirunaryCreate a directory
/sandboxagent.Filesystem/MoveunaryMove or rename a path
/sandboxagent.Filesystem/ListDirunaryList directory contents
/sandboxagent.Filesystem/RemoveunaryRemove a file or directory
/sandboxagent.Filesystem/CreateWatcherunaryCreate a watcher and return watcher_id
/sandboxagent.Filesystem/GetWatcherEventsunaryPoll watcher events
/sandboxagent.Filesystem/RemoveWatcherunaryRemove a watcher
/sandboxagent.Filesystem/WatchDirserver streamStream filesystem events

Process

Procedure pathTypePurpose
/sandboxagent.Process/ListunaryList tracked processes
/sandboxagent.Process/Connectserver streamAttach to an existing process or PTY
/sandboxagent.Process/Startserver streamStart a process and stream output
/sandboxagent.Process/UpdateunaryUpdate process state such as PTY size
/sandboxagent.Process/StreamInputclient streamStream stdin or PTY input
/sandboxagent.Process/SendInputunarySend one input payload
/sandboxagent.Process/SendSignalunarySend SIGTERM or SIGKILL

Execution and context

Procedure pathTypePurpose
/sandboxagent.ExecutionService/Executeserver streamExecute code and stream outputs or results
/sandboxagent.ContextService/CreateContextunaryCreate an execution context
/sandboxagent.ContextService/DestroyContextunaryDestroy 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.py

Unary 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;
}

See also

On this page