diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..038f60b --- /dev/null +++ b/docs/api.md @@ -0,0 +1,354 @@ +# weatherapi External API + +This document describes the public HTTP API exposed by `weatherapi` for external consumers. + +## 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. + +### 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) + +## 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 +``` + +```text + +``` + +### Discussion key messages (JSON) + +```http +GET /discussion/key-messages?format=json&tz=Chicago +``` + +```json +{ + "data": { + "officeId": "LSX", + "product": "discussion", + "issuedAt": "2026-05-29T09:25:00-05:00", + "keyMessages": [ + "Scattered showers possible this evening.", + "Warmer temperatures this weekend." + ] + } +} +``` + +### Invalid timezone error example + +```http +GET /forecast/narrative?tz=not-a-timezone +``` + +```json +{ + "error": { + "code": "invalid_parameter", + "message": "tz must be a valid timezone" + } +} +```