Skip to main content

Relay API Reference

This page is the complete HTTP and WebSocket API reference for the Otto relay. Implementation source: packages/relay/src/index.ts.

Authentication

Most endpoints require a bearer token in the Authorization header:

Authorization: Bearer <accessToken>

Role expectations:

  • Controller access token — for controller-facing endpoints (commands, logs, node discovery).
  • Node access token — for node ACL endpoints (/api/controller/access).

Pairing endpoints

MethodPathDescription
POST/api/pairing/requestNode requests a pairing challenge
GET/api/pairing/pendingList pending pairing codes (node auth required)
POST/api/pairing/approveController approves a pairing code
GET/api/pairing/statusCheck pairing status for a challenge

Request: POST /api/pairing/request

{ "nodeId": "node_local_1" }

Request: POST /api/pairing/approve

{ "code": "ABCD-1234" }

Error codes: nodeId_required, code_required, pairing_not_found, pairing_not_pending, challengeId_required, challenge_not_found

Auth endpoints

MethodPathDescription
POST/api/auth/refreshRefresh an access token using a refresh token
POST/api/auth/revokeRevoke a refresh token

Request: POST /api/auth/refresh

{ "refreshToken": "<refresh_token>" }

Error codes: refreshToken_required, invalid_refresh_token

Controller client endpoints

MethodPathAuthDescription
POST/api/controller/registerNone (or secret)Register a new controller client
POST/api/controller/tokenNoneExchange client credentials for tokens
POST/api/controller/removeController bearerRevoke a controller client by ID
POST/api/controller/remove-allController bearerRevoke and purge all controller clients

Request: POST /api/controller/register

{ "name": "my-controller", "description": "automation worker", "avatarSeed": "optional-seed" }

Request: POST /api/controller/token

{ "clientId": "clt_abc123", "clientSecret": "cs_xxx" }

Request: POST /api/controller/remove

{ "clientId": "clt_abc123" }

Removal semantics:

  • Single remove revokes client record and tears down ACL grants, refresh sessions, and active controller sockets.
  • Bulk remove additionally purges records. Subsequent remove-all calls return removedCount: 0 until new clients are registered.

Error codes: registration_forbidden, controller_metadata_required, controller_name_conflict, client_credentials_required, invalid_client_credentials, client_not_found

Node ACL endpoints

MethodPathAuthDescription
GET/api/controller/accessNode bearerList ACL grants for connected node
POST/api/controller/accessNode bearerGrant or revoke controller access to node

Request: POST /api/controller/access

{ "clientId": "clt_abc123", "grant": true, "expiresAt": 1776165600000 }

Without an active grant, node-targeted commands return acl_missing_node_grant. Client secret is only used for /api/controller/token; runtime command authorization uses access-token scopes and node ACL grants.

Error codes: missing_access_token, forbidden_role, clientId_and_grant_required, invalid_expiresAt

Node discovery endpoints

MethodPathAuthDescription
GET/api/nodes/connectedController bearerList connected node IDs

Response: GET /api/nodes/connected

{ "nodes": [{ "nodeId": "node_local_1" }] }

Error codes: missing_access_token, forbidden_role, invalid_access_token

Log endpoints

MethodPathAuthDescription
GET/api/logsController bearerQuery operation logs
GET/api/logs/statusController bearerLog storage status and aggregate byte size
GET/api/logs/exportController bearerExport logs as NDJSON

Query parameters (logs and export):

ParameterDescription
sinceISO-8601 timestamp lower bound
leveldebug | info | warn | error
sourcerelay | controller | node | all
latestLimit to newest N matching entries
nodeIdFilter by node identity
requestIdFilter by request correlation ID

Response: GET /api/logs

{
"logs": [
{
"timestamp": "2026-04-14T13:00:00.000Z",
"source": "relay",
"type": "command_routed",
"requestId": "req_cmd_1"
}
]
}

Error codes: invalid_since, invalid_level, invalid_source, invalid_latest

WebSocket connections

RoleURL pattern
Controllerws://host:port?role=controller
Nodews://host:port?role=node

After connecting, send hello then auth frames. Relay responds with auth_ack when authentication succeeds. See Reusable Snippets for frame examples.

CLI mapping

CLI commandAPI operation
otto authcodeGET /api/pairing/pending
otto pair <code>POST /api/pairing/approve
otto revokePOST /api/auth/revoke
otto client registerPOST /api/controller/register
otto client loginPOST /api/controller/token + POST /api/auth/refresh
otto client removePOST /api/controller/remove
otto client statusValidates stored tokens
otto logs listGET /api/logs
otto logs exportGET /api/logs/export
otto logs statusGET /api/logs/status
otto commands listWebSocket command.list
otto cmdWebSocket command.run

Next steps