/ doc / reference / ref_api_contracts.org
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]]