Files
weatherapi/docs/api.md
Eric Rakestraw 5a1134b955
All checks were successful
ci/woodpecker/manual/build-image Pipeline was successful
Suppress superseded alerts in the /alerts/active endpoint
2026-06-17 06:48:14 -05:00

505 lines
16 KiB
Markdown

# weatherapi HTTP API
This is the canonical public HTTP contract for `weatherapi`. The service is a
read-only API over the latest weather records available in the configured
weatherfeeder-populated Postgres database.
## Base URL
All paths are relative to the deployment root:
```text
http://localhost:8080
```
Use your deployment host in production.
## Authentication
`weatherapi` does not implement authentication or authorization. Put access
control in front of the service when a deployment requires it.
## Response Envelope
Successful JSON and XML responses use a top-level envelope:
```json
{
"data": {}
}
```
When no latest/current resource exists, the request still succeeds and returns
`data: null`.
Text responses are rendered from endpoint-specific templates. When the payload
is missing, the templates return a short no-data message.
## Formats
Every implemented endpoint can produce:
| Format | Media type |
| --- | --- |
| `json` | `application/json` |
| `xml` | `application/xml` |
| `text` | `text/plain` |
Format selection is:
1. `format` query parameter;
2. `Accept` header;
3. configured `server.default_format`.
`format` values are case-insensitive. Unsupported formats return `406
Not Acceptable` with an error envelope.
## Shared Query Rules
Unknown query parameters are rejected with `400 Bad Request`.
| Parameter | Values | Default | Supported on |
| --- | --- | --- | --- |
| `format` | `json`, `xml`, `text` | configured default | all endpoints |
| `units` | `metric`, `us` | `metric` | all endpoints |
| `precision` | integer `0` through `2` | `0` | observations, current conditions, forecasts |
| `tz` or `TZ` | timezone selector | UTC/no conversion | forecasts, discussions, weather stories, outlooks |
`units`, `format`, and `precision` values are normalized case-insensitively
where applicable. `units=metric` returns metric field names; `units=us` returns
US-customary field names for unit-bearing payloads. Alerts, discussions, and
weather stories accept `units` but their current payload fields are not
materially changed by it. Outlooks also accept `units` without changing payload
values or field names.
`precision` controls numeric rounding. The default `0` rounds to whole numbers.
`precision` is rejected on alerts, discussions, weather stories, and outlooks.
Timezone selectors accepted by `tz` / `TZ`:
- IANA timezone names such as `America/Chicago`;
- common US abbreviations such as `CDT`, `CST`, `EDT`, `EST`, `MDT`, `MST`,
`PDT`, and `PST`;
- UTC offsets in `+H`, `+HH`, `+HH:MM`, `-H`, `-HH`, or `-HH:MM` form, bounded
to `-14:00` through `+14:00`;
- aliases `Chicago` and `Stl`, both mapped to `America/Chicago`.
If both `tz` and `TZ` are provided, their values must match
case-insensitively. Timezone conversion affects rendered timestamps and forecast
`/today` and `/tomorrow` day-slice filtering. Without a timezone parameter,
day-slice routes use UTC.
## Errors
API errors use a stable error envelope:
```json
{
"error": {
"code": "invalid_parameter",
"message": "..."
}
}
```
Implemented API error statuses:
| Status | Code | Cause |
| --- | --- | --- |
| `400 Bad Request` | `invalid_parameter` | unknown query parameter, invalid parameter value, invalid timezone, conflicting `tz` / `TZ`, or unsupported parameter on a route |
| `406 Not Acceptable` | `unsupported_format` | requested response format is not supported by the endpoint/renderers |
Unhandled service or database errors are returned as server errors by the
runtime.
## Behavior Not Implemented
`weatherapi` does not implement pagination, cache-control headers, rate-limit
headers, idempotency keys, retries, writes, or historical browsing outside the
implemented latest-resource and forecast day-slice routes.
## Endpoints
### Observations
```http
GET /observations
```
Returns the latest weather observation.
Query parameters: `format`, `units`, `precision`.
Metric `data` fields:
| Field | Type | Notes |
| --- | --- | --- |
| `stationId`, `stationName` | string | optional |
| `timestamp` | RFC3339 datetime | required when `data` is not null |
| `conditionCode` | integer | WMO weather code |
| `isDay` | boolean | optional |
| `textDescription` | string | optional |
| `temperatureC`, `dewpointC`, `apparentTemperatureC` | number | optional |
| `windDirectionDegrees`, `windSpeedKmh`, `windGustKmh` | number | optional |
| `barometricPressurePa`, `visibilityMeters` | number | optional |
| `relativeHumidityPercent` | number | optional |
| `presentWeather` | array | optional |
US mode replaces unit-bearing fields with `temperatureF`, `dewpointF`,
`apparentTemperatureF`, `windSpeedMph`, `windGustMph`,
`barometricPressureInHg`, and `visibilityMiles`. Direction and percentage
fields keep the same names.
Example:
```http
GET /observations?units=us&precision=1
```
### Current Conditions
```http
GET /conditions/current
```
Returns current conditions aggregated from recent `observations` rows. The
implemented observation window is 30 minutes.
Query parameters: `format`, `units`, `precision`.
Common `data` fields:
| Field | Type | Notes |
| --- | --- | --- |
| `conditionText` | string | optional text derived from WMO code and day/night flag |
| `isDay` | boolean | optional |
| `relativeHumidityPercent` | number | optional |
| `windDirectionDegrees` | number | optional |
Metric fields: `temperatureC`, `apparentTemperatureC`, `dewpointC`,
`windSpeedKmh`.
US fields: `temperatureF`, `apparentTemperatureF`, `dewpointF`,
`windSpeedMph`.
Example:
```http
GET /conditions/current?format=json&precision=0
```
### Active Alerts
```http
GET /alerts/active
```
Returns the latest stored alert run filtered to alerts active at request time,
omitting older alerts superseded by newer alert references in the same run.
Query parameters: `format`, `units`.
When no latest alert run exists, `data` is null. When a latest run exists but
no alerts are currently active, `data` remains an object and `alerts` is an
empty array.
Run `data` fields:
| Field | Type | Notes |
| --- | --- | --- |
| `locationId`, `locationName` | string | optional |
| `asOf` | RFC3339 datetime | required when `data` is not null |
| `latitude`, `longitude` | number | optional |
| `alerts` | array | active alerts, possibly empty |
Alerts are active when `messageType` is not `Cancel`, `effective` is absent or
at or before request time, and the alert end boundary is absent or after request
time. The end boundary prefers `ends`; if `ends` is absent, `expires` is used as
a fallback for older rows or providers that do not supply an alert-period end.
`onset` is presented when available but is not used as the active boundary.
After active-time filtering, alerts referenced by another alert in the same run
are omitted as superseded. References from update and cancel messages are both
honored, even when the referencing alert is not itself returned.
Alert fields include `id`, `event`, `headline`, `severity`, `urgency`,
`certainty`, `status`, `messageType`, `category`, `response`, `description`,
`instruction`, `sent`, `effective`, `onset`, `ends`, `expires`,
`areaDescription`, `senderName`, and `references`. Most alert fields are
optional except `id` when an alert item is present. `ends` is the alert-period
end; `expires` is provider expiration metadata.
Reference fields are `id`, `identifier`, `sender`, and `sent`.
Example:
```http
GET /alerts/active?format=text
```
### Convective Outlooks
```http
GET /outlooks/convective
GET /outlooks/convective/active
```
Returns the latest SPC convective outlook run reconstructed from
weatherfeeder-owned `weather.outlook.v2` Postgres tables.
Route behavior:
- `/outlooks/convective` returns the latest run with stored location-filtered
outlook polygons unless user filters are supplied.
- `/outlooks/convective/active` adds an active-time filter using the server's
current UTC time. Outlooks are active when `validFrom <= now < validTo`.
When no latest run exists, `data` is null. When a run exists but filters match
no outlooks, `data` remains an object and `outlooks` and `discussions` are
empty arrays. Outlook endpoints use latest-run semantics and do not accumulate
historical active outlooks across older runs.
Query parameters:
| Parameter | Supported on | Values |
| --- | --- | --- |
| `format`, `units`, `tz` / `TZ` | all outlook routes | shared rules above |
| `day` | all outlook routes | `1`, `2`, or `3` |
| `outlookType` | all outlook routes | `categorical`, `tornado`, `hail`, or `wind` |
`outlookType` values are normalized case-insensitively. Weatherfeeder v2
outlooks are already filtered for the configured location. `precision`,
`containsLocation`, and unknown parameters are rejected.
Run `data` fields:
| Field | Type | Notes |
| --- | --- | --- |
| `locationId`, `locationName` | string | optional |
| `latitude`, `longitude` | number | optional |
| `asOf` | RFC3339 datetime | required when `data` is not null |
| `issuedAt` | RFC3339 datetime | optional |
| `outlooks` | array | ordered outlook polygons, possibly empty |
| `discussions` | array | ordered day-level discussions, possibly empty |
Outlook fields:
| Field | Type | Notes |
| --- | --- | --- |
| `id`, `provider`, `product`, `outlookType`, `label` | string | required when an outlook is present |
| `day` | integer | SPC outlook day |
| `labelText`, `forecaster` | string | optional |
| `severityRank` | integer | optional |
| `validFrom`, `validTo`, `issuedAt`, `expiresAt` | RFC3339 datetime | required when an outlook is present |
| `sourceUrl`, `imageUrl` | string | optional |
| `containsLocation` | boolean | whether the outlook polygon contains the configured location |
| `geometry` | GeoJSON | stored outlook geometry |
Discussion fields:
| Field | Type | Notes |
| --- | --- | --- |
| `day` | integer | SPC outlook day |
| `headline`, `summary`, `discussion` | string | optional |
| `updatedAt` | RFC3339 datetime | optional |
GeoJSON coordinates use standard GeoJSON coordinate order: longitude, then
latitude. Timezone conversion applies to run `asOf`, run `issuedAt`, and each
outlook's `validFrom`, `validTo`, `issuedAt`, and `expiresAt`, and discussion
`updatedAt`. Active filtering compares instants and is not changed by the
presentation timezone. Endpoint filters also filter `discussions` to days
represented by retained outlooks.
Examples:
```http
GET /outlooks/convective?day=1&outlookType=categorical
GET /outlooks/convective/active?format=text&tz=CDT
```
Example JSON response:
```json
{
"data": {
"locationId": "stl",
"locationName": "St. Louis",
"asOf": "2026-06-11T18:00:00Z",
"issuedAt": "2026-06-11T17:00:00Z",
"outlooks": [
{
"id": "spc-day1-cat-slight",
"provider": "spc",
"product": "convective",
"day": 1,
"outlookType": "categorical",
"label": "SLGT",
"labelText": "Slight Risk",
"severityRank": 5,
"validFrom": "2026-06-11T18:00:00Z",
"validTo": "2026-06-12T12:00:00Z",
"issuedAt": "2026-06-11T17:00:00Z",
"expiresAt": "2026-06-12T12:00:00Z",
"containsLocation": true,
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-91.0, 38.0],
[-90.0, 38.0],
[-90.0, 39.0],
[-91.0, 38.0]
]
]
}
}
],
"discussions": [
{
"day": 1,
"headline": "Severe storms possible",
"summary": "Scattered severe storms are possible.",
"discussion": "SPC discussion text.",
"updatedAt": "2026-06-11T17:30:00Z"
}
]
}
}
```
Text format uses the shared convective outlook template for both outlook routes
and renders a no-data message when `data` is null.
### Forecasts
```http
GET /forecast/hourly
GET /forecast/hourly/today
GET /forecast/hourly/tomorrow
GET /forecast/narrative
GET /forecast/narrative/today
GET /forecast/narrative/tomorrow
```
Returns the latest hourly or narrative forecast run. `/today` and `/tomorrow`
return a copy of the latest run with `periods` filtered by each period's
`startTime` in the resolved timezone. If no periods match, `data` remains an
object and `periods` is an empty array.
Query parameters: `format`, `units`, `precision`, `tz` / `TZ`.
Run `data` fields:
| Field | Type | Notes |
| --- | --- | --- |
| `locationId`, `locationName` | string | optional |
| `issuedAt` | RFC3339 datetime | required when `data` is not null |
| `updatedAt` | RFC3339 datetime | optional |
| `product` | string | `hourly` or `narrative` for implemented routes |
| `latitude`, `longitude` | number | optional |
| `elevationMeters` or `elevationFeet` | number | optional, depends on `units` |
| `periods` | array | ordered forecast periods |
Metric period fields:
`startTime`, `endTime`, `name`, `isDay`, `conditionCode`,
`textDescription`, `temperatureC`, `temperatureCMin`, `temperatureCMax`,
`dewpointC`, `relativeHumidityPercent`, `windDirectionDegrees`,
`windSpeedKmh`, `windGustKmh`, `barometricPressurePa`, `visibilityMeters`,
`apparentTemperatureC`, `cloudCoverPercent`,
`probabilityOfPrecipitationPercent`, `precipitationAmountMm`,
`snowfallDepthMm`, and `uvIndex`.
US mode uses the same non-unit fields and replaces unit-bearing fields with
`temperatureF`, `temperatureFMin`, `temperatureFMax`, `dewpointF`,
`windSpeedMph`, `windGustMph`, `barometricPressureInHg`, `visibilityMiles`,
`apparentTemperatureF`, `precipitationAmountIn`, `snowfallDepthIn`, and
`elevationFeet` at run level.
`startTime` and `endTime` are required for each period. Other period fields are
optional, including `conditionCode`; narrative forecasts may omit it.
Examples:
```http
GET /forecast/hourly?units=us&precision=1&TZ=-5
GET /forecast/narrative/tomorrow?format=text&tz=Chicago
```
### Forecast Discussions
```http
GET /discussion
GET /discussion/key-messages
GET /discussion/short-term
GET /discussion/long-term
```
Returns the latest forecast discussion or a focused subresource.
Query parameters: `format`, `units`, `tz` / `TZ`.
Full discussion `data` fields:
| Field | Type | Notes |
| --- | --- | --- |
| `officeId`, `officeName` | string | optional |
| `product` | string | currently `afd` from weatherfeeder data |
| `issuedAt` | RFC3339 datetime | required when `data` is not null |
| `updatedAt` | RFC3339 datetime | optional |
| `keyMessages` | array of strings | optional |
| `shortTerm`, `longTerm` | object | optional section objects |
Discussion section fields are `qualifier`, `issuedAt`, and `text`, all
optional.
Subresources return the same metadata plus only their focused field:
`keyMessages`, `shortTerm`, or `longTerm`.
Example:
```http
GET /discussion/short-term?format=text&tz=CDT
```
### Weather Stories
```http
GET /weatherstories
GET /weatherstories/latest
```
`/weatherstories` returns the latest weather-story run and its ordered stories.
`/weatherstories/latest` returns the latest individual story.
Query parameters: `format`, `units`, `tz` / `TZ`.
Run `data` fields:
| Field | Type | Notes |
| --- | --- | --- |
| `officeId` | string | optional |
| `asOf` | RFC3339 datetime | required when `data` is not null |
| `stories` | array | ordered story objects |
Story fields:
| Field | Type | Notes |
| --- | --- | --- |
| `officeId` | string | optional |
| `startTime`, `endTime`, `updatedAt` | RFC3339 datetime | required when a story is present |
| `title`, `description`, `altText`, `downloadUrl` | string | optional |
| `priority` | boolean | required when a story is present |
| `order` | integer | required when a story is present |
Examples:
```http
GET /weatherstories?tz=America/Chicago
GET /weatherstories/latest?format=xml
```
## Copyable Requests
See [`examples/requests.http`](../examples/requests.http) for a compact set of
requests covering the implemented endpoint families.