Back to skills
SkillHub ClubShip Full StackFull StackBackendDesigner

open-meteo

Integrate Open-Meteo Weather Forecast, Air Quality, and Geocoding APIs: query design, variable selection, timezone/timeformat/units, multi-location batching, and robust error handling. Keywords: Open-Meteo, /v1/forecast, /v1/air-quality, geocoding-api, hourly, daily, current, timezone=auto, timeformat=unixtime, models, WMO weather_code, CAMS, GeoNames, httpx, FastAPI, pytest.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
10
Hot score
84
Updated
March 20, 2026
Overall rating
C1.4
Composite score
1.4
Best-practice grade
C61.1

Install command

npx @skill-hub/cli install itechmeat-llm-code-open-meteo
apiweatherintegrationgeocodingerror-handling

Repository

itechmeat/llm-code

Skill path: skills/open-meteo

Integrate Open-Meteo Weather Forecast, Air Quality, and Geocoding APIs: query design, variable selection, timezone/timeformat/units, multi-location batching, and robust error handling. Keywords: Open-Meteo, /v1/forecast, /v1/air-quality, geocoding-api, hourly, daily, current, timezone=auto, timeformat=unixtime, models, WMO weather_code, CAMS, GeoNames, httpx, FastAPI, pytest.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack, Backend, Designer, Integration.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: itechmeat.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install open-meteo into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/itechmeat/llm-code before adding open-meteo to shared team environments
  • Use open-meteo for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: open-meteo
description: "Integrate Open-Meteo Weather Forecast, Air Quality, and Geocoding APIs: query design, variable selection, timezone/timeformat/units, multi-location batching, and robust error handling. Keywords: Open-Meteo, /v1/forecast, /v1/air-quality, geocoding-api, hourly, daily, current, timezone=auto, timeformat=unixtime, models, WMO weather_code, CAMS, GeoNames, httpx, FastAPI, pytest."
version: "1.4.0"
release_date: "2024-12-31"
---

# Open Meteo

## When to use

- You need weather forecasts (hourly/daily/current) for coordinates.
- You need air quality / pollen forecasts (hourly/current) for coordinates.
- You need to resolve a user-provided place name to coordinates and timezone (geocoding).
- You need to support multi-location batching (comma-separated lat/lon lists).
- You need a deterministic checklist for Open-Meteo query parameters, response parsing, and error handling.

## 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 `name` and optional `language`, `countryCode`, `count`.
   - Use the returned `latitude`, `longitude`, and `timezone` for subsequent calls.

3. Design your time axis (timezone, timeformat, and range)

   - Prefer `timezone=auto` when results must align to local midnight.
   - If you request `daily=...`, set `timezone` (docs: daily requires timezone).
   - Choose `timeformat=iso8601` for readability, or `timeformat=unixtime` for compactness.
     - If using `unixtime`, remember timestamps are GMT+0 and you must apply `utc_offset_seconds` for correct local dates.
   - Choose range controls:
     - `forecast_days` and optional `past_days`, or
     - explicit `start_date`/`end_date` (YYYY-MM-DD), and for sub-daily `start_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 vs `models=`") for examples and doc links.
     - for provider/model-specific selection tradeoffs, see `references/models.md`.
   - Air Quality domain selection:
     - `domains=auto` (default) or `cams_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=csv` or `format=xlsx` when you need data export.

7. Validate correctness with a “known city” check
   - Geocode “Berlin” → Forecast `hourly=temperature_2m` for 1–2 days → verify timezone and array lengths.
   - Air Quality `hourly=pm10,pm2_5,european_aqi` → verify units and presence of `hourly_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 `timezone` when requesting `daily` variables (per docs).
- Do not assume `unixtime` timestamps are local time; they are GMT+0 and require `utc_offset_seconds` adjustment.
- Do not silently ignore `{"error": true}` responses; fail fast with the provided `reason`.
- 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`, one `daily` (with timezone), and one `current` variable.
- 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: true` with clear messages.
- Attribution requirements from the docs are captured for Air Quality (CAMS) and Geocoding (GeoNames).

## Links

- Official docs (in-scope):
  - https://open-meteo.com/en/docs
  - https://open-meteo.com/en/docs/air-quality-api
  - https://open-meteo.com/en/docs/geocoding-api
- Skill references:
  - `references/forecast-api.md`
  - `references/models.md`
  - `references/weather-codes.md`
  - `references/air-quality-api.md`
  - `references/geocoding-api.md`
  - `references/examples.md`


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/models.md

```markdown
# 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=auto` or `models=<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

```

### references/forecast-api.md

```markdown
# 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

- `latitude` and `longitude` accept **comma-separated lists**.
- When multiple locations are requested, the **JSON output shape changes** (docs note this explicitly).
- CSV/XLSX export adds a `location_id` column.

## 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 `daily` variables are specified, `timezone` is required.
- `current` (string array): current-condition variables.

### Time controls

- `timezone` (string, default `GMT`)
  - Use a TZ database name (e.g., `Europe/Berlin`).
  - `timezone=auto` resolves 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, default `iso8601`)
  - `iso8601` or `unixtime`.
  - **If `unixtime`:** timestamps are returned in GMT+0; apply `utc_offset_seconds` to 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` (default `celsius`): set to `fahrenheit` to convert.
- `wind_speed_unit` (default `kmh`): alternatives `ms`, `mph`, `kn`.
- `precipitation_unit` (default `mm`): alternative `inch`.

### Models

- `models` (string array, default `auto`)
  - 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=nan` disables downscaling.
- `cell_selection` (string, default `land`)
  - `land`: prefers a land grid-cell with similar elevation.
  - `sea`: prefers sea grid-cells.
  - `nearest`: nearest possible grid-cell.

### Export formats

- `format=csv` or `format=xlsx` to export.

### Commercial usage

- `apikey` is 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_temperature`
  - `precipitation`, `rain`, `showers`, `snowfall`, `precipitation_probability`
  - `wind_speed_10m`, `wind_direction_10m`, `wind_gusts_10m`
  - `weather_code`, `cloud_cover`, `pressure_msl`, `visibility`

- Forecast `daily`:
  - `temperature_2m_max`, `temperature_2m_min`
  - `precipitation_sum`, `rain_sum`, `snowfall_sum`
  - `sunrise`, `sunset`, `daylight_duration`, `sunshine_duration`
  - `wind_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`, `elevation`
  - `generationtime_ms`
  - `utc_offset_seconds`
  - `timezone`, `timezone_abbreviation`

- Variable blocks:
  - `current` (object): values for requested current variables + `time` and `interval` (seconds)
  - `hourly` (object): `time[]` + arrays for each requested hourly variable
  - `hourly_units` (object): unit per hourly variable
  - `daily` / `daily_units` analogous

## WMO weather codes

- `weather_code` uses 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:

```json
{
  "error": true,
  "reason": "Cannot initialize WeatherVariable from invalid String value ..."
}
```

```

### references/weather-codes.md

```markdown
# 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).

```

### references/air-quality-api.md

```markdown
# 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, default `auto`)
  - `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, default `GMT`)
  - Supports TZ database names.
  - `timezone=auto` resolves coordinates to local timezone.

- `timeformat` (string, default `iso8601`)
  - `iso8601` or `unixtime`.
  - If `unixtime`: timestamps are GMT+0; apply `utc_offset_seconds` to 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, default `nearest`)
  - `nearest`, `land`, `sea`.

### Export formats

- `format=csv` or `format=xlsx` to export.

### Commercial usage

- `apikey` is 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`, `elevation`
- `generationtime_ms`
- `utc_offset_seconds`
- `timezone`, `timezone_abbreviation`
- `hourly` (object): `time[]` + arrays for each requested hourly variable
- `hourly_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:

```json
{
  "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.

```

### references/geocoding-api.md

```markdown
# 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:

```json
{
  "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., `admin4` may 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.:

```json
{
  "error": true,
  "reason": "Parameter count must be between 1 and 100."
}
```

## Attribution (docs)

- Location data based on GeoNames
- Country flags from HatScripts/circle-flags

```

### references/examples.md

```markdown
# 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

```python
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

```python
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 data
```

## Notes

- Variable names must match the docs exactly: e.g., `pm2_5` (not `pm2.5`).
- If you set `timeformat=unixtime`, timestamps are in GMT+0; use `utc_offset_seconds` to derive local dates.
- If you request `daily=...` on the Forecast API, you must set `timezone` (per docs).

```

open-meteo | SkillHub