Weather API¶
GET /api/v1/weather returns observed daily weather for a location and date. It backs the
project's course-required external API integration; see
ADR-008
for the decision and its context.
No sign-in or bearer token is required for this endpoint.
Base path¶
/api/v1
External provider¶
Weather data is retrieved server-side from Open-Meteo, a free weather
API that requires no API key. The frontend never calls Open-Meteo directly; it only calls this
project's own /api/v1/weather endpoint, and Open-Meteo is called exclusively from the backend
(apps/backend/src/modules/weather/weather.service.ts).
Open-Meteo splits its data across two endpoints, and the backend selects between them based on the requested date:
- Forecast (
api.open-meteo.com/v1/forecast) — used for dates from 92 days in the past through 16 days in the future. - Archive (
archive-api.open-meteo.com/v1/archive) — used for historical dates from1940-01-01up to the start of the forecast endpoint's supported window.
This selection happens entirely inside the backend's weather integration; callers of
/api/v1/weather and /api/v1/fixtures/{fixtureId}/weather do not need to know or specify which
provider endpoint served a given date.
Request¶
GET /api/v1/weather?latitude={latitude}&longitude={longitude}&date={date}
Fixture weather¶
GET /api/v1/fixtures/{fixtureId}/weather
This endpoint automatically uses the fixture's start_date and stored venue coordinates. When
coordinates are absent, it asks Open-Meteo's geocoding endpoint for the canonical venue together
with its stored city first, then falls back to the raw venue name if that location cannot be
resolved. A successful result is persisted on the canonical venue, then reused by later requests
without another geocoding call. Changing a venue's name or city clears the derived coordinates so
a later lookup safely resolves the changed location. It is the appropriate endpoint for fixture
pages: the frontend calls this backend endpoint only and never calls Open-Meteo. Weather is
contextual external information; it does not change or authoritatively describe fixture, event, or
statistic records.
When weather is available, the response includes fixture and venue context plus the same daily
temperature, precipitation, and wind fields returned by /weather:
{
"data": {
"fixtureId": "17",
"date": "2026-08-19",
"availability": "available",
"venue": { "name": "Wits Cricket Oval", "city": "Johannesburg" },
"weather": {
"date": "2026-08-19",
"latitude": -26.1929,
"longitude": 28.0305,
"temperatureMax": 24,
"temperatureMin": 11,
"precipitationSum": 0,
"windSpeedMax": 17
}
}
}
An existing fixture without a venue or with unsupported stored coordinate values returns 200 OK
with availability: "unavailable", a machine-readable reason, and weather: null. If neither the
city nor venue-name lookup can resolve a location, the reason is LOCATION_NOT_FOUND; no
coordinates are invented. MISSING_COORDINATES remains a compatible unavailable reason for older
responses. UNSUPPORTED_DATE is returned when the fixture's date falls outside both the forecast
and archive endpoints' supported ranges (see "External provider" above). An unknown fixture
returns 404 NOT_FOUND. Provider failures return the same 502, 503, or 504 codes as the
direct weather endpoint and affect only this weather request, not ordinary fixture reads.
| Parameter | Required | Description |
|---|---|---|
latitude |
Yes | Decimal degrees, between -90 and 90. |
longitude |
Yes | Decimal degrees, between -180 and 180. |
date |
Yes | YYYY-MM-DD. |
Example:
GET /api/v1/weather?latitude=-26.2041&longitude=28.0473&date=2026-08-19
Response¶
HTTP/1.1 200 OK
{
"data": {
"date": "2026-08-19",
"latitude": -26.2041,
"longitude": 28.0473,
"temperatureMax": 23.4,
"temperatureMin": 10.2,
"precipitationSum": 0,
"windSpeedMax": 18.5
}
}
temperatureMax/temperatureMin are in degrees Celsius, precipitationSum is in millimetres and
windSpeedMax is in km/h. Any of these fields may be null if Open-Meteo did not return a value
for the requested date.
Error responses¶
The endpoint uses the shared machine-readable API error format:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "latitude, longitude and date are required. Latitude and longitude must be numbers."
}
}
| Status | Code | Cause |
|---|---|---|
400 |
VALIDATION_FAILED |
Missing/non-numeric latitude or longitude, out-of-range coordinates, or a malformed date. |
422 |
DATE_UNSUPPORTED |
date is validly formatted but falls outside both the forecast and archive endpoints' supported ranges. |
502 |
UPSTREAM_ERROR |
Open-Meteo returned a non-2xx response, invalid JSON, or an unexpected response shape. |
503 |
WEATHER_SERVICE_UNAVAILABLE |
An unclassified failure occurred while contacting Open-Meteo. |
504 |
UPSTREAM_TIMEOUT |
Open-Meteo did not respond within the configured timeout (5000ms). |
A 502/503/504 means the external provider is unavailable or misbehaving; it does not indicate
a problem with the project's own API surface. The backend logs the underlying error before
responding, so failures remain observable without crashing the application or leaking upstream
error internals to the client.
Before returning one of these provider errors, the backend makes one immediate second attempt only when the first Open-Meteo request has a network/connection failure, times out, or returns HTTP 408 or 5xx. It does not retry normal client responses or 429 rate limits, because an immediate request would ignore the provider's rate-limit window. This bounded retry does not cache weather responses or replace the fixture page's manual retry fallback.
Known limitations¶
- Venue coordinates are optional canonical metadata (
venue.latitudeandvenue.longitude). Missing coordinates are resolved lazily from the canonical venue and city, then raw venue name, and persisted only after Open-Meteo returns valid coordinates. Changing the venue name or city invalidates the stored coordinates. Weather responses themselves are not cached. - Open-Meteo's archive endpoint only covers dates from
1940-01-01onward, and the forecast endpoint only extends 16 days into the future. Dates outside this combined range return422 DATE_UNSUPPORTEDfrom/api/v1/weather, oravailability: "unavailable"with reasonUNSUPPORTED_DATEfrom the fixture-weather endpoint, rather than an upstream error. - Responses are not cached; repeated requests for the same location and date each call Open-Meteo again.
Testing locally¶
No API key or account is required.
- Start the backend:
npm run dev:backend(ornpm run devfrom the repository root). - Request the endpoint directly:
curl "http://localhost:3000/api/v1/weather?latitude=-26.2041&longitude=28.0473&date=2026-08-19"
- To exercise the failure paths without depending on network conditions, run the automated tests instead of manual requests:
cd apps/backend
npx vitest run tests/unit/weather.service.test.ts
npx vitest run tests/api/weather.test.ts
npx vitest run tests/unit/fixture-weather.service.test.ts tests/api/fixture-weather.test.ts
The unit tests mock fetch directly to cover a successful response, a non-2xx response,
invalid JSON, and an unexpected response shape. Fixture-weather tests mock the existing
WeatherService to confirm fixture dates and stored coordinates are passed through, unavailable
locations avoid provider calls, and provider failures map to safe API responses.
OpenAPI¶
The machine-readable specification is documented in the OpenAPI specification, under
the Weather tag.
AI Declaration¶
The preceding document was planned and generated with the assistance of Claude Sonnet 5.