/v1/ws/metar. New METAR and SPECI reports are pushed as new_metar_arrived messages. The HTTP API is a fallback: it returns only the latest report per airport, and there is no history endpoint.
API keys are issued on request from support@mobs.ws. Store yours in MOBS_TOKEN. Never log the token, and never log a URL that includes ?token=.
Paste this into your agent
Connect
The
stream scope is required. A refused handshake never upgrades: 401 (no or invalid key), 403 (wrong scope), 400 (uncovered airport), 422 (bad airports or mode, or over the airport cap), 429 (per-key socket cap), 503 (server-wide cap).
Authentication
Non-browser clients sendAuthorization: Bearer $MOBS_TOKEN on the handshake (and on HTTP). If no Bearer header is present, the server reads ?token=, then the website cookie. Do not use the website cookie. There is no WebSocket subprotocol for the key.
Airport filter
Passairports on the URL, or send JSON on an open socket:
{"type":"subscription_updated","icaos":[…]} with the resulting sorted list. subscribe adds, unsubscribe removes. Unsubscribing a code you do not hold is ignored. No wildcards.
Message types
Client JSON actions:ping, subscribe, unsubscribe. Anything else is unknown_action. Binary frames are bad_request.
Record fields
Every record, in both modes:
In
mode=parsed, parsed is MOBS’s own decoded set. Only present, non-null keys are included: temp_c, dewp_c, wdir, wdir_vrb, wspd_kt, wgst_kt, visib_sm, visib_m, visib_or_greater, visib_or_less, altim_hpa, slp_mb, wx, clouds, flt_cat, corrected. Numbers that need decimals are strings; units are in the field name.
SPECI
A SPECI uses the samenew_metar_arrived message. report_type is SPECI and raw_metar starts with SPECI. polymarket.com daily temperature markets do not settle on a SPECI; filter on report_type if you are tracking those markets. Settlement uses the Aviation Weather Center METAR API, the T-group in tenths when present otherwise the body temperature in whole °C, and the station’s local calendar day. See Stations.
Corrections
Store-internal fieldscorrection, replaces_report_id and revision are not on the wire. A correction arrives as a new record with the same icao and obs_time and a different raw_metar. Keep the later record. In parsed mode, parsed.corrected may be true. An off-minute report can also arrive twice: first typed SPECI, then METAR once a second source confirms the type.
Dedupe on icao and obs_time, replacing the stored record when raw_metar changes.
Heartbeat and idle
Send{"action":"ping"} every 20 seconds. The server replies {"type":"pong"}. After 60 seconds with no JSON ping and no valid subscribe or unsubscribe, the socket closes with 4000 (idle timeout). RFC 6455 protocol pings do not reset that timer; MOBS answers them but does not send its own. A connection that reaches 24 hours is closed with 4008 (max connection age).
Reconnect
Close the old socket, wait with exponential backoff and jitter, and connect again with the same airports. Backoff is capped at about 30 s (with jitter, about 15–45 s). Reset the backoff after the first message, or after about 10 s up (the 10 s timer starts once the handshake completes), so a socket that is accepted and closed at once (for example1013) does not retry immediately. Then call GET /v1/metar/latest and merge. Reports issued while you were away are not resent; only the newest per airport can be read.
Python
pip install httpx "websockets>=17".
JavaScript (browser or Node)
Browsers cannot set socket headers; this sample uses?token=. Prefer the Bearer header from Python or another HTTP-capable client. Never log this URL.
HTTP backfill only
There is no history endpoint (/v1/history is retired). Use HTTP only to read the current latest report per airport, including after a reconnect.
airports is required on the first two. Optional mode=raw (default) or parsed. Same record shape as the socket. Auth is Authorization: Bearer $MOBS_TOKEN. The latest scope is required. Public catalogs: GET /v1/status and GET /v1/stations.