# CAMS European air quality forecasts

Every day a forecast of air quality across Europe for the next four days, hourly and per cell of about 10 km. With nitrogen dioxide, particulate matter, ozone and also birch and grass pollen. It is the median of eleven models, including the Dutch LOTOS-EUROS.

- Publisher: Copernicus, European Centre for Medium-Range Weather Forecasts
- Area: Europe · Finest detail: approx. 10 km (0.1 degree)
- Period: 2023 – heden, plus vier dagen vooruit · Refreshed: daily
- Access: Account · Licence: CC BY 4.0
- Join keys: coördinaten, tijdstip, hoogte
- Status: ok (2026-10-16) · ADS-status: warning
- URL: https://ads.atmosphere.copernicus.eu/datasets/cams-europe-air-quality-forecasts

## What can you do with it?
- **Smog day ahead?.** Fetch the ozone or particulate forecast for a cell over your city and plot the next four days hour by hour.
- **Forecasting hay fever.** The forecast includes grass, birch and alder pollen. Make a map of the Netherlands for the coming days.
- **Forecast versus measurement.** Put yesterday's forecast next to Luchtmeetnet's hourly measurements at the same spot. How far off was the model?
- **Where does the pollution come from?.** Compare the eleven models. Large differences between models mean an uncertain forecast.

## How to process it
1. **Create a free account.** Register with the Atmosphere Data Store, accept this dataset's licence and put your personal token in ~/.cdsapirc.
   `https://ads.atmosphere.copernicus.eu/how-to-api`
2. **See which options exist.** The process description can be read without an account: pollutants, models, heights, time steps and formats.
   `https://ads.atmosphere.copernicus.eu/api/retrieve/v1/processes/cams-europe-air-quality-forecasts`
3. **Request only the Netherlands.** Use area (north, west, south, east) to get a cut-out. Choose level 0 for air at ground level.
   `import cdsapi
cdsapi.Client().retrieve("cams-europe-air-quality-forecasts", {
    "variable": ["nitrogen_dioxide"], "model": ["ensemble"], "level": ["0"],
    "date": ["2026-10-15/2026-10-15"], "type": ["forecast"], "time": ["00:00"],
    "leadtime_hour": [str(h) for h in range(0, 97)],
    "data_format": "netcdf_zip", "area": [53.6, 3.2, 50.7, 7.3]}).download("no2_nl.zip")`
4. **Read the NetCDF file.** Unzip and open the file with xarray. One cell over your city is then a single time series.

## Interesting combinations
- How good is the forecast? → [luchtmeetnet](https://www.opendataindex.nl/en/bron/luchtmeetnet.md) via coördinaten en uur. Find the cell containing a station and compare forecast and measurement hour by hour.
- Europe side by side → [eea-lucht](https://www.opendataindex.nl/en/bron/eea-lucht.md) via coördinaten en uur. The CAMS analysis uses European Environment Agency measurements. Compare them across borders.

## Similar sources
- [luchtmeetnet](https://www.opendataindex.nl/en/bron/luchtmeetnet.md): Actual hourly measurements at Dutch stations, no forecast.
- [rivm-gcn](https://www.opendataindex.nl/en/bron/rivm-gcn.md): Modelled annual mean per kilometre for the Netherlands, much finer but not hourly.
- [CAMS-kaarten (verwachting als plaatje)](https://atmosphere.copernicus.eu/european-air-quality-forecast-plots): The European forecast as ready-made maps, without an account.

## What to watch out for
- **Account and licence needed.** Without an account you can read the catalogue and options, but not download. You also accept the licence (CC BY 4.0) per dataset.
- **A forecast, not a measurement.** Only nitrogen monoxide, nitrogen dioxide, sulphur dioxide, ozone, particulate matter and dust are routinely validated against measurements. CAMS calls the other variables experimental.
- **Too coarse for a single street.** A cell is about 10 by 10 km. Busy streets and motorways disappear into the average.
- **Archive from October 2023.** In the Atmosphere Data Store the forecasts go back to 10 October 2023 (as of 15 October 2026). For 2013 to 2024 there is a separate reanalysis (cams-europe-air-quality-reanalyses).
- **Requests are queued.** You don't get an immediate answer. A request is queued and later yields a file (GRIB or NetCDF in a ZIP).

## API (CADS (OGC API Processes + STAC-catalogus), via cdsapi)

- Base: https://ads.atmosphere.copernicus.eu/api
- Access: Catalogue, process description and costing work without an account (tested). Requesting data needs a free ECMWF account, accepting the dataset licence on the website and your personal key (PRIVATE-TOKEN header, or ~/.cdsapirc with url https://ads.atmosphere.copernicus.eu/api). Without a key: /jobs returns 401 'authentication required'. (tested)
- Formats: netcdf, grib, json
- Size: 16 CAMS datasets in the Atmosphere Data Store (16 Oct 2026); this one: Europe 0.1° (~10 km), 11 models + ensemble, 28 species, since 11 Oct 2023 (tested)
- Filtering: No query language: pick values per option (lists) and optionally a rectangle (area). The result is always a file.; 

### Endpoints
- `GET /catalogue/v1/collections`: All 16 datasets (EAC4 reanalysis, global forecasts, greenhouse gases, solar radiation, GFAS fire emissions …).
- `GET /catalogue/v1/collections/{dataset}`: One dataset as a STAC collection: licence, extent, DOI, links to form, constraints and costing.
- `GET /retrieve/v1/processes/{dataset}`: Which options the request takes, with all allowed values.
- `POST /retrieve/v1/processes/{dataset}/costing`: Computes how 'big' a request is against the limit ({"cost":2.0,"limit":5000.0}); works without an account.
- `POST /retrieve/v1/processes/{dataset}/execution`: Submit a request (asynchronous, key required); body {"inputs": {…}}. Response: a job.
- `GET /retrieve/v1/jobs/{jobId}`: Status of your job (accepted, running, successful); then /results with the download link.
- `GET /catalogue/v1/collections/{dataset}/messages`: Notices about outages or changes of the dataset.

### Parameters
- `variable`: 28 species: nitrogen_dioxide, ozone, particulate_matter_2.5um, particulate_matter_10um, ammonia, dust, pollen (birch, grass, alder …) … (from the description)
- `model`: ensemble (median) or one of 11 models: chimere, dehm, emep, euradim, gemaq, lotos, match, minni, mocage, monarch, silam. (from the description)
- `level`: Height in metres: 0 (ground level), 50, 100, 250, 500, 750, 1000, 2000, 3000, 5000. (from the description)
- `date`: YYYY-MM-DD/YYYY-MM-DD, from 2023-10-11 to today (in the form, not in the process description). (from the description)
- `type`: analysis or forecast. (from the description)
- `time`: Hour in UTC. forecast: only run 00:00 (with leadtime_hour 0–96); analysis: every hour 00:00–23:00 with leadtime_hour 0 (per the constraints). (from the description)
- `leadtime_hour`: Hours ahead, 0 to 96. (from the description)
- `area`: Cut-out in degrees; the Netherlands roughly [53.6, 3.2, 50.7, 7.3]. Without: all of Europe. (from the description)
- `data_format`: grib or netcdf_zip. (from the description)

### File contents (not downloaded ourselves; per the dataset description) (4)
`NetCDF: variabele per stof` getal, `time / step` datetime, `latitude / longitude` getal, `level` getal

### Limits and pitfalls
- Each request has a cost limit of 5,000 (costing). One species, one model, one level, one run and two time steps for the Netherlands costs 2. (tested)
- Requests run asynchronously in a queue; expect minutes, sometimes longer when busy. (from the description)
- Not every combination exists: the rel=constraints link in the catalogue record (60 rules) says per date which models, species, levels and hours exist. Per the description only NO, NO2, SO2, O3, PM2.5, PM10 and dust are regularly validated against observations. (from the description)
- The STAC catalogue is descriptive only: /items and /search return 501 ('STAC items is not implemented'). Data always go through processes/execution. (tested)
- Without accepting the licence on the website a request fails, even with a valid key. (from the description)
- ?limit= on /collections is ignored: you always get all 16. (tested)
- The catalogue field cads:sanity_check tells whether the dataset passed its last check (on 16 Oct 2026: down). (tested)
- Open map images: eccharts.ecmwf.int has a WMS with token=public, but its robots.txt is Disallow: / (not used). (experience)

### Example
```
https://ads.atmosphere.copernicus.eu/api/retrieve/v1/processes/cams-europe-air-quality-forecasts
```

## How to fetch it

### curl
```bash
curl "https://ads.atmosphere.copernicus.eu/api/catalogue/v1/collections/cams-europe-air-quality-forecasts"
```

### python
```python
import cdsapi  # pip install cdsapi; sleutel in ~/.cdsapirc

cdsapi.Client().retrieve("cams-europe-air-quality-forecasts", {
    "variable": ["particulate_matter_2.5um"], "model": ["ensemble"], "level": ["0"],
    "date": ["2026-10-15/2026-10-15"], "type": ["forecast"], "time": ["00:00"],
    "leadtime_hour": ["0", "24", "48", "72", "96"],
    "data_format": "netcdf_zip", "area": [53.6, 3.2, 50.7, 7.3]}).download("pm25_nl.zip")
```

## Who uses it
- [CAMS-kaarten](https://atmosphere.copernicus.eu/european-air-quality-forecast-plots): Daily maps of the European air quality forecast


## Examples with real figures (1)

For each example the finding, the reference date and the call that fetches exactly these figures.

- **Fine particulate matter (PM2.5) at urban background stations in the Netherlands: lower than in 14 of the 26 other EU countries** (2025). Table AirQualityStatistics. [Example](https://www.opendataindex.nl/en/voorbeeld/eea-pm25-eu)
  - `https://discodata.eea.europa.eu/sql?query=SELECT%20RTRIM(CountryCode)%20land%2C%20YearOfStatistics%20jaar%2C%20COUNT(*)%20n%2C%20AVG(AirPollutionLevel)%20gem%20FROM%20%5BAirQualityDataFlows%5D.%5Blatest%5D.%5BAirQualityStatistics%5D%20WHERE%20AirPollutant%3D'PM2.5'%20AND%20DataAggregationProcessId%3D'P1Y'%20AND%20DataCovFilterYN%3D'Yes'%20AND%20SourceDataFlow%20LIKE%20'E1a%25'%20AND%20AirQualityStationType%3D'Background'%20AND%20AirQualityStationArea%3D'Urban'%20AND%20YearOfStatistics%20%3E%3D%202022%20GROUP%20BY%20CountryCode%2C%20YearOfStatistics`
