> Documentation index: https://nao-rs.lukaloehr.com/llms.txt, a list of every page in this documentation.

# 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](https://nao-rs.lukaloehr.com/reference/api.md)
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>` |

```bash
ROBOT=<robot-address>
TOKEN=$(ssh nao@$ROBOT cat .config/nao-rs/rest-token)
curl -H "Authorization: Bearer $TOKEN" http://$ROBOT:7702/v1/state
```

> **Warning:** 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:

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.

| 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.

```bash
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](https://nao-rs.lukaloehr.com/safety.md).

The switch's `reason` (in `GET /v1/safety` and the `safety` event) is one of
the [SwitchReason](https://nao-rs.lukaloehr.com/reference/rest-api/schemas.md#switchreason) values, the same
names as the death switch's [reason codes](https://nao-rs.lukaloehr.com/safety/death-switch.md#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](https://nao-rs.lukaloehr.com/safety/death-switch.md#page-integrity).

## Errors

Every error has the same shape and a stable `code`:

```json
{"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"}}
```

| 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:

```bash
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

| Page | Operations |
| --- | --- |
| [System](https://nao-rs.lukaloehr.com/reference/rest-api/system.md) | Health, the OpenAPI document |
| [Robot](https://nao-rs.lukaloehr.com/reference/rest-api/robot.md) | Identity, body and head versions, ports, hardware readings (head power and temperature, CPU temperature, fan, board versions) |
| [State](https://nao-rs.lukaloehr.com/reference/rest-api/state.md) | Latest state, state stream |
| [Events](https://nao-rs.lukaloehr.com/reference/rest-api/events.md) | Event stream |
| [Safety](https://nao-rs.lukaloehr.com/reference/rest-api/safety.md) | Death switch status, stop |
| [Control](https://nao-rs.lukaloehr.com/reference/rest-api/control.md) | Leases, heartbeat, WebSocket control |
| [LEDs](https://nao-rs.lukaloehr.com/reference/rest-api/leds.md) | Groups, single LEDs, presets, animations |
| [Audio](https://nao-rs.lukaloehr.com/reference/rest-api/audio.md) | Speaker, microphones |
| [Cameras](https://nao-rs.lukaloehr.com/reference/rest-api/cameras.md) | Images and streams |
| [Power](https://nao-rs.lukaloehr.com/reference/rest-api/power.md) | Wake and rest |
| [Motion](https://nao-rs.lukaloehr.com/reference/rest-api/motion.md) | Body and head motion |
| [Schemas](https://nao-rs.lukaloehr.com/reference/rest-api/schemas.md) | 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** |

## Related topics

- [System](https://nao-rs.lukaloehr.com/reference/rest-api/system.md)
- [Robot](https://nao-rs.lukaloehr.com/reference/rest-api/robot.md)
- [State](https://nao-rs.lukaloehr.com/reference/rest-api/state.md)
- [Events](https://nao-rs.lukaloehr.com/reference/rest-api/events.md)
- [Safety](https://nao-rs.lukaloehr.com/reference/rest-api/safety.md)
- [Control](https://nao-rs.lukaloehr.com/reference/rest-api/control.md)
- [LEDs](https://nao-rs.lukaloehr.com/reference/rest-api/leds.md)
- [Audio](https://nao-rs.lukaloehr.com/reference/rest-api/audio.md)
- [Cameras](https://nao-rs.lukaloehr.com/reference/rest-api/cameras.md)
- [Power](https://nao-rs.lukaloehr.com/reference/rest-api/power.md)
- [Motion](https://nao-rs.lukaloehr.com/reference/rest-api/motion.md)
- [Schemas](https://nao-rs.lukaloehr.com/reference/rest-api/schemas.md)
- Previous: [Reference](https://nao-rs.lukaloehr.com/reference.md)
- Next: [System](https://nao-rs.lukaloehr.com/reference/rest-api/system.md)
