01 · Start
Open your first stream
Create a WebSocket connection with permessage-deflate, then send one complete JSON subscription within three seconds. The API key belongs in a server-side environment variable. Check SubscriptionConfirmation.Message.CompressionEnabled to confirm that compression was negotiated.
const WebSocket = require("ws");
const socket = new WebSocket("wss://stream.aisstream.io/v0/stream", {
perMessageDeflate: true
});
socket.on("open", () => socket.send(JSON.stringify({
APIKey: process.env.AISSTREAM_API_KEY,
BoundingBoxes: [[[25.835, -80.208], [25.603, -79.879]]],
FilterMessageTypes: ["PositionReport"]
})));
socket.on("message", data => console.log(JSON.parse(data.toString())));02 · Authenticate
Keep credentials on your backend
03 · Subscribe
Select the area or vessels you want to track
Bounding boxes are required. MMSI and message-type filters are optional. Resending a subscription replaces the active configuration. Each accepted subscription or replacement returns a SubscriptionConfirmation; invalid subscriptions do not.
| Field | Required | Meaning |
|---|---|---|
| APIKey | Yes | Account API key |
| BoundingBoxes | Yes | Pairs of latitude/longitude corners |
| FiltersShipMMSI | No | Up to 200 nine-character MMSIs |
| FilterMessageTypes | No | Supported message type names |
{
"APIKey": "<YOUR_API_KEY>",
"BoundingBoxes": [
[
[
25.835,
-80.208
],
[
25.603,
-79.879
]
]
],
"FiltersShipMMSI": [
"368207620"
],
"FilterMessageTypes": [
"PositionReport"
]
}Supported message types (25)
PositionReportUnknownMessageAddressedSafetyMessageAddressedBinaryMessageAidsToNavigationReportAssignedModeCommandBaseStationReportBinaryAcknowledgeBinaryBroadcastMessageChannelManagementCoordinatedUTCInquiryDataLinkManagementMessageDataLinkManagementMessageDataExtendedClassBPositionReportGroupAssignmentCommandGnssBroadcastBinaryMessageInterrogationLongRangeAisBroadcastMessageMultiSlotBinaryMessageSafetyBroadcastMessageShipStaticDataSingleSlotBinaryMessageStandardClassBPositionReportStandardSearchAndRescueAircraftReportStaticDataReport04 · Consume
Monitor messages and connection status
MessageType selects the typed object under Message. MetaData contains normalized AIS context such as MMSI and last known position. A subscription confirmation has no AIS metadata; its CompressionEnabled value reports whether permessage-deflate was negotiated for that connection. The server sends binary WebSocket frames containing UTF-8 JSON.
{
"MessageType": "PositionReport",
"MetaData": {
"MMSI": 368207620,
"ShipName": "EXAMPLE VESSEL",
"Latitude": 25.7617,
"Longitude": -80.1918
},
"Message": {
"PositionReport": {
"MessageID": 1,
"UserID": 368207620,
"Sog": 12.4,
"Cog": 86.7,
"TrueHeading": 87,
"Valid": true
}
}
}{
"MessageType": "SubscriptionConfirmation",
"Message": {
"CompressionEnabled": true
}
}05 · Operate
Limits and operational considerations
These limits come directly from the stream service. Build reconnects and observability around them.
| Constraint | Limit | Consideration |
|---|---|---|
| Connections per account | 3 subscribed connections | A fourth subscription is rejected. |
| Connections per originating IP | 3 open connections | The limit applies before authentication. |
| Initial subscription | Within 3 seconds | The connection closes if no valid subscription arrives. |
| Subscription updates | 1 per second per connection | A faster update closes the connection. Updates replace, rather than merge with, the previous subscription. |
| MMSI filters | 200 per subscription | Each MMSI must be a nine-character string. |
| Connection speed | Read continuously | If you do not consume messages quickly enough and sufficient buffered messages accumulate, the service drops messages. |
Uncompressed connections
Beginning in September 2026, uncompressed connections will be subject to per-user bandwidth limits, and messages exceeding those limits will be dropped.
Why are messages slow or missing?+
AIS messages are event-driven rather than emitted on a fixed schedule. Your filters, current vessel activity, upstream source interruptions, network latency, and client processing speed all affect what you observe. If you do not consume messages quickly enough and sufficient buffered messages accumulate, the service drops messages.
How should I handle reconnects?+
Treat the stream as a long-lived but fallible connection. Reconnect with exponential backoff and jitter, then send a complete replacement subscription within three seconds.
Why did my connection close?+
Common causes are a late or malformed subscription, an invalid key, a connection-limit breach, subscription updates faster than once per second, fragmented client messages, missed keepalive activity, or a slow connection.
What WebSocket framing should I expect?+
The service sends binary WebSocket frames whose payload is UTF-8 JSON. Decode the frame bytes before parsing JSON. Client subscription frames may be text or binary, but fragmented messages are not supported.
How can I use fewer bytes?+
Apply MMSI and message-type filters, keep geographic bounding boxes focused on your application, and enable permessage-deflate. Numeric JSON values are emitted without unnecessary trailing zeroes.
Are direct browser connections allowed?+
No. Direct browser connections are not permitted. Connect from your own server and proxy only the information each client needs. This protects the API key and reduces load on the stream service.
What uptime or delivery guarantees are provided?+
The service currently provides no SLA or uptime guarantee, and events are not durably replayed. Plan for interruptions, reconnect with backoff, and persist messages your application cannot afford to lose.
06 · Reference
AIS message schema explorer
Position
PositionReport
| Field | Type | Required | Description |
|---|---|---|---|
| MessageID | integer | Yes | Numeric AIS message identifier. |
| RepeatIndicator | integer | Yes | Number of times the AIS message was repeated. |
| UserID | integer | Yes | Vessel or station MMSI. |
| Valid | boolean | Yes | Whether the decoded value passed validation. |
| NavigationalStatus | integer | Yes | Reported navigation status code. |
| RateOfTurn | integer | Yes | Reported rate-of-turn value. |
| Sog | number<double> | Yes | Speed over ground in knots. |
| PositionAccuracy | boolean | Yes | Reported position-accuracy flag. |
| Longitude | number<double> | Yes | Longitude in decimal degrees. |
| Latitude | number<double> | Yes | Latitude in decimal degrees. |
| Cog | number<double> | Yes | Course over ground in degrees. |
| TrueHeading | integer | Yes | True heading in degrees. |
| Timestamp | integer | Yes | AIS UTC-second timestamp. |
| SpecialManoeuvreIndicator | integer | Yes | AIS SpecialManoeuvreIndicator field. |
| Raim | boolean | Yes | Receiver autonomous integrity monitoring flag. |
{
"MessageID": 1,
"RepeatIndicator": 1,
"UserID": 1,
"Valid": true,
"NavigationalStatus": 1,
"RateOfTurn": 1,
"Sog": 12.3,
"PositionAccuracy": true,
"Longitude": 12.3,
"Latitude": 12.3,
"Cog": 12.3,
"TrueHeading": 1,
"Timestamp": 1,
"SpecialManoeuvreIndicator": 1,
"Spare": 1,
"Raim": true,
"CommunicationState": 1
}