> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mobs.ws/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The WebSocket at wss://mobs.ws/v1/ws/metar is the product. The HTTP API is a fallback for backfill only.
> Follow /agents.md for connect, auth, fields, heartbeat, reconnect, and dedupe. Never log MOBS_TOKEN or a URL that includes ?token=.

# Quickstart

> Three steps to a report: get an API key, connect, read a record.

Python first; shell and browser versions follow.

**1. Prepare your environment**

API keys are issued on request: email [support@mobs.ws](mailto:support@mobs.ws). There are no accounts. Keep yours in an environment variable such as `MOBS_API_KEY`. See [Authentication](https://docs.mobs.ws/authentication.md).

```bash Shell theme={"dark"}
python3 -m venv .venv
. .venv/bin/activate
pip install httpx \
  "websockets>=13"
export MOBS_API_KEY="YOUR_API_KEY"
```

**2. Open the WebSocket**

List the airports you want. Send a JSON `ping` every 20 seconds, or the socket closes after 60 seconds idle. New reports arrive as `new_metar_arrived`. No reports are sent on connect.

```python Python theme={"dark"}
import asyncio, json, os
from websockets.asyncio.client import connect

URL = (
    "wss://mobs.ws/v1/ws/metar"
    "?airports=KNYC,KLGA"
)
token = os.environ["MOBS_API_KEY"]
HEADERS = {"Authorization": f"Bearer {token}"}

async def heartbeat(ws):
    while True:
        await asyncio.sleep(20)
        await ws.send(json.dumps({"action": "ping"}))

async def main():
    async with connect(
        URL,
        additional_headers=HEADERS,
    ) as ws:
        beat = asyncio.create_task(heartbeat(ws))
        try:
            async for text in ws:
                msg = json.loads(text)
                if msg["type"] == "new_metar_arrived":
                    for rec in msg["data"]:
                        print(rec["icao"], rec["raw_metar"])
        finally:
            beat.cancel()

asyncio.run(main())
```

```bash websocat theme={"dark"}
AUTH="Authorization: Bearer $MOBS_API_KEY"
Q="?airports=KNYC,KLGA"
websocat \
  -H "$AUTH" \
  "wss://mobs.ws/v1/ws/metar$Q"
```

```javascript JavaScript (browser) theme={"dark"}
// Browsers cannot set socket
// headers; use ?token= here only.
const ws = new WebSocket(
  "wss://mobs.ws/v1/ws/metar"
  + "?airports=KNYC"
  + "&token=YOUR_API_KEY"
);
setInterval(
  () => ws.send('{"action":"ping"}'),
  20000
);
ws.onmessage = (e) => {
  console.log(JSON.parse(e.data));
};
```

```json Example data · report text elided theme={"dark"}
{"type":"new_metar_arrived",
 "data":[
  {"icao":"KNYC",
   "raw_metar": "METAR KNYC 092251Z …",
   "obs_time": "2026-11-09T22:51:00+00:00",
   "report_type":"METAR",
   "arrived_at": "<UTC time, ms>"}]}
```

**3. Optional: backfill the latest report**

After connect or reconnect, `GET /v1/metar/latest` can fill the current latest report per airport. `missing` lists requested airports with no record. This is optional backfill, not the live product. There is no history endpoint.

```python Python theme={"dark"}
import os
import httpx

api_key = os.environ["MOBS_API_KEY"]
r = httpx.get(
    "https://mobs.ws/v1/metar/latest",
    params={"airports": "KNYC,KLGA"},
    headers={"Authorization": f"Bearer {api_key}"},
)
r.raise_for_status()
body = r.json()
for rec in body["data"]:
    print(rec["icao"], rec["obs_time"], rec["raw_metar"])
print("missing:", body["missing"])
```

```bash curl theme={"dark"}
AUTH="Authorization: Bearer $MOBS_API_KEY"
curl -s \
  -H "$AUTH" \
  --get \
  --data-urlencode "airports=KNYC,KLGA" \
  "https://mobs.ws/v1/metar/latest"
```

All examples are not executed and use a placeholder API key or one read from the environment.

> [card] [Next: Authentication](https://docs.mobs.ws/authentication.md)
>
> How to send your API key, scopes, rotation and revocation.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.