A small, key-authenticated HTTP API over the same engine the app uses. Analyze any build — multirotor, fixed-wing, or VTOL — work out radio link budgets and center-of-gravity balance, read the parts catalog and airframe types, and manage your own saved builds, from your scripts, notebooks, or apps.
Import the OpenAPI file into editor.swagger.io or any client to get a full interactive reference, or load the Postman collection and set base_url + api_key.
Sign in, open My account → API access, and click Create API key. The key is shown once — copy it somewhere safe. It looks like rl_…. Create a separate named key for each
integration; revoking a compromised key leaves the others working. Ground stations and other
equipment use a device credential instead: it belongs to your organization rather than to
an individual, so it remains valid after the person who created it leaves, and it reaches only
the flight and checklist endpoints.
Send your key as a bearer token (the header X-API-Key is also accepted). Base URL for this instance is /api/v1.
curl -H "Authorization: Bearer rl_your_key" \
http://127.0.0.1:8765/api/v1/version
curl -X POST http://127.0.0.1:8765/api/v1/analyze \
-H "Authorization: Bearer rl_your_key" \
-H "Content-Type: application/json" \
-d '{"airframe_type":"Quad X","motor_count":4,"prop_diameter_in":7,"motor_kv":1700}'
The response has out (headline numbers — AUW, TWR, hover/cruise endurance, currents, top speed…), charts (inline SVG), checks ([level, message] pairs), plus lint, platform, and cg.
Send a fixed-wing or VTOL airframe_type with your wing dimensions and out carries the winged figures — stall and cruise speed, best climb rate, wing loading — instead of (or, for a VTOL, alongside) the hover numbers.
curl -X POST http://127.0.0.1:8765/api/v1/analyze \
-H "Authorization: Bearer rl_your_key" -H "Content-Type: application/json" \
-d '{"airframe_type":"Fixed wing (tractor)","wing_span_mm":1800,"wing_chord_mm":220,"wing_cl_max":1.2,"motor_kv":900,"prop_diameter_in":11}'
# out.stall_speed_kmh · out.cruise_speed_kmh · out.climb_rate_ms · out.wing_loading_n_m2
GET /api/v1/airframes lists every supported type. Two more physics endpoints stand on their own:
# radio link budget — one link, or control + video together
curl -X POST http://127.0.0.1:8765/api/v1/rf \
-H "Authorization: Bearer rl_your_key" -H "Content-Type: application/json" \
-d '{"freq_mhz":5800,"tx_power_mw":1000,"rx_gain_dbi":13,"rx_sens_dbm":-90,"tx_height_m":100,"rx_height_m":2}'
# link.max_range_km · link.fresnel_radius_m · link.limiter
# center of gravity and balance for a build
curl -X POST http://127.0.0.1:8765/api/v1/cg \
-H "Authorization: Bearer rl_your_key" -H "Content-Type: application/json" \
-d '{"airframe_type":"Quadplane (lift + pusher)","wing_span_mm":1800,"body_length_mm":1000}'
# cg.cg_mm · cg.cg_pct_mac · cg.total_mass_g
POST /api/v1/crash reads a flight log
after an incident. The body is the log file itself — raw binary, not multipart and not
base64 — and the format is detected from the file’s own bytes: ArduPilot dataflash
(.bin), MAVLink telemetry (.tlog), PX4 ULog
(.ulg), DJI flight records (DJIFlightRecord
.txt, including current encrypted ones), Parrot FreeFlight JSON, GUTMA
flight-logging JSON, Litchi / Airdata / DJI GO flight-log CSV, and Betaflight / INAV Blackbox
(.bbl).
Needs the Crash Analyzer add-on on the account (otherwise
402); 64 MB maximum; one metered unit, charged only once the upload is
accepted.
curl -X POST "http://127.0.0.1:8765/api/v1/crash?name=flight.bin" -H "Authorization: Bearer rl_your_key" -H "Content-Type: application/octet-stream" --data-binary @flight.bin
# crash.detected · crash.reason · crash.confidence (high | medium | low)
# preconditions.findings[] · timeline[] · meta.vehicle · meta.source_format
# diagnostics.findings[] · diagnostics.battery.internal_resistance_mohm
# diagnostics.ekf.worst_ratio · diagnostics.compass.throttle_field_r
# add &charts=1 for ready-to-embed SVG charts (roughly triples the response)
The diagnostics block covers
pilot input against autopilot action, estimator innovation test ratios (1.0 is the rejection
threshold), pack internal resistance fitted from the flight’s own current draw, compass
interference, GPS quality and accelerometer clipping. Every section carries
available: when it is false it carries a
reason rather than numbers, so a log that never recorded a magnetometer
returns no compass reading instead of a zero. Check available before
reading a section.
import requests
BASE = "http://127.0.0.1:8765/api/v1"
h = {"Authorization": "Bearer rl_your_key"}
r = requests.post(f"{BASE}/analyze", headers=h,
json={"airframe_type": "Quad X", "motor_count": 4, "prop_diameter_in": 7})
out = r.json()["out"]
print(f"AUW {out['auw_g']:.0f} g · TWR {out['twr']:.2f} · hover {out['hover_min']:.1f} min")
# save it to your library
requests.post(f"{BASE}/builds", headers=h,
json={"name": "My quad", "params": {"airframe_type": "Quad X", "motor_count": 4}})
# read a log after an incident (.bin/.tlog/.ulg/DJI .txt/Parrot or GUTMA
# .json/flight-log .csv — detected from the bytes)
with open("flight.bin", "rb") as f:
A = requests.post(f"{BASE}/crash", headers=h, params={"name": "flight.bin"},
data=f.read()).json()
print(A["meta"]["vehicle_label"], A["crash"]["reason"], A["crash"]["confidence"])
for cat, why in A.get("preconditions", {}).get("findings", []):
print(f" [{cat}] {why}")
# the deeper diagnostics: severity-sorted, each naming the section it came from
for f in A.get("diagnostics", {}).get("findings", []):
print(f" [{f['level']}] {f['section']}: {f['title']}")
bat = A.get("diagnostics", {}).get("battery", {})
if bat.get("available"): # always check before reading
print("pack internal resistance:", bat["internal_resistance_mohm"], "mohm")
const BASE = "http://127.0.0.1:8765/api/v1";
const h = { "Authorization": "Bearer rl_your_key", "Content-Type": "application/json" };
const res = await fetch(`${BASE}/analyze`, { method: "POST", headers: h,
body: JSON.stringify({ airframe_type: "Quad X", motor_count: 4, prop_diameter_in: 7 }) });
const { out } = await res.json();
console.log(out.twr, out.hover_min);
All paths are under /api/v1 and scoped to the calling user. Builds you don’t own return 404.
| Method | Path | Description | Metered |
|---|---|---|---|
| GET | /version | API name, version, author | no |
| GET | /usage | Your quota, remaining, credit balance | no |
| GET | /pilots | Your organization’s pilots and where each one stands on currency. No email addresses. Currency is reported so a crew knows — it must never be used to prevent a flight. | no |
| GET | /aircraft | Your aircraft registry: name, registration, serial, model, status, hours flown | no |
| GET | /airframes | Supported airframe types | yes |
| GET | /catalog | Payload parts catalog | yes |
| POST | /analyze | Analyze a build (any airframe) → full results | yes |
| POST | /rf | Radio link budget and range (one link or several) | yes |
| POST | /cg | Center of gravity and balance for a build | yes |
| GET | /builds | List your saved builds | yes |
| GET | /builds/{id} | Fetch one of your builds | yes |
| POST | /builds | Create or overwrite a build by name | yes |
| DELETE | /builds/{id} | Delete one of your builds | yes |
RotorLab serves a read-only Model Context Protocol endpoint at
POST https://rotorlab.app/mcp. Point any MCP-speaking assistant
(Claude, or your own agent) at it with an API key as
Authorization: Bearer rl_… and it can ask questions of your
organization’s records: the aircraft register with airworthiness states,
per-aircraft health trends, recent flights, open work and occurrences, battery
packs with their retirement horizons, the readiness meter, and the measured
maintenance experience (repair verification and component survival).
The endpoint is read-only by construction: there is no writing tool, the key’s
organization is the boundary for every read, and no tool returns member lists,
emails, or pilot identities. Keys carrying scopes need the mcp scope;
unscoped keys pass as they do everywhere else. Supported protocol revisions:
2025-06-18, 2025-03-26 and 2024-11-05, single JSON responses.
The live cockpit natively decodes MAVLink and MSP byte streams through the cloud relay. For a source with no byte protocol — a vendor cloud API, a custom tracker, a simulator — RotorLab telemetry (RLT) is the documented third way in: your adapter dials the same authenticated relay endpoint the standard agent uses and sends one JSON object per binary WebSocket frame. The relay never parses telemetry, so an adapter needs nothing server-side beyond your API key.
wss://<your-server>/relay/agent?channel=<label> (Authorization: Bearer rl_xxx)
{"rlt": 1, "src": "my-adapter", "t": 1755672000.1,
"lat": 42.5006, "lon": -90.6646, "alt_m": 241.0, "rel_alt_m": 41.0,
"roll": 2.1, "pitch": -4.8, "yaw": 231.0,
"volt": 22.9, "curr": 14.2, "rem_pct": 71, "thr_pct": 48,
"gs_ms": 8.4, "as_ms": 8.9, "vs_ms": -0.3, "sats": 17, "fix": "3D",
"vehicle": "My quad", "serial": "SN123", "mode": "Auto",
"armed": true, "commandable": false}
rlt, src and t
(unix seconds) are required; every other field is optional. Absent means your source does
not report it — the cockpit shows the gap rather than a made-up value, so never send
zeros for readings you don’t have. Units are fixed in the field names (meters, m/s, volts,
amps, degrees, percent). mode and fix are
plain display strings; map your source’s enums yourself. RLT links are
receive-only: declare commandable: false and ignore anything
sent to your adapter. Viewers pick Any aircraft (RotorLab telemetry) as the protocol on
the cockpit; recording and replay work unchanged. A complete reference
adapter that flies a scripted flight ships in the server tree as
tools/rlt_demo_agent.py.
Most requests spend one unit. version and
usage are free so a blocked client can still read its own status,
and flights and checklists are also
free — filing your own records is never metered, and a lapsed plan never blocks a
flight record. Your plan sets a daily quota (resets at 00:00 UTC), spent first. Once it’s gone, requests draw from your credit balance — credits come included with some plans, can be granted by an admin, or purchased from My account when monetization is enabled. When both are exhausted the API returns 429.
429 also includes Retry-After (seconds until the daily reset). Check GET /api/v1/usage at any time; it is free.| Status | Meaning |
|---|---|
| 401 | Missing or invalid API key |
| 403 | Key valid but the plan doesn’t allow it (e.g. an ended trial or lapsed billing period) |
| 404 | Unknown endpoint, or a build you don’t own |
| 400 | Malformed request body |
| 429 | Daily quota and credits exhausted |

Flight physics, fleet operations and what shipped — written for people who fly. No spam, unsubscribe in one click.