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

# Historical METAR/SPECI

`GET https://api.metar.ws/v1/history/observations` returns archived METAR and SPECI reports for one or more stations, going back to 2005-01-01 wherever the underlying archive covers that station. This is a plain request/response REST endpoint, not a subscription: point it at a station and a time range and get reports back directly, with no live updates.

<Note>
  Unlike the live channels, historical queries are available starting on the Sandbox plan. Sandbox is throttled to 1 request/second — enough to explore the API, not to bulk-export data. See [Rate limits](#rate-limits) below.
</Note>

## Authentication

Historical queries use the same API key as the WebSocket stream, sent as a bearer token:

```text theme={null}
Authorization: Bearer mts_live_YOUR_KEY
```

No separate credential is needed. The endpoint reads from the same key store the stream reads from, so revoking a key in the portal takes effect immediately here too.

## Sample request

Getting METAR and SPECI reports for EDDM on 1 August 2026:

```bash theme={null}
curl --request GET \
  --url "https://api.metar.ws/v1/history/observations?stations=EDDM&from=2026-08-01T00:00:00Z&to=2026-08-02T00:00:00Z&type=all&include=raw,decoded&limit=100" \
    --header "Authorization: Bearer YOUR_API_KEY"
```

## Sample response

```json theme={null}
{
    "data": [
          {
                  "id": "zxiyckwps3ls5n3r6w3mpess",
                  "station": "EDDM",
                  "observedAt": "2026-08-01T00:50:00Z",
                  "type": "METAR",
                  "raw": "EDDM 010050Z AUTO 02003KT 350V050 CAVOK 18/16 Q1016 NOSIG",
                  "decoded": { "temp_c": "18", "dewp_c": "16", "wdir": 20, "wspd_kt": 3, "cavok": true, "altim_hpa": 1016 },
                  "parseStatus": "parsed"
          }
    ],
    "meta": {
          "hasMore": true,
          "nextCursor": "eyJ...",
          "coverage": {
                  "status": "complete",
                  "stations": [
                            { "station": "EDDM", "status": "complete", "yearsDone": 1, "yearsAll": 1 }
                  ]
          },
          "rateLimit": { "plan": "pro", "limitPerSecond": 100, "remaining": 99 }
    }
}
```

<Note>
  `decoded` is only populated when `include` requests it and the report parsed cleanly; check `parseStatus` before relying on it. It follows the same field names as the live [METAR Observations](/channels/metar-observations) channel.
</Note>

## Query parameters

| Parameter  | Required | Notes                                                                                             |
| ---------- | -------- | ------------------------------------------------------------------------------------------------- |
| `stations` | yes      | One or more uppercase ICAO codes, comma-separated                                                 |
| `from`     | yes      | RFC-3339 UTC timestamp; inclusive                                                                 |
| `to`       | yes      | RFC-3339 UTC timestamp; exclusive                                                                 |
| `type`     | no       | `all`, `metar`, or `speci`                                                                        |
| `include`  | no       | Comma-separated: `raw`, `decoded`, or both — controls which representations appear on each record |
| `limit`    | no       | Page size                                                                                         |
| `cursor`   | no       | Opaque pagination cursor from a previous response's `meta.nextCursor`                             |

<Note>
  `from` is inclusive and `to` is exclusive. All timestamps, in both the request and the response, are UTC in RFC-3339 / ISO 8601 format.
</Note>

## Response fields

Each element of `data` is one archived report:

| Field         | Type   | Notes                                                                                                                   |
| ------------- | ------ | ----------------------------------------------------------------------------------------------------------------------- |
| `id`          | string | Opaque identifier for this event                                                                                        |
| `station`     | string | Uppercase ICAO code                                                                                                     |
| `observedAt`  | string | RFC-3339 UTC observation time                                                                                           |
| `type`        | string | `METAR` or `SPECI`                                                                                                      |
| `raw`         | string | The unparsed report text; present when `include` requests it                                                            |
| `decoded`     | object | Parsed fields using the same names as the live stream; present when `include` requests it and the report parsed cleanly |
| `parseStatus` | string | e.g. `parsed`, or `raw_only` when decoding wasn't requested or didn't run                                               |

`meta` describes the page and the account making the request:

| Field               | Notes                                                                                        |
| ------------------- | -------------------------------------------------------------------------------------------- |
| `hasMore`           | `true` if another page follows                                                               |
| `nextCursor`        | Present when `hasMore` is `true`; pass it back as `cursor` with the same filters to continue |
| `coverage.status`   | `complete`, `partial`, or `none` for the request as a whole                                  |
| `coverage.stations` | Per-station breakdown: `status`, plus `yearsDone` and `yearsAll` for the requested range     |
| `rateLimit`         | Mirrors the rate-limit response headers: `plan`, `limitPerSecond`, `remaining`               |

<Warning>
  An empty `data` array with `coverage.status: "none"` means the archive has nothing for that station and range — not that the weather was calm or unreported. Check `coverage` before concluding there were no observations.
</Warning>

## Pagination

Requests page through `(observedAt, station, type)` order. When `meta.hasMore` is `true`, repeat the request with the same filters plus `cursor` set to `meta.nextCursor`. A cursor is tied to the filters it was issued with; changing `stations`, `from`, `to`, `type`, or `include` between pages is rejected rather than silently reinterpreted.

## Rate limits

| Plan         | Limit               |
| ------------ | ------------------- |
| Sandbox      | 1 request/second    |
| Starter      | 10 requests/second  |
| Professional | 100 requests/second |

Every response carries `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers, and the same numbers are mirrored in `meta.rateLimit`. Exceeding the limit returns `429 Too Many Requests` with a `Retry-After` header.

## Coverage

The archive is backfilled from the open sources and kept in metar.ws's own storage, so requests never hit that source directly. Coverage isn't uniform: stations actively carried by the live stream are fully backfilled to 2005-01-01 (or the station's period of record, if shorter), while the rest of the roster fills in gradually as the archive grows. Always check `meta.coverage` rather than assuming a station's history is complete.

<Card title="Next: Plans & Limits" icon="layer-group" href="/plans-and-limits">
  See how historical rate limits compare with the live stream's connection and channel limits.
</Card>
