{"openapi":"3.1.0","info":{"title":"RangeIQ API — Range & Mission Analysis","description":"Stateless drone-analysis service hosting two engines under one roof:\n\n- **RangeIQ** — point-to-point range rings (`/v1/rangeiq/*`)\n- **RangeIQ+** — multi-leg waypoint mission analysis (`/v1/missions/*`)\n\nBoth engines share a drone catalog at `/v1/drones`. All routes are versioned\nunder `/v1`. The OpenAPI spec at `/openapi.json` is the canonical contract;\nSDK generators may consume it freely.\n\n## Authentication\n\nThree credentials are accepted, in the order they're checked:\n\n1. **JWT bearer** — short-lived ES256 token issued by `POST /v1/auth/token`.\n   The token is bound to the issuing client's mTLS certificate via the\n   `cnf.x5t#S256` confirmation claim (RFC 8705); a stolen token cannot be\n   replayed without the matching cert.\n2. **mTLS client certificate** — the LB validates the cert against a Trust\n   Config and forwards verified identity headers. Tenants are pinned by\n   SHA-256 fingerprint of the leaf cert.\n3. **`X-API-Key` header** (legacy) — preserved for the transition window.\n   Will be retired once all integrators are on mTLS+JWT.\n\nA given tenant may purchase access to RangeIQ, RangeIQ+, or both. The\ntoken (or cert, or API key) declares which engines it covers; cross-engine\ncalls are blocked at the auth layer with `403 FORBIDDEN`.\n\n## Scopes\n\nOAuth-style scopes are reserved for fine-grained method-level controls in\na later release. The current binding authorisation is the engine list:\n\n| Scope               | Grants                          |\n| ------------------- | ------------------------------- |\n| `rangeiq:analyze`   | `POST /v1/rangeiq/analyze`      |\n| `rangeiq:read`      | `GET /v1/rangeiq/*`             |\n| `rangeiq_plus:analyze` | `POST /v1/missions/analyze*`  |\n| `rangeiq_plus:read` | `GET /v1/missions/*`            |\n| `drones:read`       | `GET /v1/drones`                |\n\n## Quotas & rate limits\n\n- **RPS** (smooth burst control): per-tenant + per-engine token bucket.\n  Headers: `X-RateLimit-{Limit,Remaining,Reset}`.\n- **Daily / monthly quotas** (billing ceilings): per-tenant + per-engine\n  counters keyed to UTC calendar boundaries. Headers:\n  `X-Quota-{Daily,Monthly}-{Limit,Remaining,Reset}`.\n  A 429 from this layer carries error code `QUOTA_EXCEEDED` and a\n  `Retry-After` header pointing at the next period reset.\n\n## Idempotency\n\nSend `Idempotency-Key: <8–128 chars [A-Za-z0-9._\\-:]>` on POST requests\nto make retries safe. A retry with the same key + same body within 24h\nreturns the cached response verbatim (engine not re-invoked, no\nduplicate quota charge). The replay is marked with\n`X-Idempotent-Replay: true`. Reusing the key with a different body\nreturns `409 IDEMPOTENCY_KEY_CONFLICT` so client bugs surface loudly.\n\n## Client identification (recommended)\n\nTwo optional headers help us help you when something goes wrong:\n\n| Header               | Example      | Purpose                              |\n| -------------------- | ------------ | ------------------------------------ |\n| `X-Client-Platform`  | `android`, `ios`, `web`, `sdk` | Cross-platform telemetry split |\n| `X-Client-Version`   | `1.4.2`      | Version-specific bug investigation   |\n\nBoth are captured in the audit log (`client_platform`, `client_version`\nfields) so support engineers can filter by platform / version when\ninvestigating an incident. Sending them is optional and forward-\ncompatible — older clients that omit them keep working.\n\n## Error envelope\n\nEvery non-2xx response has the same shape:\n\n```\n{\n  \"ok\":         false,\n  \"error\":      {\"code\": \"<STABLE_STRING>\", \"message\": \"<human>\", \"details\": {...}},\n  \"request_id\": \"<32-char hex>\"\n}\n```\n\nStable codes:\n\n| Code                       | HTTP | Meaning                                   |\n| -------------------------- | ---- | ----------------------------------------- |\n| `AUTH_REQUIRED`            | 401  | No valid credential presented             |\n| `AUTH_MTLS_REQUIRED`       | 401  | Endpoint requires a verified mTLS cert    |\n| `AUTH_TOKEN_EXPIRED`       | 401  | JWT past its `exp` claim                  |\n| `AUTH_INVALID_TOKEN`       | 401  | JWT signature or claims invalid           |\n| `AUTH_TOKEN_CERT_MISMATCH` | 401  | JWT bound to a different cert (RFC 8705)  |\n| `KEY_EXPIRED`              | 401  | API key outside its rotation window       |\n| `AMBIGUOUS_CREDENTIAL`     | 400  | Mismatched X-API-Key / Authorization      |\n| `FORBIDDEN`                | 403  | Authenticated but not authorised          |\n| `INVALID_REQUEST`          | 400/422 | Body shape / field bounds invalid     |\n| `INVALID_JSON`             | 400  | Body wasn't JSON when JSON was required   |\n| `PARSE_ERROR`              | 400  | Mission file couldn't be parsed           |\n| `PAYLOAD_OVER_LIMIT`       | 422  | Payload exceeds drone capacity            |\n| `NO_WAYPOINTS`             | 400  | Mission has zero waypoints                |\n| `PAYLOAD_TOO_LARGE`        | 413  | Body exceeds the 16 MiB cap               |\n| `UNSUPPORTED_MEDIA_TYPE`   | 415  | Content-Type the route doesn't accept     |\n| `INVALID_IDEMPOTENCY_KEY`  | 400  | Idempotency-Key header malformed          |\n| `IDEMPOTENCY_KEY_CONFLICT` | 409  | Same key reused with different body       |\n| `RATE_LIMITED`             | 429  | Per-tenant RPS exceeded                   |\n| `QUOTA_EXCEEDED`           | 429  | Daily or monthly quota exhausted          |\n| `ENGINE_TIMEOUT`           | 504  | Engine did not finish within 30 s         |\n| `SERVICE_UNAVAILABLE`      | 503  | Transient upstream / downstream outage    |\n| `INTERNAL_ERROR`           | 500  | Uncaught exception (details in audit log) |\n\n## Audit\n\nEvery non-public request emits one structured JSON line on the dedicated\n`api_server.audit` logger. Operators route this stream to an immutable\nsink for compliance retention. Bodies are never captured; the record\nreferences the request by `request_id` and identity. See `docs/SECURITY.md`\nfor the full schema.\n","contact":{"name":"RangeSight","url":"https://github.com/ermin-code/rangesight-android"},"license":{"name":"Proprietary"},"version":"0.1.0-phase2-headwind-v5"},"paths":{"/v1/health":{"get":{"tags":["health"],"summary":"Health","operationId":"health_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/v1/ready":{"get":{"tags":["health"],"summary":"Ready","description":"Deep health probe: exercises the engine via the shared thread pool\nso a deadlocked/exhausted pool surfaces as ``engine_ok=False`` rather\nthan a silently-succeeding probe.","operationId":"ready_v1_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadyResponse"}}}}}}},"/v1/version":{"get":{"tags":["health"],"summary":"Version","operationId":"version_v1_version_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionResponse"}}}}}}},"/v1/drones":{"get":{"tags":["drones"],"summary":"List Drones","operationId":"list_drones_v1_drones_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DronesResponse"}}}}}}},"/v1/drones/{key}":{"get":{"tags":["drones"],"summary":"Get Drone","operationId":"get_drone_v1_drones__key__get","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DroneResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/missions/analyze":{"post":{"tags":["missions"],"summary":"Analyze","description":"Multipart endpoint matching how the Android app uploads missions.","operationId":"analyze_v1_missions_analyze_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_analyze_v1_missions_analyze_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/missions/analyze-geometry":{"post":{"tags":["missions"],"summary":"Analyze Geometry","description":"Pre-parsed mission JSON in, full analysis out.\n\nThe Pydantic model validates the request shape; the engine's\n``handle_geometry_request`` constructs all internal model objects so\nthis router stays free of engine-internal imports.","operationId":"analyze_geometry_v1_missions_analyze_geometry_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeGeometryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/rangeiq/analyze":{"post":{"tags":["rangeiq"],"summary":"Analyze","description":"Compute range rings for a launch point.\n\nReturns six concentric rings (0–25% return-battery thresholds) plus\na one-way maximum range ring.  Each ring is a 360-point polygon\nexpressed as ``[{\"lat\": ..., \"lon\": ...}, ...]`` ready to render on\nany mapping SDK.\n\nAlso returns per-request physics breakdown so enterprise clients can\naudit how environmental conditions affect the predicted range.","operationId":"analyze_v1_rangeiq_analyze_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RangeIQAnalyzeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RangeIQAnalyzeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/rangeiq/version":{"get":{"tags":["rangeiq"],"summary":"Version","description":"Return the RangeIQ engine version and capability flags.","operationId":"version_v1_rangeiq_version_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RangeIQVersionResponse"}}}}}}},"/v1/rangeiq/drones":{"get":{"tags":["rangeiq"],"summary":"List Drones","description":"List drone profiles available to the RangeIQ engine.","operationId":"list_drones_v1_rangeiq_drones_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DronesResponse"}}}}}}},"/v1/beta/activate":{"post":{"tags":["beta"],"summary":"Redeem a beta activation code","description":"Validates a one-time activation code and returns a device-bound RS256-signed JWT that unlocks lifetime Pro access.  The code is tied to the device fingerprint on first use; subsequent attempts from a different device are rejected.","operationId":"activate_beta_code_v1_beta_activate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/token":{"post":{"tags":["auth"],"summary":"Exchange mTLS client cert for a short-lived JWT","operationId":"issue_token_v1_auth_token_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest","default":{"engines":[],"scope":[]}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"ActivateRequest":{"properties":{"code":{"type":"string","maxLength":64,"minLength":4,"title":"Code"},"device_fingerprint":{"type":"string","maxLength":128,"minLength":16,"title":"Device Fingerprint","description":"SHA-256 hex of device ID + salt"}},"type":"object","required":["code","device_fingerprint"],"title":"ActivateRequest"},"ActivateResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"token":{"type":"string","title":"Token","default":""},"message":{"type":"string","title":"Message","default":""}},"type":"object","required":["ok"],"title":"ActivateResponse"},"AnalyzeGeometryRequest":{"properties":{"mission":{"$ref":"#/components/schemas/GeometryMission"},"home":{"$ref":"#/components/schemas/Home"},"drone":{"additionalProperties":true,"type":"object","title":"Drone"},"battery":{"$ref":"#/components/schemas/Battery"},"weather":{"$ref":"#/components/schemas/Weather"},"options":{"$ref":"#/components/schemas/Options"},"api_keys":{"$ref":"#/components/schemas/ApiKeys"}},"additionalProperties":false,"type":"object","required":["mission","home","drone"],"title":"AnalyzeGeometryRequest","description":"Pre-parsed mission as JSON; bypasses the file-format parser."},"AnalyzeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"engine_version":{"type":"string","title":"Engine Version"},"result":{"$ref":"#/components/schemas/MissionAnalysisResult"},"request_id":{"type":"string","title":"Request Id"},"engine_signature":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Engine Signature"}},"type":"object","required":["ok","engine_version","result","request_id"],"title":"AnalyzeResponse","description":"Mission analysis success envelope."},"ApiKeys":{"properties":{"wu":{"type":"string","maxLength":128,"title":"Wu","default":""},"owm":{"type":"string","maxLength":128,"title":"Owm","default":""}},"additionalProperties":false,"type":"object","title":"ApiKeys"},"Battery":{"properties":{"initial_pct":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Initial Pct","default":100.0},"rth_threshold_pct":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Rth Threshold Pct","default":25.0},"cycles":{"type":"integer","minimum":0.0,"title":"Cycles","default":0}},"additionalProperties":false,"type":"object","title":"Battery"},"Body_analyze_v1_missions_analyze_post":{"properties":{"mission_file":{"type":"string","contentMediaType":"application/octet-stream","title":"Mission File","description":"Mission file (.kmz/.kml/.gpx/.csv/.wpml)"},"request":{"type":"string","title":"Request","description":"JSON-encoded ``AnalyzeRequest`` object: home, drone, battery, weather, options, api_keys. See the AnalyzeRequest schema component for the full field list."}},"type":"object","required":["mission_file","request"],"title":"Body_analyze_v1_missions_analyze_post"},"DroneProfile":{"properties":{"key":{"type":"string","maxLength":64,"minLength":1,"title":"Key"},"cruise_speed_mps":{"type":"number","maximum":100.0,"exclusiveMinimum":0.0,"title":"Cruise Speed Mps","description":"Cruise speed in m/s."},"wind_shear_exponent":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Wind Shear Exponent","description":"Hellman exponent for altitude wind profile."},"baseline_flight_time_min":{"type":"number","maximum":300.0,"exclusiveMinimum":0.0,"title":"Baseline Flight Time Min","description":"Manufacturer-rated flight time in minutes."},"battery_reserve_frac":{"type":"number","exclusiveMaximum":1.0,"exclusiveMinimum":0.0,"title":"Battery Reserve Frac","description":"Fraction of battery kept in reserve (0–1 exclusive)."},"takeoff_penalty_frac":{"type":"number","exclusiveMaximum":1.0,"minimum":0.0,"title":"Takeoff Penalty Frac","description":"Fraction of usable battery consumed by takeoff."},"max_payload_g":{"type":"number","minimum":0.0,"title":"Max Payload G","description":"Maximum payload mass in grams (0 = no payload support)."},"efficiency_payload_slope_per_g":{"type":"number","maximum":0.0,"title":"Efficiency Payload Slope Per G","description":"Efficiency change per gram of payload (must be ≤ 0)."},"efficiency_floor":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Efficiency Floor","description":"Minimum efficiency multiplier under any load."},"hover_drain_pct_per_min":{"type":"number","maximum":20.0,"minimum":0.0,"title":"Hover Drain Pct Per Min","description":"Battery % consumed per minute of hover."}},"additionalProperties":false,"type":"object","required":["key","cruise_speed_mps","wind_shear_exponent","baseline_flight_time_min","battery_reserve_frac","takeoff_penalty_frac","max_payload_g","efficiency_payload_slope_per_g","efficiency_floor","hover_drain_pct_per_min"],"title":"DroneProfile","description":"Inline drone profile for the RangeIQ engine.\n\nAll fields must be physically plausible positive values.  Supply this\nalongside ``selected_drone_key`` (with matching ``key``) to override the\nbuilt-in catalog entry for a custom airframe."},"DroneResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"drone":{"additionalProperties":true,"type":"object","title":"Drone"},"request_id":{"type":"string","title":"Request Id"}},"type":"object","required":["ok","drone","request_id"],"title":"DroneResponse","description":"Single drone profile with the standard ok envelope."},"DronesResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"drones":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Drones"},"request_id":{"type":"string","title":"Request Id"}},"type":"object","required":["ok","drones","request_id"],"title":"DronesResponse","description":"List of drone profiles with the standard ok envelope."},"GeoPoint":{"properties":{"lat":{"type":"number","title":"Lat"},"lon":{"type":"number","title":"Lon"}},"type":"object","required":["lat","lon"],"title":"GeoPoint"},"GeometryMission":{"properties":{"waypoints":{"items":{"$ref":"#/components/schemas/WaypointModel"},"type":"array","maxItems":500,"minItems":2,"title":"Waypoints"},"altitude_reference":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Altitude Reference"},"end_action":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"End Action"},"source_format":{"type":"string","maxLength":64,"title":"Source Format","default":"geometry"},"source_filename":{"type":"string","maxLength":256,"title":"Source Filename","default":""}},"additionalProperties":false,"type":"object","required":["waypoints"],"title":"GeometryMission"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"server_version":{"type":"string","title":"Server Version"},"engine_version":{"type":"string","title":"Engine Version"}},"type":"object","required":["ok","server_version","engine_version"],"title":"HealthResponse"},"Home":{"properties":{"lat":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Lat"},"lon":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Lon"},"elevation_msl_m":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Elevation Msl M"}},"additionalProperties":false,"type":"object","required":["lat","lon"],"title":"Home"},"LegResult":{"properties":{"leg_index":{"type":"integer","title":"Leg Index"},"from_lat":{"type":"number","title":"From Lat"},"from_lon":{"type":"number","title":"From Lon"},"to_lat":{"type":"number","title":"To Lat"},"to_lon":{"type":"number","title":"To Lon"},"distance_m":{"type":"number","title":"Distance M"},"bearing_deg":{"type":"number","title":"Bearing Deg"},"altitude_from_m":{"type":"number","title":"Altitude From M"},"altitude_to_m":{"type":"number","title":"Altitude To M"},"altitude_change_m":{"type":"number","title":"Altitude Change M"},"agl_avg_m":{"type":"number","title":"Agl Avg M"},"duration_s":{"type":"number","title":"Duration S"},"battery_start_pct":{"type":"number","title":"Battery Start Pct"},"battery_end_pct":{"type":"number","title":"Battery End Pct"},"hover_cost_pct":{"type":"number","title":"Hover Cost Pct"},"battery_after_hover_pct":{"type":"number","title":"Battery After Hover Pct"},"rth_cost_pct":{"type":"number","title":"Rth Cost Pct"},"return_margin_pct":{"type":"number","title":"Return Margin Pct"},"can_complete":{"type":"boolean","title":"Can Complete"},"status":{"type":"string","title":"Status"},"color":{"type":"string","title":"Color"},"speed_mps":{"type":"number","title":"Speed Mps"},"v_out_mps":{"type":"number","title":"V Out Mps"},"wind_component_mps":{"type":"number","title":"Wind Component Mps"},"eff":{"type":"number","title":"Eff"},"weather":{"anyOf":[{"$ref":"#/components/schemas/LegWeatherResult"},{"type":"null"}]},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"},"is_final_rth_leg":{"type":"boolean","title":"Is Final Rth Leg","default":false},"dest_curve_size_m":{"type":"number","title":"Dest Curve Size M","default":0.0}},"type":"object","required":["leg_index","from_lat","from_lon","to_lat","to_lon","distance_m","bearing_deg","altitude_from_m","altitude_to_m","altitude_change_m","agl_avg_m","duration_s","battery_start_pct","battery_end_pct","hover_cost_pct","battery_after_hover_pct","rth_cost_pct","return_margin_pct","can_complete","status","color","speed_mps","v_out_mps","wind_component_mps","eff"],"title":"LegResult"},"LegWeatherResult":{"properties":{"wind_speed_mps":{"type":"number","title":"Wind Speed Mps"},"wind_dir_deg":{"type":"number","title":"Wind Dir Deg"},"gust_speed_mps":{"type":"number","title":"Gust Speed Mps"},"temp_c":{"type":"number","title":"Temp C"},"humidity_pct":{"type":"number","title":"Humidity Pct"},"source":{"type":"string","title":"Source"},"station_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Station Id"},"station_elevation_m":{"type":"number","title":"Station Elevation M","default":0.0}},"type":"object","required":["wind_speed_mps","wind_dir_deg","gust_speed_mps","temp_c","humidity_pct","source"],"title":"LegWeatherResult"},"MissionAnalysisResult":{"properties":{"home_lat":{"type":"number","title":"Home Lat"},"home_lon":{"type":"number","title":"Home Lon"},"legs":{"items":{"$ref":"#/components/schemas/LegResult"},"type":"array","title":"Legs"},"can_complete_mission":{"type":"boolean","title":"Can Complete Mission"},"mission_verdict":{"type":"string","title":"Mission Verdict"},"predicted_rth_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Rth Lat"},"predicted_rth_lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Rth Lon"},"predicted_rth_leg_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Predicted Rth Leg Index"},"predicted_rth_fraction":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Rth Fraction"},"battery_swap_before_legs":{"items":{"type":"integer"},"type":"array","title":"Battery Swap Before Legs"},"total_distance_m":{"type":"number","title":"Total Distance M"},"total_duration_s":{"type":"number","title":"Total Duration S"},"initial_battery_pct":{"type":"number","title":"Initial Battery Pct"},"final_battery_pct":{"type":"number","title":"Final Battery Pct"},"hover_at_final_s":{"type":"number","title":"Hover At Final S","default":0.0},"predicted_landing_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Landing Pct"},"predicted_crash_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Crash Lat"},"predicted_crash_lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Crash Lon"},"predicted_crash_leg_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Predicted Crash Leg Index"},"predicted_crash_fraction":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Predicted Crash Fraction"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"},"data_sources":{"additionalProperties":true,"type":"object","title":"Data Sources"},"path_polyline":{"items":{"items":{"type":"number"},"type":"array"},"type":"array","title":"Path Polyline"}},"type":"object","required":["home_lat","home_lon","legs","can_complete_mission","mission_verdict","total_distance_m","total_duration_s","initial_battery_pct","final_battery_pct"],"title":"MissionAnalysisResult","description":"Typed mirror of ``rangeiq_plus.models.MissionAnalysis``."},"OnewayRing":{"properties":{"label":{"type":"string","title":"Label"},"color":{"type":"string","title":"Color"},"polygon":{"items":{"$ref":"#/components/schemas/GeoPoint"},"type":"array","title":"Polygon"}},"type":"object","required":["label","color","polygon"],"title":"OnewayRing"},"Options":{"properties":{"payload_g":{"type":"number","minimum":0.0,"title":"Payload G","default":0.0},"alt_penalty":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"title":"Alt Penalty","default":0.98},"fine_tune_factor":{"type":"number","maximum":10.0,"exclusiveMinimum":0.0,"title":"Fine Tune Factor","default":1.0},"preflight_hover_s":{"type":"number","minimum":0.0,"title":"Preflight Hover S","default":0.0},"global_cruise_mps":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Global Cruise Mps"},"multi_battery":{"type":"boolean","title":"Multi Battery","default":false},"end_action":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"End Action"},"altitude_reference_override":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Altitude Reference Override"},"force_global_cruise":{"type":"boolean","title":"Force Global Cruise","default":false},"force_global_altitude":{"type":"boolean","title":"Force Global Altitude","default":false},"global_altitude_m":{"anyOf":[{"type":"number","maximum":10000.0,"minimum":-500.0},{"type":"null"}],"title":"Global Altitude M"},"smart_rth_enabled":{"type":"boolean","title":"Smart Rth Enabled","default":true},"use_live_weather":{"type":"boolean","title":"Use Live Weather","default":false}},"additionalProperties":false,"type":"object","title":"Options"},"RangeIQAnalyzeRequest":{"properties":{"user_lat":{"type":"number","maximum":89.99,"minimum":-89.99,"title":"User Lat"},"user_lon":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"User Lon"},"unit_mode":{"type":"string","enum":["metric","imperial"],"title":"Unit Mode","default":"metric"},"selected_drone_key":{"type":"string","maxLength":64,"title":"Selected Drone Key","description":"Key from the /v1/rangeiq/drones catalog. If empty and no drone_profile is provided, built-in Mini 4 Pro defaults are used.","default":""},"drone_profile":{"anyOf":[{"$ref":"#/components/schemas/DroneProfile"},{"type":"null"}],"description":"Inline drone profile that overrides the catalog entry. The 'key' field inside the profile must match selected_drone_key."},"wind_speed":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Wind Speed","default":0.0},"gust_speed":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Gust Speed","default":0.0},"wind_direction":{"type":"number","maximum":359.99,"minimum":0.0,"title":"Wind Direction","default":0.0},"temperature":{"type":"number","title":"Temperature","description":"Ambient temperature in the caller's unit_mode (°C or °F).","default":20.0},"humidity":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Humidity","default":50.0},"altitude":{"type":"number","minimum":0.0,"title":"Altitude","description":"Flight altitude above ground in the caller's unit_mode (m or ft).","default":120.0},"cruise_speed":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Cruise Speed","description":"Cruise speed in the caller's unit_mode (kph or mph). Omit or set null to use the drone profile's default."},"initial_battery":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Initial Battery","default":85.0},"hover_time_min":{"type":"number","minimum":0.0,"title":"Hover Time Min","description":"Hover duration in minutes before the outbound flight.","default":0.0},"elevation_penalty":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"title":"Elevation Penalty","default":0.98},"use_battery_cycles":{"type":"boolean","title":"Use Battery Cycles","default":false},"battery_cycles":{"type":"integer","minimum":0.0,"title":"Battery Cycles","default":0},"use_payload":{"type":"boolean","title":"Use Payload","default":false},"payload_weight":{"type":"number","minimum":0.0,"title":"Payload Weight","default":0.0}},"additionalProperties":false,"type":"object","required":["user_lat","user_lon"],"title":"RangeIQAnalyzeRequest","description":"Point-to-point range analysis request for the RangeIQ engine.\n\nWeather and flight inputs follow the same unit conventions as the\nAndroid app: imperial (mph / °F / ft) or metric (kph / °C / m)\nselected by ``unit_mode``.  All values are normalised to SI internally\nbefore any physics is computed, so results are unit-independent."},"RangeIQAnalyzeResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"engine_version":{"type":"string","title":"Engine Version"},"result":{"$ref":"#/components/schemas/RangeIQResult"},"request_id":{"type":"string","title":"Request Id"}},"type":"object","required":["ok","engine_version","result","request_id"],"title":"RangeIQAnalyzeResponse","description":"Range-ring analysis success envelope."},"RangeIQInputsSI":{"properties":{"wind_speed_mps":{"type":"number","title":"Wind Speed Mps"},"gust_speed_mps":{"type":"number","title":"Gust Speed Mps"},"wind_dir_deg":{"type":"number","title":"Wind Dir Deg"},"temp_c":{"type":"number","title":"Temp C"},"humidity_pct":{"type":"number","title":"Humidity Pct"},"altitude_m":{"type":"number","title":"Altitude M"},"cruise_mps":{"type":"number","title":"Cruise Mps"},"initial_battery_pct":{"type":"number","title":"Initial Battery Pct"},"hover_time_min":{"type":"number","title":"Hover Time Min"}},"type":"object","required":["wind_speed_mps","gust_speed_mps","wind_dir_deg","temp_c","humidity_pct","altitude_m","cruise_mps","initial_battery_pct","hover_time_min"],"title":"RangeIQInputsSI","description":"Weather + flight inputs normalised to SI for auditability."},"RangeIQPhysics":{"properties":{"wind_at_altitude_mps":{"type":"number","title":"Wind At Altitude Mps"},"wind_shear_factor":{"type":"number","title":"Wind Shear Factor"},"temp_efficiency":{"type":"number","title":"Temp Efficiency"},"humidity_efficiency":{"type":"number","title":"Humidity Efficiency"},"gust_efficiency":{"type":"number","title":"Gust Efficiency"},"cruise_efficiency":{"type":"number","title":"Cruise Efficiency"},"alt_penalty":{"type":"number","title":"Alt Penalty"},"cycle_efficiency":{"type":"number","title":"Cycle Efficiency"},"payload_efficiency":{"type":"number","title":"Payload Efficiency"},"combined_efficiency":{"type":"number","title":"Combined Efficiency"},"climb_battery_cost_pct":{"type":"number","title":"Climb Battery Cost Pct"},"hover_battery_cost_pct":{"type":"number","title":"Hover Battery Cost Pct"},"battery_for_flight_pct":{"type":"number","title":"Battery For Flight Pct"},"base_flight_time_s":{"type":"number","title":"Base Flight Time S"}},"type":"object","required":["wind_at_altitude_mps","wind_shear_factor","temp_efficiency","humidity_efficiency","gust_efficiency","cruise_efficiency","alt_penalty","cycle_efficiency","payload_efficiency","combined_efficiency","climb_battery_cost_pct","hover_battery_cost_pct","battery_for_flight_pct","base_flight_time_s"],"title":"RangeIQPhysics","description":"Per-request physics breakdown for enterprise auditability."},"RangeIQResult":{"properties":{"center":{"$ref":"#/components/schemas/GeoPoint"},"unit_mode":{"type":"string","title":"Unit Mode"},"drone_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Drone Key"},"inputs_si":{"$ref":"#/components/schemas/RangeIQInputsSI"},"physics":{"$ref":"#/components/schemas/RangeIQPhysics"},"rings":{"items":{"$ref":"#/components/schemas/RangeRing"},"type":"array","title":"Rings"},"oneway_ring":{"$ref":"#/components/schemas/OnewayRing"}},"type":"object","required":["center","unit_mode","drone_key","inputs_si","physics","rings","oneway_ring"],"title":"RangeIQResult"},"RangeIQVersionResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"engine":{"type":"string","title":"Engine"},"engine_version":{"type":"string","title":"Engine Version"},"request_id":{"type":"string","title":"Request Id"}},"type":"object","required":["ok","engine","engine_version","request_id"],"title":"RangeIQVersionResponse","description":"Version endpoint response for the RangeIQ engine."},"RangeRing":{"properties":{"battery_threshold_pct":{"type":"integer","title":"Battery Threshold Pct"},"label":{"type":"string","title":"Label"},"color":{"type":"string","title":"Color"},"status":{"type":"string","title":"Status"},"polygon":{"items":{"$ref":"#/components/schemas/GeoPoint"},"type":"array","title":"Polygon"}},"type":"object","required":["battery_threshold_pct","label","color","status","polygon"],"title":"RangeRing"},"ReadyResponse":{"properties":{"ok":{"type":"boolean","title":"Ok"},"server_version":{"type":"string","title":"Server Version"},"engine_version":{"type":"string","title":"Engine Version"},"engine_ok":{"type":"boolean","title":"Engine Ok"},"request_id":{"type":"string","title":"Request Id"}},"type":"object","required":["ok","server_version","engine_version","engine_ok","request_id"],"title":"ReadyResponse","description":"Deep-health probe: exercises the engine to confirm it's importable\nand the thread pool is responsive. Separate from /v1/health so startup\nprobes don't pay the engine-warm-up cost on every call."},"TokenRequest":{"properties":{"engines":{"items":{"type":"string"},"type":"array","maxItems":8,"title":"Engines"},"scope":{"items":{"type":"string"},"type":"array","maxItems":32,"title":"Scope"}},"additionalProperties":false,"type":"object","title":"TokenRequest","description":"Optional scope-narrowing for the issued token.\n\nClients may request a token with FEWER engines or scopes than their\ncert is configured for; they may not request MORE. Empty / omitted\nmeans \"give me everything my cert is entitled to\" — the simplest\nintegration path."},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"Bearer"},"expires_in":{"type":"integer","title":"Expires In"},"issued_at":{"type":"integer","title":"Issued At"},"tenant":{"type":"string","title":"Tenant"},"engines":{"items":{"type":"string"},"type":"array","title":"Engines"},"scope":{"items":{"type":"string"},"type":"array","title":"Scope"},"request_id":{"type":"string","title":"Request Id"}},"type":"object","required":["access_token","expires_in","issued_at","tenant","engines","scope","request_id"],"title":"TokenResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VersionResponse":{"properties":{"server_version":{"type":"string","title":"Server Version"},"engine_version":{"type":"string","title":"Engine Version"}},"type":"object","required":["server_version","engine_version"],"title":"VersionResponse"},"WaypointAction":{"properties":{"action_type":{"type":"string","maxLength":64,"title":"Action Type"},"duration_s":{"type":"number","minimum":0.0,"title":"Duration S","default":0.0}},"additionalProperties":false,"type":"object","required":["action_type"],"title":"WaypointAction"},"WaypointModel":{"properties":{"lat":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Lat"},"lon":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Lon"},"altitude_m":{"type":"number","maximum":10000.0,"minimum":-500.0,"title":"Altitude M"},"speed_mps":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Speed Mps"},"hover_time_s":{"type":"number","maximum":3600.0,"minimum":0.0,"title":"Hover Time S","default":0.0},"actions":{"items":{"$ref":"#/components/schemas/WaypointAction"},"type":"array","maxItems":20,"title":"Actions"},"curve_size":{"type":"number","maximum":1000.0,"minimum":0.0,"title":"Curve Size","default":0.0}},"additionalProperties":false,"type":"object","required":["lat","lon","altitude_m"],"title":"WaypointModel"},"Weather":{"properties":{"wind_speed_mps":{"type":"number","minimum":0.0,"title":"Wind Speed Mps","default":0.0},"wind_dir_deg":{"type":"number","maximum":360.0,"minimum":0.0,"title":"Wind Dir Deg","default":0.0},"gust_speed_mps":{"type":"number","minimum":0.0,"title":"Gust Speed Mps","default":0.0},"temp_c":{"type":"number","title":"Temp C","default":20.0},"humidity_pct":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Humidity Pct","default":50.0},"source":{"type":"string","maxLength":64,"title":"Source","default":"request"}},"additionalProperties":false,"type":"object","title":"Weather"}},"securitySchemes":{"BearerJWT":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"ES256 JWT bound to the issuing client's mTLS cert (RFC 8705 cnf.x5t#S256). Obtain via POST /v1/auth/token."},"ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Long-lived API key. Deprecated in favour of mTLS + JWT; scheduled for removal once all integrators are migrated."},"MutualTLS":{"type":"mutualTLS","description":"Client certificate validated at the load balancer. The cert's SHA-256 fingerprint maps to a tenant via the operator-configured RANGEIQ_MTLS_IDENTITIES allow-list. Required for /v1/auth/token; required for all paths when RANGEIQ_MTLS_MODE=enforced."}}},"tags":[{"name":"health","description":"Liveness, version, and probe endpoints."},{"name":"auth","description":"Token issuance (mTLS → JWT exchange)."},{"name":"drones","description":"Built-in drone profile catalog (shared)."},{"name":"missions","description":"RangeIQ+: multi-leg waypoint mission analysis."},{"name":"rangeiq","description":"RangeIQ: point-to-point range-ring analysis."}],"security":[{"BearerJWT":[]},{"MutualTLS":[]},{"ApiKeyHeader":[]}]}