This is the lookup page for the entire external contract of the SkyHub Gateway Service: every HTTP route across the 12 Flask blueprints plus the Socket.IO telemetry namespace. It is intentionally terse and table-heavy. For the narrative — auth flows, body semantics, and the reasoning behind each group — follow the links into /gateway/api/*.
Everything HTTP lives under the base path /api/v1. All 12 blueprints register with url_prefix="/api/v1/" in src/main.py:222-233, and every route rule also begins with /, so the effective base is /api/v1 (the doubled slash collapses). Interactive Swagger 2.0 UI is served at GET /api/docs/ and the raw spec at GET /apispec.json (both public, generated by flasgger).

Auth models at a glance

Three distinct authentication models coexist on the same app. The table below defines the short tags used in every endpoint table on this page. See /gateway/api/overview and /architecture/auth-and-security for the full model.
VPN-IP is the sole auth for drone callbacks (/drone/pull, /executions/{start,complete,log,current}, /authenticate_upload, /complete_upload). check_vpn_ip trusts remote_addr / X-Real-IP / X-Forwarded-For beginning with 10.71., plus the X-Drone-IP header (SKYHUB_SITL_* container names when ENABLE_SITL, or local IPs in dev). There is no JWT on these routes — anything that can set those headers behind the proxy is trusted. Preserve the jumphost/nginx source-IP handling described in /gateway/security/vpn-middleware-jumphost.

Response envelope (read this before parsing bodies)

Two envelope conventions coexist and you must handle both:
  • Newer routes (auth, executions, assets, billing, calendar, user) use get_success_response/get_error_response from src/utils/common_helper.py:
    • success → {"success": true, "data": ..., "message": ...}
    • error → {"success": false, "error": {"code": <int>, "message": ...}} with the same HTTP status.
  • Older routes (drone, mission, geofence) return raw jsonify(...) with bare fields and frequently return 201 for reads and updates (e.g. GET /drones, PATCH /mission/point/{id}).
  • vpn returns raw jsonify(...) with bare fields (e.g. {url}, {status}, {droneId: {is_active}}) — no success/data envelope. See the vpn_routes table below.
Do not “normalize” these status codes in a refactor — the current Dashboard depends on the exact codes (including the 201-for-reads). Known quirks are flagged inline in the tables below.

Authentication — auth_routes

src/routes/auth_routes.py · full lifecycle in /gateway/api/authentication. Access tokens expire in 10 min, refresh tokens in 12 hr (src/main.py).
BLOCKLIST is a per-process set() marked TODO (auth_routes.py:17). Logout revocation is not persisted and not shared across gunicorn workers, and the Socket.IO handshake never consults it — a logged-out token still works on other workers and on Socket.IO.

Current user — user_routes

src/routes/user_routes.py.

Drones & control actions — drone_routes

src/routes/drone_routes.py (~1700 lines). Narrative + body detail in /gateway/api/drones-and-actions; the underlying dispatch is /gateway/services/drone-control. All /drone/action/* commands take {drone_id} at minimum and return 200/500 unless noted.

CRUD & connection

Flight commands (/drone/action/*)

Params & drone bootstrap

Executions, logs & report emails — execution_routes

src/routes/execution_routes.py. Split by auth: drone/agent callbacks are VPN-IP, UI reads are JWT. Detail in /gateway/api/executions-assets-reports and /gateway/services/executions-reports.
The report is deferred if assets are still uploading: complete_execution marks it pending, and complete_upload dispatches it once the last pending asset lands. See the pipeline in /gateway/services/executions-reports.

Assets & media — asset_routes

src/routes/asset_routes.py. Upload pair is VPN-IP (drone), listing/download/delete are JWT. S3 layout and HLS rewriting in /gateway/services/assets-archives.
asset_routes imports get_*_response from utils.common_helper while most routes import from src.utils.common_helper — two module paths for the same helper; keep both importable.

Missions — mission_routes

src/routes/mission_routes.py. MAVLink waypoint format detail in /gateway/data/mission-waypoint-format.

Geofences — geofence_routes

src/routes/geofence_routes.py. Registered in main.py even though the CLAUDE.md overview omits geofences. Same MAVLink-fence semantics as /gateway/data/mission-waypoint-format.

Video rooms — video_room_routes

src/routes/video_room_routes.py. Janus room + on-drone control in /gateway/services/video-rooms.

VPN config & status — vpn_routes

src/routes/vpn_routes.py. Presigned WireGuard config URLs + status proxy. See /gateway/security/vpn-middleware-jumphost and /ecosystem/user-vpn.

Billing — billing_routes

src/routes/billing_routes.py. Stripe integration; pricing and gating in /gateway/services/billing.

Calendar — calendar_routes

src/routes/calendar_routes.py. ISO-8601 datetimes normalized to UTC; recurrence via iCal RRULE.

Isaac Sim EC2 control — isaac_sim_routes

src/routes/isaac_sim_routes.py. Controls a single EC2 instance; IsaacSimService() is instantiated at module import (line 16). See /gateway/services/platform-services.

Socket.IO telemetry namespace

Real-time telemetry rides a single Flask-SocketIO namespace (gevent async mode). Handlers live in src/routes/socket_routes.py; emission is DroneControlService._emit_* in src/service/drone_control_service.py. Full narrative in /gateway/api/telemetry-socketio and the client side in /dashboard/telemetry-socketio. Handshake: connect with the JWT in the query param ?token=<jwt> (e.g. wss://<host>/socket.io/?token=<access_token>). handle_connect decodes HS256, reads sub, and stores it as session["user_id"].
Two Socket.IO security facts a refactor must not silently change:
  1. Rooms are drone-scoped, not user-scoped. Subscribe joins f"drone_{drone_id}_{stream_type}" (socket_routes.py:123) and emission targets f"drone_{drone_id}_{suffix}". There is no ownership check on subscribe, so any authenticated socket that knows a drone_id receives that drone’s telemetry. The CLAUDE.md user_{user_id}_drone_{drone_id}_{stream_type} scheme is wrong — trust the real room strings.
  2. The handshake reads the token only from request.args["token"] and never consults the HTTP BLOCKLIST, so a logged-out token still authenticates on Socket.IO.

Client → Server events

Server → Client events

Only dashboard delivers meaningful data in practice. Selecting it fans out to GPS, GPS_RAW, home position, rel-alt, /rosout logs, VFR_HUD, IMU, and /diagnostics, and the server injects a computed yaw into the GPS payload (VFR_HUD heading first, else IMU quaternion → compass). The single-stream rooms (gps/logs/relalt/system) exist but are effectively dead in the current UI.

Status-code & contract quirks (do not “fix” blindly)

GET /drones, GET /drones/mission, PATCH /mission/point/{id}, DELETE /mission/point/{id}, PATCH /mission/{id}/points all return 201. The Dashboard expects these — changing them breaks the UI.
POST /drone returns 402 when over the subscription limit (legitimate), but POST /video_room also returns 402 when the auth token is missing — an unusual use of Payment Required for an auth error.
POST /drone/action/arm and POST /drone/action/arm_drone are functionally identical (both call arm_drone and pre-push the SITL video room). Disarm is only POST /drone/action/disarm_drone.
Swagger documents video-source as [main, thermal], but the code accepts only ["CAMERA", "TEST"] (drone_utils.VIDEO_ROOM_SOURCES); anything else → 400. Trust the code.
GET /user calls get_error_response("User not found", 404) with code/message reversed. Because get_error_response(code, message) returns (..., code), the 404/500 branches return a string status and raise — a latent bug to fix deliberately, not by accident.
create_drone, delete_drone, create_video_room, and restart_video_stream are Flask async def (require flask[async]/gevent); restart_video_stream mixes a sync time.sleep.

Where to go next

HTTP API Overview & Auth Models

Base path, blueprint registration, envelope, and the three auth models in narrative form.

Drone Management & Actions

Body semantics for every /drone/action/* command.

Socket.IO Telemetry

Stream types, dashboard fan-out, and room-naming security.

Environment Variables

ENABLE_REGISTRATION, ENABLE_SITL, JWT/Socket.IO tuning, and the rest.

Database Schema

The models behind these payloads.

Redis Channels & MAVLink Ports

The out-of-band agent/telemetry channels.