Build on the agent.
curl http://<board-ip>:8080/api/status \
-H "X-ADOS-Key: <your-key>"REST API
A full REST control surface.
The control surface binds port 8080 and serves 239 routes: 199 native Rust routes and 40 proxied to the Python feature service. Responses are JSON. A paired agent expects its key in the X-ADOS-Key header. The complete route table is generated from the router and published in the agent repo as docs/api-surface.md; the nine below are the ones an integration starts with.
/api/status/api/telemetry/api/params/api/command/api/commands/api/config/api/config/api/services/api/logsTRANSPORTS
Ports and topics.
The agent exposes local ports for direct integration and MQTT topics for the optional cloud relay. Every transport speaks the same underlying MAVLink.
- REST control surface, setup webapp, WHEP video
- TCP :8080
- MAVLink WebSocket relay
- TCP :8765
- Raw MAVLink TCP proxy
- TCP :5760
- Raw MAVLink UDP proxies
- UDP :14550, :14551
- RTSP video
- TCP :8554
- Logging store read plane
- TCP :8090
- Cloud MAVLink from the drone
- ados/{id}/mavlink/tx
- Cloud MAVLink to the drone
- ados/{id}/mavlink/rx
- Cloud WebRTC signaling
- ados/{id}/webrtc/offer
EXAMPLES
Quick start.
curl -s http://<board-ip>:8080/api/telemetry \
-H "X-ADOS-Key: <your-key>" | jq
{
"armed": true,
"mode": "GUIDED",
"battery": { "voltage": 22.4, "current": 12.1, "remaining": 78 },
"gps": { "fix_type": 3, "satellites": 14 },
"position": { "lat": 47.3977, "lon": 8.5456, "alt_rel": 50.2 },
"attitude": { "roll": 0.02, "pitch": -0.01, "yaw": 3.18 }
}import httpx
BASE = "http://<board-ip>:8080"
headers = {"X-ADOS-Key": "<your-key>"}
telem = httpx.get(f"{BASE}/api/telemetry", headers=headers).json()
print(telem["battery"]["remaining"], telem["gps"]["satellites"])
httpx.post(
f"{BASE}/api/command",
headers=headers,
json={"cmd": "takeoff", "args": [10]},
)WEBSOCKET
Raw MAVLink relay.
The relay on port 8765 forwards raw MAVLink v2 between the flight controller and any number of consumers. Wire-compatible with the ArduPilot SITL WebSocket bridge.
- ✓Bidirectional: read telemetry and send commands
- ✓Raw binary MAVLink v2 frames, no JSON wrapping
- ✓Multi-consumer: many clients connect at once
- ✓Wire-compatible with the ArduPilot SITL bridge
- ✓Frames are forwarded, not re-encoded or buffered into batches
const ws = new WebSocket("ws://<board-ip>:8765");
ws.binaryType = "arraybuffer";
// inbound: raw MAVLink v2 frames from the flight controller
ws.onmessage = (e) => decodeMavlink(new Uint8Array(e.data));
// outbound: send a frame straight to the FC
ws.send(encodeCommandLong(sysid, compid, cmd, params));PLUGIN SDKS
ados.sdk and ados-sdk
Plugin and driver authors get the same surface in two languages: the Python ados.sdk and the Rust ados-sdk crate. Both ship with the agent and expose the same stable base classes for hardware drivers, the on-vehicle vision bus, and a plugin test harness.
CameraDriverCamera drivers over CSI, USB UVC, IP, and vendor SDKs
GimbalDriverGimbal drivers with pan, tilt, and roll control
LidarDriverLiDAR and rangefinder drivers
GpsDriverGPS and GNSS receiver drivers
EscDriverESC drivers with telemetry feedback
VisionClientRead frames and detections from the on-vehicle vision bus
PayloadActuatorDriverDrivers for releases, winches, and other payload actuators
SECURITY
Pairing key, then scoped tokens.
A paired agent requires its pairing key in the X-ADOS-Key header, and that key carries full access. An unpaired agent answers on the LAN with no key at all, so pairing is the first thing you do. For AI and automation clients the agent also accepts a scoped, revocable token on x-ados-mcp-token, admitted per route against the scope groups below. That lane is fail-closed and off until you turn it on: a write with no declared scope class is unreachable by token.
readEvery GET except plugin lifecycle: telemetry, status, params, services, logs. No state changes.secret_readA read whose body would carry a secret. Separate, so a read token cannot reach one.safe_writeReversible operator writes: vision target designation and log push.flightAnything that can move the aircraft: POST /api/command, CAN passthrough, RC channel injection, taking pilot-in-command.adminParameter writes, service restarts, radio and network changes, model upload, plugin config.destructiveUnpairing, which tears down the trust relationship with the ground station.MESSAGE INTEGRITY
MAVLink frame signing.
Signing is managed from the ground station. The agent exposes a small REST surface for capability detection, one-shot enrollment, and clearing the key. The agent never stores key material.
When MAVLink v2 signing is enabled for a drone, every outbound frame carries a 48-bit signature: the first 6 bytes of a SHA-256 hash over a 32-byte key the browser generated, the frame, a link id, and a timestamp. Once an ArduPilot flight controller holds the key, it rejects unsigned commands on every link except USB.
The key is pushed to the flight controller once, over the authenticated REST channel. After that the agent is a transparent pipe for signed frames and never writes the key to disk. The browser keeps it as raw bytes in IndexedDB, so the companion computer cannot forge commands. Export a copy before you enable signing on a drone you cannot reach over USB.
GET /api/mavlink/signing/capability
X-ADOS-Key: <your-key>
{
"supported": true,
"reason": "ok",
"firmware_name": "ArduPilot",
"firmware_version": "4.5.0",
"signing_params_present": true
}RELATED
Extend the agent.
Read the full API reference.
Complete endpoints, the WebSocket protocol, and the SDK documentation on GitHub.
View on GitHub