Remotes API

Register machines that connect as remotes, inspect their status, and dispatch command executions with streamed output.

Requires API key scopes remotes:read (list, get, read exec output) and remotes:write (register, update, delete, heartbeat, WebSocket connect, dispatch execs, signal runs).

Remotes guide · CLI setup


Concepts

ResourceDescription
RemoteA machine running belt remote. It hosts agent harness profiles and optional shell execution.
ProfileOne harness slot on a remote (for example Claude Code). Profiles are created from daemon registration. There is no REST endpoint that creates them.
Exec runOne command executed on a remote, with sequenced stdout/stderr output stored for replay

The daemon registers with POST /remotes, then keeps an outbound WebSocket open at wss://api.inference.sh/ws/remotes/{id} (or your configured API host). Command output arrives over that socket as remote_exec_output frames and is stored per run.

Exec opt-in. exec_enabled on the remote reflects whether the daemon was started with belt remote --exec. The API refuses dispatch when it is false, and the daemon refuses the command frame as well.


Register remote

POST /remotes

Called by belt remote on startup. Registration keys on the daemon's public_key for your user. A machine that registers again with the same key reuses its existing remote and does not create a duplicate. Each registration also reconciles the harnesses probe results into profiles (one per harness kind) and returns the remote with profiles populated.

Request

FieldTypeDescription
namestringDisplay name. belt remote sends the hostname unless --name is set.
public_keystringBase64 ed25519 public key that identifies the machine
remote_versionstringbelt version string
system_infoobjectHost metadata (see below)
exec_enabledbooleanWhether this daemon accepts command execution (belt remote --exec)
harnessesarrayHarnesses discovered on the host (see below)

The handler does not validate any of these fields as required.

system_info

belt remote sends this subset of the API's SystemInfo object. Fields it does not report (gpus, docker, volumes, and others) default to zero values.

FieldTypeDescription
hostnamestringMachine hostname
osstringOS label, for example Ubuntu 24.04 LTS or macOS
cpusarrayCPU entries with name, cores, and frequency
ramobjecttotal is physical memory in bytes
json
1{2  "hostname": "work-laptop",3  "os": "macOS",4  "cpus": [{ "name": "Apple M3 Pro", "cores": 12 }],5  "ram": { "total": 38654705664 }6}

harnesses entries

Each object describes one agent harness binary found on PATH:

FieldTypeDescription
kindstringHarness identifier: claude-code, codex, gemini, cursor, opencode, or amp
commandstringResolved path to the harness binary
versionstringFirst line of <command> --version. Optional, best effort.
logged_inbooleantrue when a vendor auth file exists on the host. The daemon checks existence only and never reads the file.

A harness kind that is missing from the array is not installed, or not on PATH.

Profile reconciliation

On each registration the API reconciles harnesses into the remote's profiles:

Registration inputProfile effect
New kindCreates a profile with harness_kind, command, and status
Existing kindUpdates command and status in place (same profile id)
logged_in: truestatus becomes idle
logged_in: falsestatus becomes unavailable
Harness no longer reportedThe profile is marked unavailable. It is not deleted.

The API never receives harness credentials.

Response

RemoteDTO:

FieldTypeDescription
idstringRemote ID
namestringDisplay name
statusstringLifecycle status (see below)
heartbeat_atstring | nullTime of the last heartbeat
system_infoobject | nullHost metadata from registration
remote_versionstringbelt version the daemon reported
exec_enabledbooleanWhether the host opted in to command execution
profilesarrayHarness profiles (see below)

public_key is not part of the response.

Profile

Each ProfileDTO in profiles:

FieldTypeDescription
remote_idstringOwning remote
harness_kindstringHarness type (claude-code, codex, ...)
namestringDisplay name for this slot
commandstringHarness binary path on the remote
argsstring[]Extra launch arguments
statusstringidle, busy, or unavailable
max_concurrentintegerConcurrency cap for this profile
Profile statusMeaning
idleReady to take a run
busyAt its concurrency limit
unavailableLogged out, removed from the host, rate-limited, or disabled

Remote lifecycle status:

statusMeaning
pendingRegistered, not yet connected
runningConnected and sending heartbeats
disconnectedHeartbeat is stale. Reversible on reconnect.
stoppedFinalized after prolonged disconnection

List remotes

GET /remotes or POST /remotes/list

Cursor-paginated list of remotes. Requires remotes:read.

bash
1curl -X POST https://api.inference.sh/remotes/list \2  -H "Authorization: Bearer inf_your_key" \3  -H "Content-Type: application/json" \4  -d '{"limit": 20}'

Get remote

GET /remotes/{id}

Returns a RemoteDTO with profiles included. Requires remotes:read. Use the id that belt remote prints when it connects, or one from the list endpoint.


Update remote

POST /remotes/{id}

Updates a remote you own and returns the updated RemoteDTO. Requires remotes:write.

The body is a full remote object. The update writes every mutable field, so a field you omit is reset to its zero value. Send the object from GET /remotes/{id} with your changes applied.

public_key is one of those fields and no response returns it. An update that omits it clears the key, and the next belt remote start on that machine registers a new remote.

belt remote overwrites name, status, system_info, remote_version, and exec_enabled on its next registration.


Delete remote

DELETE /remotes/{id}

Deletes a remote you own. Requires remotes:write.

bash
1curl -X DELETE https://api.inference.sh/remotes/remote_abc123 \2  -H "Authorization: Bearer inf_your_key"

Heartbeat

POST /remotes/{id}/heartbeat

Records a liveness ping. Requires remotes:write. belt remote sends its heartbeats over the WebSocket. This endpoint is the HTTP equivalent. The body is optional. The response is a bare JSON object without the standard envelope:

json
1{ "revived": false }

revived is true when the remote was disconnected and this beat moved it back to running.


Dispatch exec

POST /remotes/{id}/execs

Runs a command on a connected remote. Returns an ExecRunDTO immediately. Output is read separately. The Exec Runs API page lists every response field.

Request

FieldTypeDescription
commandstringExecutable name or path (required)
argsstring[]Arguments
cwdstringWorking directory on the remote
envstring[]Extra KEY=VALUE entries
ptybooleanRun under a pseudo-terminal
timeout_msintegerTimeout in milliseconds (0 = no limit)
bash
1curl -X POST "https://api.inference.sh/remotes/remote_abc123/execs" \2  -H "Authorization: Bearer inf_your_key" \3  -H "Content-Type: application/json" \4  -d '{"command": "uname", "args": ["-a"]}'

Errors

HTTPCodeCause
400invalid_requestcommand is empty or the body is not valid JSON
403exec_disabledThe daemon was not started with --exec
409remote_offlineThe remote is not connected

Get exec run

GET /execs/{id}

Returns an ExecRunDTO: command, status, exit code, timing, and last_seq (highest output sequence number).

statusTerminal?Meaning
pendingNoCreated, not yet sent to the host
runningNoSent to the host
exitedYesProcess ended. exit_code is set.
killedYesEnded without an exit code: signal, timeout, cancel, or the daemon refused the run
deniedYesThe API could not deliver the command to the daemon

List exec runs

GET /execs or POST /execs/list

Cursor-paginated list of exec runs. Requires remotes:read.


Read exec output

GET /execs/{id}/output?after_seq={n}

Returns an array of ExecRunOutputDTO chunks with seq > after_seq, in order, up to 2000 per call. Poll with an increasing after_seq until the run reaches a terminal status.

FieldTypeDescription
seqintegerSequence number within the run
streamstringstdout or stderr
datastringOutput bytes, base64-encoded
bash
1curl "https://api.inference.sh/execs/exec_abc123/output?after_seq=0" \2  -H "Authorization: Bearer inf_your_key"

Signal exec

POST /execs/{id}/signal

Kills a running exec. Returns {"ok": true}. A run that is already terminal returns the same response and nothing is sent.

bash
1curl -X POST "https://api.inference.sh/execs/exec_abc123/signal" \2  -H "Authorization: Bearer inf_your_key"

WebSocket protocol

The daemon connects to /ws/remotes/{id} with Authorization: Bearer <api-key> and optional X-Team-ID. Requires remotes:write.

Server to remote:

EventPurpose
remote_exec_startStart a command (exec_run_id, command, args, cwd, env, pty, timeout_ms)
remote_exec_signalKill a run

Remote to server:

EventPurpose
remote_heartbeatLiveness (status: running)
remote_exec_outputSequenced stdout/stderr chunk
remote_exec_exitRun finished (exit_code, error, timed_out, duration_ms)

The protocol also defines remote_terminal_* frames for PTY sessions. No REST endpoint opens a terminal session, and the API only logs the terminal frames it receives.


Scopes

ScopeAccess
remotes:readList and get remotes, list and get exec runs, read output
remotes:writeRegister, update, and delete remotes, heartbeat, WebSocket connect, dispatch execs, signal runs

we use cookies

we use cookies to ensure you get the best experience on our website. for more information on how we use cookies, please see our cookie policy.

by clicking "accept", you agree to our use of cookies.
learn more.