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, and Litchi / Airdata / DJI GO flight-log CSV.
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 |
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 |