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 URL | http://<robot>:7702/v1 |
| Format | JSON; Server-Sent Events, Motion JPEG and WAV for streams |
| Description | OpenAPI 3.1 at GET /v1/openapi.json; this reference is generated from it |
| Reachable from | The local network only |
| Auth | Authorization: Bearer <token> |
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:
| Variable | Default | Meaning |
|---|---|---|
NAO_RS_REST | on | off disables the REST API |
NAO_RS_REST_PORT | 7702 | The port |
NAO_RS_REST_ALLOW | local networks only | Extra networks to allow, comma-separated, for example 100.64.0.0/10 |
NAO_RS_REST_TOKEN_FILE | $HOME/.config/nao-rs/rest-token | Where the token lives |
NAO_RS_REST_RATE | 50 | Requests 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:
POST /v1/controlwith{"client": "<name>"}returns a lease.- Send the lease id in the
Nao-Leaseheader with every command. Without it:428 missing_lease. - 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. DELETE /v1/controlreleases it.
| Rule | Result |
|---|---|
| A brain is connected over the MessagePack API | No lease: 409 brain_connected. A brain that connects ends the current lease |
| Another client holds the lease | 409 lease_held, unless you ask for "takeover": true |
| Your lease ended or was replaced | 409 not_lease_holder: take a new one |
| A new controller | Starts 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.
| Direction | Messages |
|---|---|
| Client to robot | hello, 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 client | welcome (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.
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:
| Status | Codes |
|---|---|
| 400 | malformed_json, bad_body |
| 401 | unauthorized |
| 403 | not_on_lan |
| 404 | not_found |
| 409 | brain_connected, lease_held, not_lease_holder, not_allowed |
| 415 | unsupported_media_type |
| 422 | invalid_input, invalid_query |
| 428 | missing_lease |
| 429 | rate_limited |
| 503 | no_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
| Stream | Endpoint | Format |
|---|---|---|
| State | GET /v1/state/stream?rate= | Server-Sent Events, 0.1 to 20 states per second (default 10) |
| Events | GET /v1/events/stream | Server-Sent Events: touch, fall, watchdog, safety, control |
| Camera | GET /v1/cameras/{top\|bottom}/stream?fps= | Motion JPEG, 0.5 to 15 frames per second (default 5) |
| Microphones | GET /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:
It has no motors and no death switch page; its stop sends the UDP stop to localhost, where no guard listens.
Reference
| Page | Operations |
|---|---|
| System | Health, the OpenAPI document |
| Robot | Identity, body and head versions, ports, hardware readings (head power and temperature, CPU temperature, fan, board versions) |
| State | Latest state, state stream |
| Events | Event stream |
| Safety | Death switch status, stop |
| Control | Leases, heartbeat, WebSocket control |
| LEDs | Groups, single LEDs, presets, animations |
| Audio | Speaker, microphones |
| Cameras | Images and streams |
| Power | Wake and rest |
| Motion | Body and head motion |
| Schemas | Every request and response shape |
Security
| Topic | State |
|---|---|
| Authentication | Exists: one bearer token per robot, created on first start |
| Network | Exists: local networks only, by default |
| Motors | Holds: no endpoint bypasses the death switch, none can re-arm it |
| Stop | Exists: reachable without a token, on purpose, like the UDP stop on port 7701 |
| Per-client tokens with read-only and control scopes | Planned |
| TLS | Planned; today the token travels in plain HTTP on the local network |
| The MessagePack API on port 7700 | Still without authentication |
| Updates | Signed releases planned |