From a0f5e85516f489f2ac72222647618036076c273a Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Thu, 11 Jun 2026 14:13:46 +0000 Subject: [PATCH] Rewrite public API documentation --- docs/api.md | 736 ++++++++++++++++++----------------------- examples/requests.http | 45 +++ 2 files changed, 370 insertions(+), 411 deletions(-) create mode 100644 examples/requests.http diff --git a/docs/api.md b/docs/api.md index 00d5297..9885ac0 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,441 +1,355 @@ -# weatherapi External API +# weatherapi HTTP API -This document describes the public HTTP API exposed by `weatherapi` for external consumers. +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 -The service is typically served at your deployment host, for example: - -- `https://weather.example.com` - -All paths below are relative to the service root. - -## Common Conventions - -### Response envelope - -All endpoints return a top-level envelope: - -- `data`: endpoint payload or `null` when no current/latest resource is available. - -JSON example: - -```json -{ - "data": {"...": "..."} -} -``` - -### Output format - -Supported via `format` query parameter (case-insensitive): - -- `json` (default) -- `xml` -- `text` - -### Units - -Supported via `units` query parameter (case-insensitive): - -- `metric` (default) -- `us` - -Endpoints that include unit-based numeric fields return either metric or US field variants depending on this value. - -### Precision - -Supported where documented via `precision` query parameter: - -- integer range: `0` to `2` -- controls decimal rounding of numeric output fields - -### Timezone (`tz` / `TZ`) - -Supported where documented: - -- accepted values include: - - IANA timezone names (example: `America/Chicago`) - - common US abbreviations (example: `CDT`, `EST`) - - UTC offsets in `±H`, `±HH`, or `±HH:MM` (example: `-5`, `+09:30`) - - aliases including `Chicago` and `Stl` -- `tz` and `TZ` are treated equivalently -- if both are provided, they must match exactly or the request fails - -Timezone affects datetime rendering and day-slice filtering for `/today` and `/tomorrow` forecast routes. -It also affects weather story and discussion datetime rendering. - -### Query validation - -- Unknown query parameters are rejected with `400 Bad Request`. -- Invalid parameter values are rejected with `400 Bad Request`. - -Error response body follows the service error envelope; exact fields may vary by error type. - -## Endpoints - -## `GET /observations` - -Returns the latest weather observation. - -Query parameters: - -- `units`: `metric` | `us` -- `format`: `json` | `xml` | `text` -- `precision`: `0..2` - -Response `data` fields: - -- `stationId` (string, optional) -- `stationName` (string, optional) -- `timestamp` (RFC3339 datetime, required) -- `conditionCode` (integer WMO code, required) -- `isDay` (boolean, optional) -- `textDescription` (string, optional) -- Metric mode fields: - - `temperatureC`, `dewpointC`, `windSpeedKmh`, `windGustKmh`, `barometricPressurePa`, `visibilityMeters`, `relativeHumidityPercent`, `apparentTemperatureC` (number, optional) - - `windDirectionDegrees` (number, optional) -- US mode fields: - - `temperatureF`, `dewpointF`, `windSpeedMph`, `windGustMph`, `barometricPressureInHg`, `visibilityMiles`, `relativeHumidityPercent`, `apparentTemperatureF` (number, optional) - - `windDirectionDegrees` (number, optional) -- `presentWeather` (array, optional) - -## `GET /alerts/active` - -Returns the latest active alert run. - -Query parameters: - -- `units`: `metric` | `us` (accepted; does not materially alter alert payload) -- `format`: `json` | `xml` | `text` - -Response `data` fields: - -- Weather alert run object from canonical model (includes run metadata and active alerts list). - -## `GET /conditions/current` - -Returns current conditions synthesized from latest observation/forecast data. - -Query parameters: - -- `units`: `metric` | `us` -- `format`: `json` | `xml` | `text` -- `precision`: `0..2` - -Response `data` fields: - -- Common: - - `conditionText` (string, optional) - - `isDay` (boolean, optional) - - `relativeHumidityPercent` (number, optional) - - `windDirectionDegrees` (number, optional) -- Metric mode: - - `temperatureC`, `apparentTemperatureC`, `dewpointC`, `windSpeedKmh` (number, optional) -- US mode: - - `temperatureF`, `apparentTemperatureF`, `dewpointF`, `windSpeedMph` (number, optional) - -## Forecast endpoints - -- `GET /forecast/hourly` -- `GET /forecast/hourly/today` -- `GET /forecast/hourly/tomorrow` -- `GET /forecast/narrative` -- `GET /forecast/narrative/today` -- `GET /forecast/narrative/tomorrow` - -Query parameters: - -- `units`: `metric` | `us` -- `format`: `json` | `xml` | `text` -- `precision`: `0..2` -- `tz` or `TZ`: timezone selector - -Day-slice routes: - -- `/today` returns periods with `period.startTime` in the current calendar day for the resolved timezone. -- `/tomorrow` returns periods with `period.startTime` in the next calendar day for the resolved timezone. - -Response `data` fields: - -- Run-level: - - `locationId` (string, optional) - - `locationName` (string, optional) - - `issuedAt` (RFC3339 datetime, required) - - `updatedAt` (RFC3339 datetime, optional) - - `product` (string, required; e.g. `hourly`, `narrative`) - - `latitude`, `longitude` (number, optional) - - Metric mode: `elevationMeters` (number, optional) - - US mode: `elevationFeet` (number, optional) - - `periods` (array, required) -- Period fields: - - `startTime`, `endTime` (RFC3339 datetime, required) - - `name` (string, optional) - - `isDay` (boolean, optional) - - `conditionCode` (integer WMO code, optional) - - `textDescription` (string, optional) - - Metric mode (optional): - - `temperatureC`, `temperatureCMin`, `temperatureCMax`, `dewpointC`, `windSpeedKmh`, `windGustKmh`, `barometricPressurePa`, `visibilityMeters`, `apparentTemperatureC`, `cloudCoverPercent`, `probabilityOfPrecipitationPercent`, `precipitationAmountMm`, `snowfallDepthMM`, `uvIndex`, `relativeHumidityPercent`, `windDirectionDegrees` - - US mode (optional): - - `temperatureF`, `temperatureFMin`, `temperatureFMax`, `dewpointF`, `windSpeedMph`, `windGustMph`, `barometricPressureInHg`, `visibilityMiles`, `apparentTemperatureF`, `cloudCoverPercent`, `probabilityOfPrecipitationPercent`, `precipitationAmountIn`, `snowfallDepthIn`, `uvIndex`, `relativeHumidityPercent`, `windDirectionDegrees` - -Notes: - -- Narrative periods may omit `conditionCode`. -- Text format uses forecast-specific templates (`hourly` and `narrative`). - -## Discussion endpoints - -- `GET /discussion` -- `GET /discussion/key-messages` -- `GET /discussion/short-term` -- `GET /discussion/long-term` - -Query parameters: - -- `units`: `metric` | `us` (accepted; does not materially alter discussion payload) -- `format`: `json` | `xml` | `text` -- `tz` or `TZ`: timezone selector - -Response `data` fields: - -- `/discussion`: - - `officeId` (string, optional) - - `officeName` (string, optional) - - `product` (string, required) - - `issuedAt` (RFC3339 datetime, required) - - `updatedAt` (RFC3339 datetime, optional) - - `keyMessages` (array of string) - - `shortTerm` (object, optional) - - `longTerm` (object, optional) -- `/discussion/key-messages`: - - `officeId`, `officeName`, `product`, `issuedAt`, `updatedAt` - - `keyMessages` (array of string) -- `/discussion/short-term`: - - `officeId`, `officeName`, `product`, `issuedAt`, `updatedAt` - - `shortTerm` (object, optional) -- `/discussion/long-term`: - - `officeId`, `officeName`, `product`, `issuedAt`, `updatedAt` - - `longTerm` (object, optional) - -Discussion section object fields: - -- `title` (string, optional) -- `narrative` (string, optional) -- `issuedAt` (RFC3339 datetime, optional) - -## Weather Stories endpoints - -- `GET /weatherstories` -- `GET /weatherstories/latest` - -Query parameters: - -- `units`: `metric` | `us` (accepted; does not materially alter weather story payloads) -- `format`: `json` | `xml` | `text` -- `tz` or `TZ`: timezone selector - -Response behavior: - -- `/weatherstories` returns the latest stored weather story run and its ordered stories. -- `/weatherstories/latest` returns the single story with the greatest `updatedAt`. -- When no weather story data exists, `data` is `null`. - -`/weatherstories` response `data` fields: - -- `officeId` (string, optional) -- `asOf` (RFC3339 datetime, required) -- `stories` (array, required) - -Weather story fields: - -- `officeId` (string, optional) -- `startTime` (RFC3339 datetime, required) -- `endTime` (RFC3339 datetime, required) -- `updatedAt` (RFC3339 datetime, required) -- `title` (string, optional) -- `description` (string, optional) -- `altText` (string, optional) -- `priority` (boolean, required) -- `order` (integer, required) -- `downloadUrl` (string, optional) - -## Examples - -### Observation (JSON, metric) - -```http -GET /observations?format=json&units=metric&precision=1 -``` - -```json -{ - "data": { - "stationId": "KSTL", - "timestamp": "2026-05-29T14:00:00Z", - "conditionCode": 3, - "isDay": true, - "textDescription": "Partly cloudy", - "temperatureC": 24.4, - "windSpeedKmh": 17.2, - "relativeHumidityPercent": 56.0 - } -} -``` - -### Alerts (JSON) - -```http -GET /alerts/active?format=json -``` - -```json -{ - "data": { - "asOf": "2026-05-29T14:00:00Z", - "alerts": [] - } -} -``` - -### Current conditions (JSON, US) - -```http -GET /conditions/current?format=json&units=us&precision=1 -``` - -```json -{ - "data": { - "conditionText": "Partly cloudy", - "isDay": true, - "temperatureF": 75.9, - "apparentTemperatureF": 76.1, - "windSpeedMph": 10.7, - "relativeHumidityPercent": 56.0 - } -} -``` - -### Forecast narrative (JSON, optional `conditionCode`) - -```http -GET /forecast/narrative?format=json&units=metric&precision=1&tz=America/Chicago -``` - -```json -{ - "data": { - "locationId": "nws-lsx-grid-90-74", - "issuedAt": "2026-05-29T10:30:00-05:00", - "product": "narrative", - "periods": [ - { - "startTime": "2026-05-29T13:00:00-05:00", - "endTime": "2026-05-29T19:00:00-05:00", - "name": "Today", - "isDay": true, - "textDescription": "Partly sunny, with a high near 81.", - "temperatureC": 27.2, - "windSpeedKmh": 18.0, - "probabilityOfPrecipitationPercent": 10.0 - } - ] - } -} -``` - -### Forecast hourly today (text) - -```http -GET /forecast/hourly/today?format=text&units=us&precision=1&tz=CDT -``` +All paths are relative to the deployment root: ```text - +http://localhost:8080 ``` -### Discussion key messages (JSON) +Use your deployment host in production. -```http -GET /discussion/key-messages?format=json&tz=Chicago -``` +## 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": { - "officeId": "LSX", - "product": "discussion", - "issuedAt": "2026-05-29T09:25:00-05:00", - "keyMessages": [ - "Scattered showers possible this evening.", - "Warmer temperatures this weekend." - ] - } + "data": {} } ``` -### Weather stories (JSON) +When no latest/current resource exists, the request still succeeds and returns +`data: null`. -```http -GET /weatherstories?format=json&tz=America/Chicago -``` +Text responses are rendered from endpoint-specific templates. When the payload +is missing, the templates return a short no-data message. -```json -{ - "data": { - "officeId": "LSX", - "asOf": "2026-05-30T04:00:34-05:00", - "stories": [ - { - "officeId": "LSX", - "startTime": "2026-05-30T03:46:00-05:00", - "endTime": "2026-05-31T06:00:00-05:00", - "updatedAt": "2026-05-30T04:00:34-05:00", - "title": "Several Chances for Rain Through Monday", - "description": "Scattered showers and thunderstorms remain possible.", - "altText": "This slide shows the forecast for today through Tuesday.", - "priority": false, - "order": 1, - "downloadUrl": "https://api.weather.gov/offices/LSX/weatherstories/download/3228e499-2aae-45a8-9ff9-1c060311026f" - } - ] - } -} -``` +## Formats -### Latest weather story (JSON) +Every implemented endpoint can produce: -```http -GET /weatherstories/latest?format=json&tz=CDT -``` +| Format | Media type | +| --- | --- | +| `json` | `application/json` | +| `xml` | `application/xml` | +| `text` | `text/plain` | -```json -{ - "data": { - "officeId": "LSX", - "startTime": "2026-05-30T03:46:00-05:00", - "endTime": "2026-05-31T06:00:00-05:00", - "updatedAt": "2026-05-30T04:00:34-05:00", - "title": "Several Chances for Rain Through Monday", - "description": "Scattered showers and thunderstorms remain possible.", - "priority": false, - "order": 1 - } -} -``` +Format selection is: -### Invalid timezone error example +1. `format` query parameter; +2. `Accept` header; +3. configured `server.default_format`. -```http -GET /forecast/narrative?tz=not-a-timezone -``` +`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 | + +`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. + +`precision` controls numeric rounding. The default `0` rounds to whole numbers. +`precision` is rejected on alerts, discussions, and weather stories. + +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": "tz must be a valid timezone" + "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 active-alert snapshot. + +Query parameters: `format`, `units`. + +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 | + +Alert fields include `id`, `event`, `headline`, `severity`, `urgency`, +`certainty`, `status`, `messageType`, `category`, `response`, `description`, +`instruction`, `sent`, `effective`, `onset`, `expires`, `areaDescription`, +`senderName`, and `references`. Most alert fields are optional except `id` when +an alert item is present. + +Reference fields are `id`, `identifier`, `sender`, and `sent`. + +Example: + +```http +GET /alerts/active?format=text +``` + +### 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. diff --git a/examples/requests.http b/examples/requests.http new file mode 100644 index 0000000..78a2d67 --- /dev/null +++ b/examples/requests.http @@ -0,0 +1,45 @@ +@baseUrl = http://localhost:8080 + +### Latest observation +GET {{baseUrl}}/observations?units=metric&precision=1 +Accept: application/json + +### Current conditions +GET {{baseUrl}}/conditions/current?units=us&precision=0 +Accept: application/json + +### Active alerts as text +GET {{baseUrl}}/alerts/active?format=text + +### Hourly forecast in US units +GET {{baseUrl}}/forecast/hourly?units=us&precision=1&tz=Chicago +Accept: application/json + +### Hourly forecast for today +GET {{baseUrl}}/forecast/hourly/today?tz=CDT +Accept: application/json + +### Narrative forecast for tomorrow as text +GET {{baseUrl}}/forecast/narrative/tomorrow?format=text&tz=America/Chicago + +### Full forecast discussion +GET {{baseUrl}}/discussion?tz=CDT +Accept: application/json + +### Forecast discussion key messages as text +GET {{baseUrl}}/discussion/key-messages?format=text + +### Forecast discussion short term +GET {{baseUrl}}/discussion/short-term?tz=CDT +Accept: application/json + +### Forecast discussion long term +GET {{baseUrl}}/discussion/long-term?TZ=-5 +Accept: application/json + +### Weather stories +GET {{baseUrl}}/weatherstories?tz=America/Chicago +Accept: application/json + +### Latest weather story as XML +GET {{baseUrl}}/weatherstories/latest?format=xml