ref_api_contracts.org
1 #+title: API Contracts — Reference 2 #+author: Claude Code 3 #+date: [2026-04-01 Wed] 4 #+startup: indent 5 #+options: toc:t num:t 6 7 * Overview 8 9 This document describes the exact data shapes for the three APIs consumed by the mission control dashboard: OpenSky Network (polling), the mission WebSocket server, and MBTA (SSE). 10 11 * OpenSky Network — REST (Polling) 12 13 ** Endpoint 14 15 =GET https://opensky-network.org/api/states/all= 16 17 ** Query Parameters 18 19 | Parameter | Type | Required | Description | 20 |-----------+-------+----------+---------------------------------------| 21 | lamin | float | No | Minimum latitude (bounding box) | 22 | lamax | float | No | Maximum latitude (bounding box) | 23 | lomin | float | No | Minimum longitude (bounding box) | 24 | lomax | float | No | Maximum longitude (bounding box) | 25 | icao24 | string | No | Filter by transponder address(es) | 26 | time | int | No | Unix timestamp (authenticated only) | 27 | extended | int | No | Set to 1 for aircraft category | 28 29 All four bounding box parameters must be provided together or not at all. 30 31 ** Response Shape 32 33 #+begin_src json 34 { 35 "time": 1711929600, 36 "states": [ 37 ["3c6444", "DLH1234 ", "Germany", 1711929595, 1711929600, 38 8.5432, 47.1234, 10668.0, false, 230.5, 270.0, -0.5, 39 null, 10972.0, "1000", false, 0, 0] 40 ] 41 } 42 #+end_src 43 44 ** State Vector Fields 45 46 | Index | Field | Type | Nullable | Description | 47 |-------+-----------------+----------+----------+---------------------------------------------| 48 | 0 | icao24 | string | No | Transponder address (hex) | 49 | 1 | callsign | string | Yes | 8-char callsign, may have trailing spaces | 50 | 2 | origin_country | string | No | Country inferred from ICAO address | 51 | 3 | time_position | int | Yes | Last position update (Unix timestamp) | 52 | 4 | last_contact | int | Yes | Last message received (Unix timestamp) | 53 | 5 | longitude | float | Yes | WGS-84 decimal degrees | 54 | 6 | latitude | float | Yes | WGS-84 decimal degrees | 55 | 7 | baro_altitude | float | Yes | Barometric altitude in metres | 56 | 8 | on_ground | boolean | No | Whether aircraft is on ground | 57 | 9 | velocity | float | Yes | Ground speed in m/s | 58 | 10 | true_track | float | Yes | Heading in degrees clockwise from north | 59 | 11 | vertical_rate | float | Yes | Climb/descent rate in m/s | 60 | 12 | sensors | int[] | Yes | Receiver IDs (null if unfiltered) | 61 | 13 | geo_altitude | float | Yes | Geometric altitude in metres | 62 | 14 | squawk | string | Yes | Transponder code | 63 | 15 | spi | boolean | No | Special purpose indicator | 64 | 16 | position_source | int | No | 0=ADS-B, 1=ASTERIX, 2=MLAT, 3=FLARM | 65 | 17 | category | int | No | Aircraft category (0-20) | 66 67 ** Rate Limits 68 69 | Tier | Resolution | Credits/day | 70 |---------------+------------+-------------| 71 | Anonymous | 10 seconds | 400 | 72 | Authenticated | 5 seconds | 4,000+ | 73 74 Credit cost depends on bounding box area: 75 76 | Area | Credits | 77 |-------------+---------| 78 | ≤25 sq° | 1 | 79 | 25-100 sq° | 2 | 80 | 100-400 sq° | 3 | 81 | >400 sq° | 4 | 82 83 ** Authentication 84 85 Anonymous access works for basic polling. Authenticated access requires OAuth2: 86 87 1. Register at https://opensky-network.org/login 88 2. Obtain =client_id= and =client_secret= from account settings 89 3. POST to token endpoint to get Bearer token 90 4. Pass =Authorization: Bearer {TOKEN}= header 91 92 * Mission WebSocket Server — WebSocket 93 94 ** Connection 95 96 =ws://YOUR_VPS_IP:8080= 97 98 No authentication. Connection receives a snapshot immediately, then updates every second. 99 100 ** Message Types 101 102 *** Snapshot (sent on connection) 103 104 #+begin_src json 105 { 106 "type": "snapshot", 107 "assets": [ ... ], 108 "alerts": [ ... ] 109 } 110 #+end_src 111 112 *** Update (sent every second) 113 114 #+begin_src json 115 { 116 "type": "update", 117 "assets": [ ... ], 118 "alerts": [ ... ] 119 } 120 #+end_src 121 122 ** Asset Shape 123 124 | Field | Type | Description | 125 |------------+----------------------------------------------+--------------------------------------| 126 | id | string | Unique identifier (e.g. "ASSET-001") | 127 | type | "drone" \vert "vehicle" \vert "sensor" | Asset classification | 128 | status | "active" \vert "idle" \vert "alert" | Current operational status | 129 | latitude | number | WGS-84 decimal degrees | 130 | longitude | number | WGS-84 decimal degrees | 131 | altitude | number | Altitude in metres | 132 | speed | number | Ground speed (arbitrary units) | 133 | heading | number | Degrees clockwise from north | 134 | battery | number | Battery percentage (0-100) | 135 | lastUpdate | number | Unix timestamp in milliseconds | 136 137 ** Alert Shape 138 139 | Field | Type | Description | 140 |-----------+--------------------------------------+------------------------------| 141 | id | string | Unique alert identifier | 142 | assetId | string | ID of the triggering asset | 143 | message | string | Human-readable alert message | 144 | severity | "warning" \vert "critical" | Alert severity level | 145 | timestamp | number | Unix timestamp in milliseconds | 146 | active | boolean | Whether alert is still active | 147 148 * MBTA — Server-Sent Events 149 150 ** Endpoint 151 152 =GET https://api-v3.mbta.com/vehicles?filter[route_type]=3&api_key={KEY}= 153 154 Request with =Accept: text/event-stream= to receive SSE stream. 155 156 ** Authentication 157 158 Free API key required. Register at https://api-v3.mbta.com/. 159 160 Without key: 20 requests/minute. With key: significantly higher. 161 162 ** SSE Event Types 163 164 | Event | Payload | Description | 165 |--------+-----------------+---------------------------------------------| 166 | reset | array of vehicles | Full state replacement (sent on connection) | 167 | update | single vehicle | One vehicle changed position/status | 168 | add | single vehicle | New vehicle appeared | 169 | remove | vehicle ID | Vehicle removed from service | 170 171 ** Vehicle Shape (JSON:API format) 172 173 #+begin_src json 174 { 175 "type": "vehicle", 176 "id": "y1234", 177 "attributes": { 178 "bearing": 180, 179 "current_status": "IN_TRANSIT_TO", 180 "current_stop_sequence": 12, 181 "direction_id": 0, 182 "label": "1234", 183 "latitude": 42.3601, 184 "longitude": -71.0589, 185 "occupancy_status": "FEW_SEATS_AVAILABLE", 186 "revenue": "REVENUE", 187 "speed": 8.5, 188 "updated_at": "2026-04-01T12:34:56-04:00" 189 }, 190 "relationships": { 191 "route": { "data": { "id": "1", "type": "route" } }, 192 "stop": { "data": { "id": "110", "type": "stop" } }, 193 "trip": { "data": { "id": "54321", "type": "trip" } } 194 } 195 } 196 #+end_src 197 198 ** Vehicle Attributes 199 200 | Field | Type | Nullable | Description | 201 |------------------------+---------+----------+-------------------------------------| 202 | bearing | number | Yes | Direction of travel in degrees | 203 | current_status | string | No | IN_TRANSIT_TO, STOPPED_AT, INCOMING_AT | 204 | current_stop_sequence | number | Yes | Stop index in current trip | 205 | direction_id | number | Yes | 0 or 1 (outbound/inbound) | 206 | label | string | No | Vehicle display label | 207 | latitude | number | No | WGS-84 decimal degrees | 208 | longitude | number | No | WGS-84 decimal degrees | 209 | occupancy_status | string | Yes | Crowding level | 210 | speed | number | Yes | Speed in m/s | 211 | updated_at | string | No | ISO 8601 timestamp | 212 213 ** Route Type Filter Values 214 215 | Value | Mode | 216 |-------+-------------| 217 | 0 | Light Rail | 218 | 1 | Heavy Rail | 219 | 2 | Commuter Rail | 220 | 3 | Bus | 221 | 4 | Ferry | 222 223 * Self-Hosted OSM Tile Server — Raster Tiles (optional upgrade) 224 225 ** Endpoint 226 227 =GET https://<vps-domain>/tile/{z}/{x}/{y}.png= 228 229 Served via Caddy reverse proxy to the =overv/openstreetmap-tile-server= Docker 230 container. See [[file:../decisions/adr_002_map_tile_provider.org::*Self-hosted tile server (future upgrade)][ADR-002]] for setup rationale. 231 232 ** URL Pattern 233 234 | Parameter | Type | Description | 235 |-----------+------+------------------------------------------| 236 | z | int | Zoom level (0-19) | 237 | x | int | Tile column | 238 | y | int | Tile row | 239 240 Tiles are 256x256 PNG images following the standard [[https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames][slippy map]] tile naming 241 convention. 242 243 ** Response 244 245 - =200 OK= with =Content-Type: image/png= — a 256x256 tile image 246 - =404 Not Found= — tile outside the imported geographic region 247 - =500 Internal Server Error= — rendering failure (check container logs) 248 249 ** Authentication 250 251 None. The tile server is behind Caddy on the same domain as the WebSocket 252 server, accessed via the =/tile/= path prefix. 253 254 ** Attribution 255 256 Required regardless of who serves the tiles: 257 258 =© OpenStreetMap contributors= 259 260 The data license (ODbL) applies to the underlying OpenStreetMap data, not the 261 tile server. 262 263 ** Rate Limits 264 265 None (limited only by VPS hardware). The Docker image includes =mod_tile='s 266 built-in tile caching. 267 268 ** Fallback 269 270 When the self-hosted tile server is unreachable, the dashboard's resilient tile 271 layer component falls back to the default OSM public tiles at: 272 273 =https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png= 274 275 * See Also 276 277 - [[file:../tutorials/tutorial_mission_control_dashboard.org][Tutorial: Build a Mission Control Dashboard]] 278 - [[file:../explanation/explanation_realtime_data_patterns.org][Explanation: Real-Time Data Patterns]] 279 - [[file:../howto/howto_connect_opensky.org][How-to: Connect to OpenSky Network]] 280 - [[file:../decisions/adr_002_map_tile_provider.org][ADR-002: Map Tile Provider]]