boxd.sh:9443. If your language doesn’t have a boxd SDK yet, generate one from the proto file and talk to it directly.
Installation
There is nothing to install from boxd’s side. Point a gRPC client at the endpoint, and generate stubs from the proto if your toolchain wants them.Endpoint
- Transport: TLS, with the same certificate the cluster’s HTTPS domains use. Plain HTTP/2 (h2c) is still accepted on the same port so clients released before TLS keep working; new integrations should connect over TLS. Auth is short-lived JWTs either way.
- Reflection: gRPC server reflection (the v1 protocol) is enabled, so tools like
grpcurl1.9+ work without the proto file. - Service:
boxd.api.v1.BoxdApi. Reflection also listsboxd.api.v1.DeviceBridge, the internal transport behind the client utilities, which is not part of the public surface.
The proto
The full service definition lives on its own page: Proto. Copy it into a localapi.proto and generate stubs with protoc, buf, grpc_tools, or @grpc/proto-loader.
You often don’t need the file at all. Server reflection is on, so the schema can be inferred straight from the endpoint, and grpcurl boxd.sh:9443 describe boxd.api.v1.BoxdApi prints it on demand.
Authentication
Two steps: a long-lived API key is exchanged for a short-lived JWT, and the JWT rides as a bearer token on every gRPC call.1. Create an API key
API keys are issued from the boxd console. Sign in at boxd.sh, open the API keys page, and create one. The raw key is shown once, so copy it immediately. The format isbxd_ followed by 43 base62 characters.
You can also create them via the CLI once you’re logged in:
2. Exchange for a JWT
Send the API key to the exchange endpoint on the console host, over HTTPS:3. Send it on every gRPC call
What an API-key session can do
An API key is fenced to one org. Amember key (the default) covers the machine, snapshot, checkpoint, disk, port, proxy, domain, and env/secret surface. An org key is a userless service credential that reaches only the org’s shared machines, and env/secret operations reject it too. A handful of RPCs additionally require a JWT from an interactive login (the browser or CLI device flow) and answer PERMISSION_DENIED to API-key sessions: CreateApiKey, ListApiKeys, DeleteApiKey, CreateToken, RevokeToken, LinkSshKey, CreateNetwork, ListNetworks, GetBilling, GetOrgBilling, and the checkout/portal calls. The practical consequence is that an API key can never mint more keys.
Commands
The full RPC surface is theservice BoxdApi block in the proto: machines, snapshots, checkpoints, disks, ports, proxies, domains, env vars and secrets, organizations, tokens, and billing. What a given RPC expects is documented on its request message, and grpcurl describe dumps any of it on demand. Egress control is SetVmEgressAllow plus the domains field of SetVarRequest; see Egress control.
Hello world
- grpcurl
- Go
- Node / TypeScript
- Python
Easiest way to verify auth and reachability, with no proto file needed since server reflection is on.Install:
brew install grpcurl or github.com/fullstorydev/grpcurl.Errors
Standard gRPC status codes. The most common ones you’ll hit:
The error message in
Status.message() is human-readable and safe to surface to users.
Streaming RPCs
Two RPCs stream:Execis bidirectional. The first message must containvm_idandcommand. Subsequent client messages withstdin=truepipe stdin, orwindow_change=truewithcols/rowsresize a PTY. Server messages carrydata, withis_stderrset for stderr chunks on non-PTY execs (PTY-mode execs merge stderr into stdout at the kernel). The final server message hasexit_codeset. Closing the client’s send half signals stdin EOF to the subprocess.UploadFileStreamis client-streaming and lifts the 4 MiB single-message gRPC cap ofUploadFile. The firstUploadFileChunkcarriesvm_id,path, andtotal_size, and subsequent chunks carry bytes only. The server reads exactlytotal_sizebytes and returns the confirmed byte count.
Calling from a browser or Cloudflare Worker
Raw gRPC needs HTTP/2 trailers and a socket API neither browsers nor Cloudflare Workers expose — both are limited tofetch and WebSocket. boxd.sh:9443 also speaks grpc-web, a wire-compatible HTTP/1.1-friendly framing of the same protobuf messages, over the same port, for exactly this case.
Everything above still applies: same endpoint, same Authorization: Bearer JWT (as a normal HTTP header this time, not gRPC metadata), same RPCs, same errors. The differences are only in how a message gets on the wire:
- Content type:
application/grpc-web+proto. Each message is one frame: a 1-byte flag, a 4-byte big-endian length, then the protobuf payload. The response ends with a trailer frame (flag bit0x80set) carryinggrpc-statusandgrpc-messageaskey: value\r\ntext —grpc-messageis percent-encoded, so decode it before displaying it. - CORS: enabled, so a browser can call directly with no server-side proxy.
ExecandUploadFileStreamrun half-duplex: the whole request (every message you’d have sent on a native gRPC stream) has to finish before the response starts streaming back, sincefetchcan’t read a response body while still writing the request body. This is already howexec()(send the command, read the result) and a file upload (send the bytes, read the confirmation) behave in every boxd SDK — the only thing that doesn’t work over grpc-web is an interactive PTY session, which needs a connection that’s live in both directions at once.
Unless you’re targeting a language with no grpc-web library, use the TypeScript SDK’s
@boxd-sh/sdk/web entry point rather than framing grpc-web by hand — see TypeScript SDK: Cloudflare Workers, browsers, and other non-Node runtimes. It implements the framing above once and exposes the same boxd.<namespace>.<verb>() surface as the Node SDK.