
Open Meteo
- 81 installs
- 22 repo stars
- Updated August 1, 2026
- itechmeat/llm-code
Call the Open-Meteo Forecast, Air Quality, and Geocoding APIs with correct variable, timezone, unit and error handling.
About
A guide to integrating the Open-Meteo weather, air-quality and geocoding REST APIs, covering query design, timezone/timeformat/units and robust error handling. Use it when fetching forecasts, air-quality/pollen data, or geocoding place names to coordinates.
- Handles HTTP errors and JSON-level {"error": true} responses separately, failing fast on the reason
- Requires timezone for daily variables and notes unixtime is GMT+0 needing utc_offset_seconds adjustment
Open Meteo by the numbers
- 81 all-time installs (skills.sh)
- +5 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #3,045 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/itechmeat/llm-code --skill open-meteoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 81 |
|---|---|
| repo stars | ★ 22 |
| Last updated | August 1, 2026 |
| Repository | itechmeat/llm-code ↗ |
What it does
Call the Open-Meteo Forecast, Air Quality, and Geocoding APIs with correct variable, timezone, unit and error handling.
Files
Open Meteo
Goal
Provide a reliable, production-friendly way to call Open-Meteo APIs (Forecast, Air Quality, Geocoding), choose variables, control time/units/timezone, and parse responses consistently.
Steps
1. Pick the correct API and base URL
- Forecast:
https://api.open-meteo.com/v1/forecast - Air Quality:
https://air-quality-api.open-meteo.com/v1/air-quality - Geocoding:
https://geocoding-api.open-meteo.com/v1/search
2. Resolve coordinates (if you only have a name)
- Call Geocoding with
nameand optionallanguage,countryCode,count. - Use the returned
latitude,longitude, andtimezonefor subsequent calls.
3. Design your time axis (timezone, timeformat, and range)
- Prefer
timezone=autowhen results must align to local midnight. - If you request
daily=..., settimezone(docs: daily requires timezone). - Choose
timeformat=iso8601for readability, ortimeformat=unixtimefor compactness. - If using
unixtime, remember timestamps are GMT+0 and you must applyutc_offset_secondsfor correct local dates. - Choose range controls:
forecast_daysand optionalpast_days, or- explicit
start_date/end_date(YYYY-MM-DD), and for sub-dailystart_hour/end_hour.
4. Choose variables minimally (avoid "download everything")
- Forecast: request only the variables you need via
hourly=...,daily=...,current=.... - Air Quality: request only the variables you need via
hourly=...,current=.... - Keep variable names exact; typos return a JSON error with
error: true.
5. Choose units and model selection deliberately
- Forecast units:
temperature_unit(celsius/fahrenheit)wind_speed_unit(kmh/ms/mph/kn)precipitation_unit(mm/inch)- Forecast model selection:
- default
models=auto/ “Best match” combines the best models. - you can explicitly request models via
models=.... - provider-specific forecast endpoints also exist (provider implied by path). See
references/models.md(section "Endpoints vsmodels=") for examples and doc links. - for provider/model-specific selection tradeoffs, see
references/models.md. - Air Quality domain selection:
domains=auto(default) orcams_europe/cams_global.
6. Implement robust request/response handling
- Treat HTTP errors and JSON-level errors separately.
- JSON error format is:
{"error": true, "reason": "..."}- When requesting multiple locations (comma-separated coordinates), expect the JSON output shape to change to a list of structures.
- Optionally use
format=csvorformat=xlsxwhen you need data export.
7. Validate correctness with a “known city” check
- Geocode “Berlin” → Forecast
hourly=temperature_2mfor 1–2 days → verify timezone and array lengths. - Air Quality
hourly=pm10,pm2_5,european_aqi→ verify units and presence ofhourly_units.
Critical prohibitions
- Do not include out-of-scope APIs in this skill’s implementation guidance: Historical Weather, Ensemble Models, Seasonal Forecast, Climate Change, Marine, Satellite Radiation, Elevation, Flood.
- Do not omit
timezonewhen requestingdailyvariables (per docs). - Do not assume
unixtimetimestamps are local time; they are GMT+0 and requireutc_offset_secondsadjustment. - Do not silently ignore
{"error": true}responses; fail fast with the providedreason. - Do not request huge variable sets by default; keep queries minimal to reduce payload and avoid accidental overuse.
Definition of done
- You can geocode a place name and obtain coordinates/timezone.
- You can fetch Forecast data with at least one
hourly, onedaily(with timezone), and onecurrentvariable. - You can fetch Air Quality data for at least one pollutant and one AQI metric.
- Your client code handles both HTTP-level failures and JSON-level
error: truewith clear messages. - Attribution requirements from the docs are captured for Air Quality (CAMS) and Geocoding (GeoNames).
Links
Open-Meteo Air Quality API (in-scope)
Source of truth: https://open-meteo.com/en/docs/air-quality-api
Base URL
https://air-quality-api.open-meteo.com/v1/air-quality
Core query parameters
Required
latitude(float)longitude(float)
Variable selection
hourly(string array): comma-separated or repeated&hourly=parameters.current(string array): current-condition air-quality variables.
Domain selection
domains(string, defaultauto)auto: combine both domains automatically.cams_europe: European domain.cams_global: global domain.
Docs note: European and global domains are not coupled and may show different forecasts.
Time controls
timezone(string, defaultGMT)- Supports TZ database names.
timezone=autoresolves coordinates to local timezone.
timeformat(string, defaultiso8601)iso8601orunixtime.- If
unixtime: timestamps are GMT+0; applyutc_offset_secondsto derive correct local dates.
forecast_days(int 0–7, default 5)past_days(int 0–92, default 0)
- Alternative timestep control:
forecast_hours,past_hours
- Explicit time interval:
start_date,end_date(YYYY-MM-DD)start_hour,end_hour(YYYY-MM-DDThh:mm)
Grid cell selection
cell_selection(string, defaultnearest)nearest,land,sea.
Export formats
format=csvorformat=xlsxto export.
Commercial usage
apikeyis optional; docs describe it as required only for commercial use to access reserved resources.- Docs note the server URL requires the prefix
customer-for commercial usage.
Hourly variables (docs list)
Common pollutants and indices:
- Particulate matter:
pm10,pm2_5 - Gases:
carbon_monoxide,nitrogen_dioxide,sulphur_dioxide,ozone - AQI (European):
european_aqi(consolidated max)european_aqi_pm2_5,european_aqi_pm10,european_aqi_nitrogen_dioxide,european_aqi_ozone,european_aqi_sulphur_dioxide- AQI (United States):
us_aqi(consolidated max)us_aqi_pm2_5,us_aqi_pm10,us_aqi_nitrogen_dioxide,us_aqi_ozone,us_aqi_sulphur_dioxide,us_aqi_carbon_monoxide
Pollen variables (Europe only, during pollen season with 4 days forecast):
alder_pollen,birch_pollen,grass_pollen,mugwort_pollen,olive_pollen,ragweed_pollen
Other hourly variables called out in the docs UI include:
carbon_dioxide,aerosol_optical_depth,dust,uv_index,uv_index_clear_sky,ammonia,methane
European AQI scale (docs)
- 0–20: good
- 20–40: fair
- 40–60: moderate
- 60–80: poor
- 80–100: very poor
- >100: extremely poor
Docs note: PM uses a 24-hour rolling average; gases use hourly values.
EU thresholds table (from docs)
| Pollutant (μg/m³) | Timespan | Good | Fair | Moderate | Poor | Very poor | Extremely poor |
|---|---|---|---|---|---|---|---|
| PM2.5 | 24h | 0-10 | 10-20 | 20-25 | 25-50 | 50-75 | 75-800 |
| PM10 | 24h | 0-20 | 20-40 | 40-50 | 50-100 | 100-150 | 150-1200 |
| NO2 | 1h | 0-40 | 40-90 | 90-120 | 120-230 | 230-340 | 340-1000 |
| O3 | 1h | 0-50 | 50-100 | 100-130 | 130-240 | 240-380 | 380-800 |
| SO2 | 1h | 0-100 | 100-200 | 200-350 | 350-500 | 500-750 | 750-1250 |
US AQI scale (docs)
- 0–50: good
- 51–100: moderate
- 101–150: unhealthy for sensitive groups
- 151–200: unhealthy
- 201–300: very unhealthy
- 301–500: hazardous
US thresholds table (from docs)
| Pollutant | Timespan | Good | Moderate | Unhealthy for Sensitive Groups | Unhealthy | Very Unhealthy | Hazardous | |
|---|---|---|---|---|---|---|---|---|
| O3 (ppb) | 8h | 0-55 | 55-70 | 70-85 | 85-105 | 105-200 | - | - |
| O3 (ppb) | 1h | - | - | 125-165 | 165-205 | 205-405 | 405-505 | 505-605 |
| PM2.5 (μg/m³) | 24h | 0-12 | 12-35.5 | 35.5-55.5 | 55.5-150.5 | 150.5-250.5 | 250.5-350.5 | 350.5-500.5 |
| PM10 (μg/m³) | 24h | 0-55 | 55-155 | 155-255 | 255-355 | 355-425 | 425-505 | 505-605 |
| CO (ppm) | 8h | 0-4.5 | 4.5-9.5 | 9.5-12.5 | 12.5-15.5 | 15.5-30.5 | 30.5-40.5 | 40.5-50.5 |
| SO2 (ppb) | 1h | 0-35 | 35-75 | 75-185 | 185-305 | - | - | - |
| SO2 (ppb) | 24h | - | - | - | - | 305-605 | 605-805 | 805-1005 |
| NO2 (ppb) | 1h | 0-54 | 54-100 | 100-360 | 360-650 | 650-1250 | 1250-1650 | 1650-2050 |
Response structure (single-location)
The docs show the canonical response shape:
latitude,longitude,elevationgenerationtime_msutc_offset_secondstimezone,timezone_abbreviationhourly(object):time[]+ arrays for each requested hourly variablehourly_units(object): unit per hourly variable
(If current was requested, a current object is included.)
Error handling
The docs show JSON error objects returned with HTTP 400:
{
"error": true,
"reason": "Cannot initialize WeatherVariable from invalid String value ..."
}Attribution / acknowledgement (docs)
Docs require users to provide attribution to:
- CAMS ENSEMBLE data provider, and
- Open-Meteo
See the “Citation & Acknowledgement” section on the Air Quality docs page.
Examples (Geocoding → Forecast / Air Quality)
All examples are derived from the official docs pages:
- https://open-meteo.com/en/docs
- https://open-meteo.com/en/docs/air-quality-api
- https://open-meteo.com/en/docs/geocoding-api
Python (httpx) — geocode then forecast
from __future__ import annotations
from typing import Any
import httpx
def _raise_if_open_meteo_error(payload: dict[str, Any]) -> None:
if payload.get("error") is True:
reason = payload.get("reason")
raise RuntimeError(f"Open-Meteo error: {reason}")
def geocode(name: str, *, language: str = "en", count: int = 1) -> dict[str, Any]:
url = "https://geocoding-api.open-meteo.com/v1/search"
params = {"name": name, "language": language, "count": count, "format": "json"}
with httpx.Client(timeout=10.0) as client:
resp = client.get(url, params=params)
resp.raise_for_status()
data = resp.json()
_raise_if_open_meteo_error(data)
results = data.get("results")
if not results:
raise ValueError(f"No geocoding results for: {name}")
return results[0]
def forecast(latitude: float, longitude: float, timezone: str) -> dict[str, Any]:
url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": latitude,
"longitude": longitude,
"hourly": "temperature_2m,precipitation,weather_code",
"daily": "temperature_2m_max,temperature_2m_min,precipitation_sum",
"current": "temperature_2m,wind_speed_10m",
"timezone": timezone, # daily requires timezone per docs
"forecast_days": 2,
}
with httpx.Client(timeout=10.0) as client:
resp = client.get(url, params=params)
resp.raise_for_status()
data = resp.json()
_raise_if_open_meteo_error(data)
return data
place = geocode("Berlin", language="en", count=1)
lat = float(place["latitude"])
lon = float(place["longitude"])
tz = str(place.get("timezone") or "GMT")
payload = forecast(lat, lon, tz)
print(payload["timezone"], payload["hourly_units"].get("temperature_2m"))
print(payload["hourly"]["time"][0], payload["hourly"]["temperature_2m"][0])Python (httpx) — air quality for the same point
from __future__ import annotations
from typing import Any
import httpx
def _raise_if_open_meteo_error(payload: dict[str, Any]) -> None:
if payload.get("error") is True:
raise RuntimeError(f"Open-Meteo error: {payload.get('reason')}")
def air_quality(latitude: float, longitude: float, timezone: str) -> dict[str, Any]:
url = "https://air-quality-api.open-meteo.com/v1/air-quality"
params = {
"latitude": latitude,
"longitude": longitude,
"hourly": "pm10,pm2_5,european_aqi,us_aqi",
"current": "pm10,pm2_5,european_aqi",
"timezone": timezone,
"forecast_days": 2,
"domains": "auto",
}
with httpx.Client(timeout=10.0) as client:
resp = client.get(url, params=params)
resp.raise_for_status()
data = resp.json()
_raise_if_open_meteo_error(data)
return dataNotes
- Variable names must match the docs exactly: e.g.,
pm2_5(notpm2.5). - If you set
timeformat=unixtime, timestamps are in GMT+0; useutc_offset_secondsto derive local dates. - If you request
daily=...on the Forecast API, you must settimezone(per docs).
Open-Meteo Weather Forecast API (in-scope)
Source of truth: https://open-meteo.com/en/docs
Base URL
https://api.open-meteo.com/v1/forecast
Request shape
- HTTP method:
GET - Query parameters are URL-encoded.
Multi-location batching
latitudeandlongitudeaccept comma-separated lists.- When multiple locations are requested, the JSON output shape changes (docs note this explicitly).
- CSV/XLSX export adds a
location_idcolumn.
Core query parameters
Required
latitude(float)longitude(float)
Variable selection
hourly(string array): comma-separated or repeated&hourly=parameters.daily(string array): comma-separated or repeated&daily=parameters.- Important: If
dailyvariables are specified,timezoneis required. current(string array): current-condition variables.
Time controls
timezone(string, defaultGMT)- Use a TZ database name (e.g.,
Europe/Berlin). timezone=autoresolves coordinates to the local timezone.- If timezone is set, timestamps are returned in local time and data starts at 00:00 local-time.
timeformat(string, defaultiso8601)iso8601orunixtime.- If `unixtime`: timestamps are returned in GMT+0; apply
utc_offset_secondsto get correct local dates.
forecast_days(int 0–16, default 7)past_days(int 0–92, default 0)
- Fine-grained timestep control:
forecast_hours,past_hours- For 15-minutely data:
forecast_minutely_15,past_minutely_15
- Explicit time interval:
start_date,end_date(YYYY-MM-DD)- For hourly/15-minutely:
start_hour,end_hour,start_minutely_15,end_minutely_15(YYYY-MM-DDThh:mm)
Units
temperature_unit(defaultcelsius): set tofahrenheitto convert.wind_speed_unit(defaultkmh): alternativesms,mph,kn.precipitation_unit(defaultmm): alternativeinch.
Models
models(string array, defaultauto)- Docs note: “Best match” provides the best forecast worldwide.
- “Seamless” combines models from a provider into a seamless prediction.
Elevation and grid-cell selection
elevation(float)- Default: uses a 90m digital elevation model.
elevation=nandisables downscaling.cell_selection(string, defaultland)land: prefers a land grid-cell with similar elevation.sea: prefers sea grid-cells.nearest: nearest possible grid-cell.
Export formats
format=csvorformat=xlsxto export.
Commercial usage
apikeyis optional and described as required only for commercial use to access reserved resources.- Docs note the server URL requires the prefix `customer-` for commercial usage.
Variables (high-level map)
The docs provide extensive variable lists. These are common, high-signal variables to start with:
- Forecast
hourly: temperature_2m,relative_humidity_2m,apparent_temperatureprecipitation,rain,showers,snowfall,precipitation_probabilitywind_speed_10m,wind_direction_10m,wind_gusts_10mweather_code,cloud_cover,pressure_msl,visibility
- Forecast
daily: temperature_2m_max,temperature_2m_minprecipitation_sum,rain_sum,snowfall_sumsunrise,sunset,daylight_duration,sunshine_durationwind_speed_10m_max,wind_gusts_10m_max
- Forecast
current: - any hourly variable can be requested as current; docs mention current values are based on 15-minutely data.
Response structure (single-location)
The docs show the canonical response shape:
- Top-level metadata:
latitude,longitude,elevationgenerationtime_msutc_offset_secondstimezone,timezone_abbreviation
- Variable blocks:
current(object): values for requested current variables +timeandinterval(seconds)hourly(object):time[]+ arrays for each requested hourly variablehourly_units(object): unit per hourly variabledaily/daily_unitsanalogous
WMO weather codes
weather_codeuses WMO interpretation codes.- See:
references/weather-codes.md.
Error handling
If URL parameters are invalid or variable names are misspelled, the API returns HTTP 400 with a JSON object:
{
"error": true,
"reason": "Cannot initialize WeatherVariable from invalid String value ..."
}Open-Meteo Geocoding API (in-scope)
Source of truth: https://open-meteo.com/en/docs/geocoding-api
Base URL
- Search:
https://geocoding-api.open-meteo.com/v1/search
Docs also mention IDs can be resolved via:
- Get by ID:
https://geocoding-api.open-meteo.com/v1/get?id=<id>
Search parameters
| Parameter | Required | Default | Notes |
|---|---|---|---|
name | yes | Search string: location name or postal code | |
count | no | 10 | 1–100 allowed |
language | no | en | Lower-cased; returns translated results if available |
format | no | json | json or protobuf |
countryCode | no | ISO-3166-1 alpha2 filter | |
apikey | no | Docs describe it as required only for commercial use (reserved resources) |
Matching behavior (docs)
- empty string or 1 character → empty result
- 2 characters → exact matching only
- 3+ characters → fuzzy matching
Response
On success, returns:
{
"results": [
{
"id": 2950159,
"name": "Berlin",
"latitude": 52.52437,
"longitude": 13.41053,
"elevation": 74.0,
"feature_code": "PPLC",
"country_code": "DE",
"timezone": "Europe/Berlin",
"population": 3426354,
"postcodes": ["10967", "13347"],
"country": "Deutschland",
"admin1": "Berlin"
}
]
}Docs note:
- Empty fields are not returned (e.g.,
admin4may be missing). - The schema includes administrative levels (
admin1..admin4) and corresponding IDs.
Error handling
Docs show HTTP 400 with a JSON error object, e.g.:
{
"error": true,
"reason": "Parameter count must be between 1 and 100."
}Attribution (docs)
- Location data based on GeoNames
- Country flags from HatScripts/circle-flags
Forecast models= selection guide
Source: Official Open-Meteo provider documentation pages.
Scope: Weather Forecast API only. Does not cover Historical, Ensemble, Seasonal, Climate, Marine, Satellite Radiation, Elevation, or Flood APIs.
How model selection works
models=auto(default) — "Best match" combines optimal models automatically- Explicit
models=<key>— select specific provider/model - Provider-specific endpoints — some providers have dedicated endpoints (e.g.,
/v1/ecmwf)
Endpoints vs models=
Generic Forecast endpoint (recommended):
- URL:
https://api.open-meteo.com/v1/forecast - Use
models=autoormodels=<provider_key> - Most flexible, supports switching providers per request
Provider-specific endpoints:
/v1/dwd-icon— DWD ICON/v1/gfs— NOAA GFS/HRRR/v1/ecmwf— ECMWF IFS/AIFS/v1/bom— Australia BOM/v1/cma— China CMA
Start with the generic endpoint + models=auto unless you have specific requirements.
Model selection rules
1. Default to `models=auto` — works globally, picks best available
2. Use "seamless" models when you want:
- High-resolution short-range (2–3 days) + automatic extension with global models
3. Use regional models when you need:
- Higher spatial resolution
- Only care about short horizons (nowcast / 0–72h)
4. Variable availability varies:
- Not all providers have direct solar radiation (some derive it)
- Cloud cover at 2m (fog indicator) — only KMA, UKMO UKV, DMI
- Probability data — only NBM (NOAA), ARPEGE (Météo-France)
5. Licensing:
- UKMO: CC BY-SA 4.0 (derived products must stay compatible)
6. Operational notes:
- BOM: temporary suspension due to platform upgrades
- CMA: recent overload/reliability issues
Provider quick reference
| Provider | Region | Resolution | Horizon | Seamless key |
|---|---|---|---|---|
| DWD ICON | Germany/Europe | 2–11 km | 2–7.5d | icon_seamless |
| NOAA GFS | Global/US | 3–13 km | 16d | gfs_seamless |
| Météo-France | France/Europe | 1.5–25 km | 2–4d | meteofrance_seamless |
| ECMWF | Global | 9–25 km | 15d | ecmwf_ifs |
| UKMO | UK/Global | 2–10 km | 2–7d | ukmo_seamless |
| KMA | Korea/Global | 1.5–12 km | 2–12d | kma_seamless |
| JMA | Japan/Global | 5–55 km | 4–11d | jma_seamless |
| MeteoSwiss | Central Europe | 1–2 km | 33h–5d | meteoswiss_icon_ch1 |
| MET Norway | Nordic | 1 km | 2.5d | metno_seamless |
| GEM | Canada/Global | 2.5–15 km | 2–10d | gem_seamless |
| BOM | Australia | 15 km | 10d | bom_access_global |
| CMA | China/Global | 15 km | 10d | cma_grapes_global |
| KNMI | Netherlands | 2–5.5 km | 2.5–10d | knmi_seamless |
| DMI | Denmark | 2 km | 2.5–10d | dmi_seamless |
| ItaliaMeteo | Southern Europe | 2 km | 3d | italia_meteo_arpae_icon_2i |
Finding specific model keys
If you need a specific sub-model (not seamless): 1. Go to the provider's docs page 2. Use "Preview → URL" generator 3. Copy models=... values from the generated URL
Provider docs:
- https://open-meteo.com/en/docs/dwd-api
- https://open-meteo.com/en/docs/gfs-api
- https://open-meteo.com/en/docs/ecmwf-api
- https://open-meteo.com/en/docs/meteofrance-api
- https://open-meteo.com/en/docs/ukmo-api
- https://open-meteo.com/en/docs/kma-api
- https://open-meteo.com/en/docs/jma-api
- https://open-meteo.com/en/docs/meteoswiss-api
- https://open-meteo.com/en/docs/metno-api
- https://open-meteo.com/en/docs/gem-api
- https://open-meteo.com/en/docs/bom-api
- https://open-meteo.com/en/docs/cma-api
- https://open-meteo.com/en/docs/knmi-api
- https://open-meteo.com/en/docs/dmi-api
- https://open-meteo.com/en/docs/italia-meteo-arpae-api
WMO Weather interpretation codes (WW)
Source of truth: https://open-meteo.com/en/docs
Open-Meteo’s weather_code uses WMO interpretation codes.
| Code | Description |
|---|---|
| 0 | Clear sky |
| 1, 2, 3 | Mainly clear, partly cloudy, and overcast |
| 45, 48 | Fog and depositing rime fog |
| 51, 53, 55 | Drizzle: Light, moderate, and dense intensity |
| 56, 57 | Freezing Drizzle: Light and dense intensity |
| 61, 63, 65 | Rain: Slight, moderate and heavy intensity |
| 66, 67 | Freezing Rain: Light and heavy intensity |
| 71, 73, 75 | Snow fall: Slight, moderate, and heavy intensity |
| 77 | Snow grains |
| 80, 81, 82 | Rain showers: Slight, moderate, and violent |
| 85, 86 | Snow showers slight and heavy |
| 95* | Thunderstorm: Slight or moderate |
| 96, 99* | Thunderstorm with slight and heavy hail |
\* Thunderstorm forecast with hail is only available in Central Europe (per docs).