Skip to main content
GET
/
connect
Establish a WebSocket for an airport
curl --request GET \
  --url https://v2.stopbars.com/connect

Establish a session

  1. Connect to /connect with airport and your API key.
  2. Wait for INITIAL_STATE (this confirms your role and current airport state).
  3. Keep the connection alive by sending HEARTBEAT regularly.
The server sends 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
}
  • type is required.
  • airport is optional on most client packets (server uses your connected airport if omitted).
  • timestamp is optional on client packets and server-populated on outbound packets.

Packet permissions by role

Packet typeWho can sendWhat happens
HEARTBEATcontroller, pilot, observerServer replies with HEARTBEAT_ACK
GET_STATEcontroller, pilot, observerServer replies with STATE_SNAPSHOT
STATE_UPDATEcontroller onlyBroadcast to same-airport clients (except sender)
MULTI_STATE_UPDATEcontroller onlyBroadcast to same-airport clients (except sender)
SHARED_STATE_UPDATEcontroller onlyBroadcast to same-airport clients (including sender)
STOPBAR_CROSSINGpilot onlyBroadcast to same-airport controllers
CLOSEcontroller, pilot, observerServer 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_STATE immediately after connect
  • HEARTBEAT every 60 seconds
  • HEARTBEAT_ACK in response to client HEARTBEAT
  • STATE_SNAPSHOT in response to GET_STATE
  • STATE_UPDATE when a controller changes one object state
  • MULTI_STATE_UPDATE when a controller sends a batch state change
  • CONTROLLER_CONNECT / CONTROLLER_DISCONNECT for controller presence changes
  • SHARED_STATE_UPDATE when shared state changes
  • STOPBAR_CROSSING (controllers only) when a pilot crosses a stopbar
  • ERROR when validation/processing fails

Validation and limits (avoid malformed packets)

  • objectId must match ^[a-zA-Z0-9_-]+$.
  • STATE_UPDATE must include data.objectId and one of data.state or data.patch.
  • MULTI_STATE_UPDATE must include data.updates (maximum 200 updates).
  • SHARED_STATE_UPDATE must include data.sharedStatePatch object (maximum 10,240 serialized characters).
  • Maximum packet size is 50,000 characters.
  • Unknown packet type values are rejected.

Headers

Authorization
string

Bearer (alternative to key query parameter)

Query Parameters

airport
string
required

Airport ICAO (4 alphanumeric characters)

Required string length: 4
Pattern: ^[A-Z0-9]{4}$
key
string

User API key (optional when using Authorization header)

Response

WebSocket upgrade accepted