# weatherfeeder API (Wire Contract) This document defines the stable, consumer-facing JSON contract emitted by weatherfeeder sinks. weatherfeeder emits **events** encoded as JSON. Each event has: - an **envelope** (metadata + schema identifier), and - a **payload** whose shape is determined by `schema`. Downstream consumers should: 1. parse the event envelope, 2. switch on `schema`, then 3. decode `payload` into the matching schema. --- ## Event envelope All events are JSON objects with these fields: | Field | Type | Required | Notes | |---|---:|:---:|---| | `id` | string | yes | Stable event identifier. Treat as opaque. | | `schema` | string | yes | Schema identifier (e.g. `weather.observation.v1`). | | `source` | string | yes | Provider/source identifier (stable within configuration). | | `effectiveAt` | string (timestamp) | yes | RFC3339Nano timestamp indicating when this event is effective. | | `payload` | object | yes | Schema-specific payload (see below). | ### Timestamp format All timestamps are encoded as JSON strings using Go’s `time.Time` JSON encoding (RFC3339Nano). Examples: - `"2026-01-17T14:27:00Z"` - `"2026-01-17T08:27:00-06:00"` --- ## Canonical schemas weatherfeeder emits three canonical domain schemas: - `weather.observation.v1` - `weather.forecast.v1` - `weather.alert.v1` Each payload is described below using the JSON field names as the contract. --- ## Shared conventions ### Optional fields Most non-identity measurements are optional. Optional fields are omitted when unknown (i.e., not present). ### Units Canonical payloads are normalized to metric units: - Temperature: `*C` (Celsius) - Wind speed/gust: `*Kmh` (kilometers/hour) - Pressure: `*Pa` (Pascals) - Visibility / distance / elevation: `*Meters` (meters) - Precipitation amount / snowfall depth: `*Mm` (millimeters) - Humidity / cloud cover / PoP: `*Percent` (0–100) ### Float rounding For readability and stability, weatherfeeder rounds floating-point values in canonical payloads to **2 digits after the decimal** during normalization finalization. ### WMO condition codes `conditionCode` uses the WMO weather interpretation code vocabulary. - Type: integer - Unknown/unmappable: `-1` Downstream consumers should treat unknown codes as “unknown conditions” rather than failing decoding. --- ## Schema: `weather.observation.v1` Payload type: `WeatherObservation` A `WeatherObservation` represents a point-in-time observation for a station/location. ### Fields | Field | Type | Required | Units / Notes | |---|---:|:---:|---| | `stationId` | string | no | Provider station/location identifier | | `stationName` | string | no | Human name, if available | | `timestamp` | string (timestamp) | yes | Observation time | | `conditionCode` | int | yes | WMO code (`-1` for unknown) | | `conditionText` | string | no | Canonical short text (often derived from WMO code) | | `isDay` | bool | no | Day/night hint when available | | `providerRawDescription` | string | no | Provider-specific “evidence” text | | `textDescription` | string | no | Legacy/transitional human text | | `iconUrl` | string | no | Legacy/transitional icon URL | | `temperatureC` | number | no | °C | | `dewpointC` | number | no | °C | | `windDirectionDegrees` | number | no | Degrees (meteorological) | | `windSpeedKmh` | number | no | km/h | | `windGustKmh` | number | no | km/h | | `barometricPressurePa` | number | no | Pa | | `seaLevelPressurePa` | number | no | Pa | | `visibilityMeters` | number | no | meters | | `relativeHumidityPercent` | number | no | percent (0–100) | | `apparentTemperatureC` | number | no | °C | | `elevationMeters` | number | no | meters | | `rawMessage` | string | no | Provider raw message (e.g. METAR), if available | | `presentWeather` | array | no | Provider-specific structured fragments | | `cloudLayers` | array | no | Cloud layers (base + amount) | ### Nested: `cloudLayers[]` Each `cloudLayers[]` element: | Field | Type | Required | Notes | |---|---:|:---:|---| | `baseMeters` | number | no | Cloud base altitude in meters | | `amount` | string | no | Provider string (e.g. FEW/SCT/BKN/OVC) | ### Nested: `presentWeather[]` Each `presentWeather[]` element: | Field | Type | Required | Notes | |---|---:|:---:|---| | `raw` | object | no | Provider-specific JSON object | --- ## Schema: `weather.forecast.v1` Payload type: `WeatherForecastRun` A `WeatherForecastRun` is a single issued forecast snapshot for a location and a specific product (hourly / narrative / daily). The run contains an ordered list of forecast periods. ### `product` values `product` is one of: - `"hourly"` - `"narrative"` - `"daily"` ### Fields | Field | Type | Required | Notes | |---|---:|:---:|---| | `locationId` | string | no | Provider location identifier | | `locationName` | string | no | Human name, if available | | `issuedAt` | string (timestamp) | yes | When this run was generated/issued | | `updatedAt` | string (timestamp) | no | Optional later update time | | `product` | string | yes | `"hourly"`, `"narrative"`, or `"daily"` | | `latitude` | number | no | Degrees | | `longitude` | number | no | Degrees | | `elevationMeters` | number | no | meters | | `periods` | array | yes | Chronological forecast periods | ### Nested: `periods[]` (`WeatherForecastPeriod`) A `WeatherForecastPeriod` is valid for `[startTime, endTime)`. | Field | Type | Required | Units / Notes | |---|---:|:---:|---| | `startTime` | string (timestamp) | yes | Period start | | `endTime` | string (timestamp) | yes | Period end | | `name` | string | no | Human label (often empty for hourly) | | `isDay` | bool | no | Day/night hint | | `conditionCode` | int | yes | WMO code (`-1` for unknown) | | `conditionText` | string | no | Canonical short text | | `providerRawDescription` | string | no | Provider-specific “evidence” text | | `textDescription` | string | no | Human-facing short phrase | | `detailedText` | string | no | Longer narrative | | `iconUrl` | string | no | Legacy/transitional | | `temperatureC` | number | no | °C | | `temperatureCMin` | number | no | °C (aggregated products) | | `temperatureCMax` | number | no | °C (aggregated products) | | `dewpointC` | number | no | °C | | `relativeHumidityPercent` | number | no | percent | | `windDirectionDegrees` | number | no | degrees | | `windSpeedKmh` | number | no | km/h | | `windGustKmh` | number | no | km/h | | `barometricPressurePa` | number | no | Pa | | `visibilityMeters` | number | no | meters | | `apparentTemperatureC` | number | no | °C | | `cloudCoverPercent` | number | no | percent | | `probabilityOfPrecipitationPercent` | number | no | percent | | `precipitationAmountMm` | number | no | mm (liquid equivalent) | | `snowfallDepthMm` | number | no | mm | | `uvIndex` | number | no | unitless index | --- ## Schema: `weather.alert.v1` Payload type: `WeatherAlertRun` A `WeatherAlertRun` is a snapshot of *active* alerts for a location as-of a point in time. A run may contain zero, one, or many alerts. ### Fields | Field | Type | Required | Notes | |---|---:|:---:|---| | `locationId` | string | no | Provider location identifier | | `locationName` | string | no | Human name, if available | | `asOf` | string (timestamp) | yes | When the provider asserted this snapshot is current | | `latitude` | number | no | Degrees | | `longitude` | number | no | Degrees | | `alerts` | array | yes | Active alerts (order provider-dependent) | ### Nested: `alerts[]` (`WeatherAlert`) | Field | Type | Required | Notes | |---|---:|:---:|---| | `id` | string | yes | Provider-stable identifier (often a URL/URI) | | `event` | string | no | Classification | | `headline` | string | no | Short headline | | `severity` | string | no | e.g. Extreme/Severe/Moderate/Minor/Unknown | | `urgency` | string | no | e.g. Immediate/Expected/Future/Past/Unknown | | `certainty` | string | no | e.g. Observed/Likely/Possible/Unlikely/Unknown | | `status` | string | no | e.g. Actual/Exercise/Test/System/Unknown | | `messageType` | string | no | e.g. Alert/Update/Cancel | | `category` | string | no | e.g. Met/Geo/Safety/... | | `response` | string | no | e.g. Shelter/Evacuate/Prepare/... | | `description` | string | no | Narrative | | `instruction` | string | no | What to do | | `sent` | string (timestamp) | no | Provider-dependent | | `effective` | string (timestamp) | no | Provider-dependent | | `onset` | string (timestamp) | no | Provider-dependent | | `expires` | string (timestamp) | no | Provider-dependent | | `areaDescription` | string | no | Often a provider string | | `senderName` | string | no | Provenance | | `references` | array | no | Related alert references | ### Nested: `references[]` (`AlertReference`) | Field | Type | Required | Notes | |---|---:|:---:|---| | `id` | string | no | Provider reference ID/URI | | `identifier` | string | no | Provider identifier string, if distinct | | `sender` | string | no | Sender | | `sent` | string (timestamp) | no | Timestamp | --- ## Compatibility rules - Consumers **must** ignore unknown fields. - Producers (weatherfeeder) prefer **additive changes** within a schema version. - Renames/removals/semantic breaks require a **schema version bump** (`weather.*.v2`). --- ## Examples ### Observation event (`weather.observation.v1`) ```json { "id": "nws:KSTL:2026-01-17T14:00:00Z", "schema": "weather.observation.v1", "source": "nws_observation", "effectiveAt": "2026-01-17T14:00:00Z", "payload": { "stationId": "KSTL", "timestamp": "2026-01-17T14:00:00Z", "conditionCode": 1, "conditionText": "Mainly Sunny", "temperatureC": 3.25, "windSpeedKmh": 18.5 } } ``` ### Forecast event (`weather.forecast.v1`) ```json { "id": "openmeteo:38.63,-90.20:2026-01-17T13:00:00Z", "schema": "weather.forecast.v1", "source": "openmeteo_forecast", "effectiveAt": "2026-01-17T13:00:00Z", "payload": { "locationName": "St. Louis, MO", "issuedAt": "2026-01-17T13:00:00Z", "product": "hourly", "latitude": 38.63, "longitude": -90.2, "periods": [ { "startTime": "2026-01-17T14:00:00Z", "endTime": "2026-01-17T15:00:00Z", "conditionCode": 2, "conditionText": "Partly Cloudy", "temperatureC": 3.5, "probabilityOfPrecipitationPercent": 10 } ] } } ``` ### Alert event (`weather.alert.v1`) ```json { "id": "nws:alerts:2026-01-17T14:10:00Z", "schema": "weather.alert.v1", "source": "nws_alerts", "effectiveAt": "2026-01-17T14:10:00Z", "payload": { "asOf": "2026-01-17T14:05:00Z", "alerts": [ { "id": "https://api.weather.gov/alerts/abc123", "event": "Winter Weather Advisory", "headline": "Winter Weather Advisory issued January 17 at 8:05AM CST", "severity": "Moderate", "description": "Mixed precipitation expected...", "expires": "2026-01-18T06:00:00Z" } ] } } ```