Establish a WebSocket for an airport
curl --request GET \
--url https://v2.stopbars.com/connectimport requests
url = "https://v2.stopbars.com/connect"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://v2.stopbars.com/connect', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));RealTime
Establish a WebSocket for an airport
Performs a WebSocket upgrade to stream real-time airport state. Requires:
- GET with
Upgrade: websocket airport(ICAO, 4 chars) and an API key via either:keyquery parameter, orAuthorization: Bearer <API key>header The Durable Object validates the API key from the query parameter or Bearer header.
GET
/
connect
Establish a WebSocket for an airport
curl --request GET \
--url https://v2.stopbars.com/connectimport requests
url = "https://v2.stopbars.com/connect"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://v2.stopbars.com/connect', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));Establish a session
- Connect to
/connectwithairportand your API key. - Wait for
INITIAL_STATE(this confirms your role and current airport state). - Keep the connection alive by sending
HEARTBEATregularly.
HEARTBEAT every 60 seconds and closes idle sessions after ~70 seconds without inbound client messages.
Packet envelope
Every packet uses this top-level shape:{
"type": "STATE_UPDATE",
"airport": "YSSY",
"data": {},
"timestamp": 1739400000000
}
typeis required.airportis optional on most client packets (server uses your connected airport if omitted).timestampis optional on client packets and server-populated on outbound packets.
Packet permissions by role
| Packet type | Who can send | What happens |
|---|---|---|
HEARTBEAT | controller, pilot, observer | Server replies with HEARTBEAT_ACK |
GET_STATE | controller, pilot, observer | Server replies with STATE_SNAPSHOT |
STATE_UPDATE | controller only | Broadcast to same-airport clients (except sender) |
MULTI_STATE_UPDATE | controller only | Broadcast to same-airport clients (except sender) |
SHARED_STATE_UPDATE | controller only | Broadcast to same-airport clients (including sender) |
STOPBAR_CROSSING | pilot only | Broadcast to same-airport controllers |
CLOSE | controller, pilot, observer | Server closes the session gracefully |
Client packets you send
HEARTBEAT
{ "type": "HEARTBEAT" }
GET_STATE
{ "type": "GET_STATE" }
STATE_UPDATE (controller only)
{
"type": "STATE_UPDATE",
"data": {
"objectId": "BARS_7K2QH",
"state": false
}
}
MULTI_STATE_UPDATE (controller only)
{
"type": "MULTI_STATE_UPDATE",
"data": {
"updates": [
{ "objectId": "BARS_7K2QH", "state": false },
{ "objectId": "BARS_8R1LP", "state": true }
]
}
}
SHARED_STATE_UPDATE (controller only)
{
"type": "SHARED_STATE_UPDATE",
"data": {
"sharedStatePatch": {
"profile": "default",
"nodes": {
"TWY_A1": true,
"TWY_B2": false,
"RWY_09L_HOLD": true
},
"blocks": {
"BLOCK_A": "clear",
"BLOCK_B": "relax",
"BLOCK_C": { "route": ["TWY_A1", "TWY_B2"] }
}
}
}
}
STOPBAR_CROSSING (pilot only)
{
"type": "STOPBAR_CROSSING",
"data": {
"objectId": "BARS_7K2QH"
}
}
CLOSE
{ "type": "CLOSE" }
Server packets you should handle
INITIAL_STATEimmediately after connectHEARTBEATevery 60 secondsHEARTBEAT_ACKin response to clientHEARTBEATSTATE_SNAPSHOTin response toGET_STATESTATE_UPDATEwhen a controller changes one object stateMULTI_STATE_UPDATEwhen a controller sends a batch state changeCONTROLLER_CONNECT/CONTROLLER_DISCONNECTfor controller presence changesSHARED_STATE_UPDATEwhen shared state changesSTOPBAR_CROSSING(controllers only) when a pilot crosses a stopbarERRORwhen validation/processing fails
Validation and limits (avoid malformed packets)
objectIdmust match^[a-zA-Z0-9_-]+$.STATE_UPDATEmust includedata.objectIdand one ofdata.stateordata.patch.MULTI_STATE_UPDATEmust includedata.updates(maximum200updates).SHARED_STATE_UPDATEmust includedata.sharedStatePatchobject (maximum10,240serialized characters).- Maximum packet size is
50,000characters. - Unknown packet
typevalues are rejected.
Was this page helpful?
