ReferenceREST API

REST API

The HTTP/JSON API of a robot running nao-rs - port, LAN-only access, token, control leases, errors, streams, and how to try it without a robot.

The REST API lets any program steer the robot with plain HTTP: a script that sets the eyes, a dashboard that shows the battery, a test that reads a sensor. It runs inside the robot program next to the MessagePack API and takes the same command path as the brain, so the watchdog, HULKs' motion safety and the death switch apply to it in the same way. On the robot, health, identity, the death switch status, the state and the LEDs (per group and per single LED) have been checked live.

Base URLhttp://<robot>:7702/v1
FormatJSON; Server-Sent Events, Motion JPEG and WAV for streams
DescriptionOpenAPI 3.1 at GET /v1/openapi.json; this reference is generated from it
Reachable fromThe local network only
AuthAuthorization: Bearer <token>
ROBOT=<robot-address>
TOKEN=$(ssh nao@$ROBOT cat .config/nao-rs/rest-token)
curl -H "Authorization: Bearer $TOKEN" http://$ROBOT:7702/v1/state

No motion in this phase

nao-rs doesn't drive the motors yet, and the robot must not move during bring-up without a person present. The power and motion operations are documented for completeness; use the API to read the robot, set LEDs and play audio.

Access

LAN only. The API answers clients on loopback, the private IPv4 networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), link-local (169.254.0.0/16, fe80::/10) and IPv6 unique local addresses (fc00::/7). Anyone else gets 403 not_on_lan. Tailscale addresses (100.64.0.0/10) are not allowed by default; start the robot program with NAO_RS_REST_ALLOW=100.64.0.0/10 to allow them.

Token. The robot program creates a token on its first start, 32 random bytes, in ~/.config/nao-rs/rest-token of the user it runs as (nao on the robot), mode 0600. Send it as Authorization: Bearer <token>. GET requests may pass it as ?access_token=<token> instead, so a browser can open a camera stream in an img element. Three endpoints need no token: GET /v1/health, GET /v1/openapi.json and POST /v1/safety/stop.

Rate limit. 50 requests per second per client address, bursts up to twice that; above it, 429 rate_limited with the time to wait.

Settings

Set in the robot program's environment:

VariableDefaultMeaning
NAO_RS_RESTonoff disables the REST API
NAO_RS_REST_PORT7702The port
NAO_RS_REST_ALLOWlocal networks onlyExtra networks to allow, comma-separated, for example 100.64.0.0/10
NAO_RS_REST_TOKEN_FILE$HOME/.config/nao-rs/rest-tokenWhere the token lives
NAO_RS_REST_RATE50Requests per second per client

Control

Reading is free for every client with the token. Commanding needs control, and only one controller exists at a time:

  1. POST /v1/control with {"client": "<name>"} returns a lease.
  2. Send the lease id in the Nao-Lease header with every command. Without it: 428 missing_lease.
  3. Send a heartbeat (POST /v1/control/heartbeat) or any command at least every 100 ms. After 0.5 s of silence the robot stands, after 3 s it sits, and after 10 s the lease ends.
  4. DELETE /v1/control releases it.
RuleResult
A brain is connected over the MessagePack APINo lease: 409 brain_connected. A brain that connects ends the current lease
Another client holds the lease409 lease_held, unless you ask for "takeover": true
Your lease ended or was replaced409 not_lease_holder: take a new one
A new controllerStarts with no motion

WebSocket control

GET /v1/control/ws upgrades to a WebSocket that holds control for as long as it is open, like the brain's TCP connection; closing it gives control back. Pass the token as ?access_token= or in the header, and send {"type": "hello", "client": "<name>"} within 5 s.

DirectionMessages
Client to robothello, heartbeat, motion, head, power, leds (group, setting), volume (like PUT /v1/audio/volume), stop. Any message may carry an id, which the answer repeats
Robot to clientwelcome (answer to hello), ok, error (the same codes as HTTP), stopped, and event with every robot event, pushed live

The same watchdog applies: send something at least every 100 ms. Limits: 100 messages per second, 64 KiB per message.

websocat "ws://$ROBOT:7702/v1/control/ws?access_token=$TOKEN"
{"type": "hello", "client": "pad", "id": 1}
{"type": "leds", "group": "eyes", "setting": {"color": "#0050ff"}, "id": 2}
{"type": "volume", "volume": {"percent": 40}, "id": 3}

Safety

POST /v1/safety/stop trips the death switch in two ways at once: it writes the switch page directly (reason Api) and sends the guard's UDP stop, so it works even when the robot program hangs. It needs no token and no lease. GET /v1/safety shows the switch. Nothing in the API can re-arm it: that stays naoctl rearm on the robot. See Safety model.

The switch's reason (in GET /v1/safety and the safety event) is one of the SwitchReason values, the same names as the death switch's reason codes. A page without its magic value reads as tripped with PageLost. A page cut short is not read at all: the switch shows available: false, with an unavailable_reason, and the drivers treat it as tripped. See Page integrity.

Errors

Every error has the same shape and a stable code:

{"error": {"code": "missing_lease", "message": "this command needs control: take a lease with POST /v1/control and send its id in the `Nao-Lease` header"}}
StatusCodes
400malformed_json, bad_body
401unauthorized
403not_on_lan
404not_found
409brain_connected, lease_held, not_lease_holder, not_allowed
415unsupported_media_type
422invalid_input, invalid_query
428missing_lease
429rate_limited
503no_state, no_image, too_many_streams, stop_failed

field names the input at fault when there is one. Each operation's page lists the statuses it returns.

Streams

StreamEndpointFormat
StateGET /v1/state/stream?rate=Server-Sent Events, 0.1 to 20 states per second (default 10)
EventsGET /v1/events/streamServer-Sent Events: touch, fall, watchdog, safety, control
CameraGET /v1/cameras/{top\|bottom}/stream?fps=Motion JPEG, 0.5 to 15 frames per second (default 5)
MicrophonesGET /v1/audio/microphone?format=wav or raw, 16 kHz mono 16-bit, endless

The number of open streams is limited; one too many gets 503 too_many_streams.

Try it without a robot

The nao-rs repository has a pretend robot that serves the same API:

cargo run -p nao_rs_rest --example mock_robot   # prints where it put the token
curl localhost:7702/v1/health

It has no motors and no death switch page; its stop sends the UDP stop to localhost, where no guard listens.

Reference

PageOperations
SystemHealth, the OpenAPI document
RobotIdentity, body and head versions, ports, hardware readings (head power and temperature, CPU temperature, fan, board versions)
StateLatest state, state stream
EventsEvent stream
SafetyDeath switch status, stop
ControlLeases, heartbeat, WebSocket control
LEDsGroups, single LEDs, presets, animations
AudioSpeaker, microphones
CamerasImages and streams
PowerWake and rest
MotionBody and head motion
SchemasEvery request and response shape

Security

TopicState
AuthenticationExists: one bearer token per robot, created on first start
NetworkExists: local networks only, by default
MotorsHolds: no endpoint bypasses the death switch, none can re-arm it
StopExists: reachable without a token, on purpose, like the UDP stop on port 7701
Per-client tokens with read-only and control scopesPlanned
TLSPlanned; today the token travels in plain HTTP on the local network
The MessagePack API on port 7700Still without authentication
UpdatesSigned releases planned

On this page