# 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.