RotorLab logoRotorLabDeveloper API
RotorLab API

Build with the physics engine

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.

Download OpenAPI spec Download Postman collection Get an API key

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.

Quickstart

1 · Create your 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.

2 · Authenticate

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

3 · Analyze a build

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.

4 · The same endpoint analyzes a wing or a VTOL

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

Log forensics

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.

Python

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")

JavaScript (fetch)

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);

Endpoints

All paths are under /api/v1 and scoped to the calling user. Builds you don’t own return 404.

MethodPathDescriptionMetered
GET/versionAPI name, version, authorno
GET/usageYour quota, remaining, credit balanceno
GET/pilotsYour 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/aircraftYour aircraft registry: name, registration, serial, model, status, hours flownno
GET/airframesSupported airframe typesyes
GET/catalogPayload parts catalogyes
POST/analyzeAnalyze a build (any airframe) → full resultsyes
POST/rfRadio link budget and range (one link or several)yes
POST/cgCenter of gravity and balance for a buildyes
GET/buildsList your saved buildsyes
GET/builds/{id}Fetch one of your buildsyes
POST/buildsCreate or overwrite a build by nameyes
DELETE/builds/{id}Delete one of your buildsyes

Quotas, credits & purchasing

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.

Every metered response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Credits. A 429 also includes Retry-After (seconds until the daily reset). Check GET /api/v1/usage at any time; it is free.

Errors

StatusMeaning
401Missing or invalid API key
403Key valid but the plan doesn’t allow it (e.g. an ended trial or lapsed billing period)
404Unknown endpoint, or a build you don’t own
400Malformed request body
429Daily quota and credits exhausted
RotorLab API · RotorLab.app · the API is available when the server runs in multi-user mode (--web --auth).