Skip to article frontmatterSkip to article content

ECMWF Open Data


Currently, open data are available from the following locations:

  • ECMWF Data Store offers data sets from current date and up to four days before today.
File-naming convention

An example (for HH=00z and stream=oper) shows how a filename has changed since year 2023

  • 20230118/00z/0p4-beta/oper/20230118000000-0h-oper-fc.grib2,
    in Februar 2024 one can choose between the resolution 0p4-beta and 0p25
  • 20240201/00z/0p4-beta\color{red}{\text{0p4-beta}}/oper/20240201000000-0h-oper-fc.grib2
  • 20240201/00z/0p25\color{red}{\text{0p25}}/oper/20240201000000-0h-oper-fc.grib2,
    in March 2024 one can choose between aifs and ifs (only ifs is available in 0p4-beta or 0p25)
  • 20240301/00z/aifs\color{red}{\text{aifs}}/0p25/oper/20240301000000-0h-oper-fc.grib2
  • 20240301/00z/ifs\color{red}{\text{ifs}}/0p4-beta/oper/20240301000000-0h-oper-fc.grib2,
    in February 2025 one can choose between aifs-single and aifs (ifs does not contain any changes)
  • 20250210/00z/aifs-single\color{red}{\text{aifs-single}}/0p25/experimental\color{red}{\text{experimental}}/oper/20250210000000-0h-oper-fc.grib2
  • 20250210/00z/aifs\color{red}{\text{aifs}}/0p25/oper/20250210000000-0h-oper-fc.grib2,
    in March 2025 the file-naming convention is the same as we know it today
  • 20250301/00z/aifs-single/0p25/oper/20250301000000-0h-oper-fc.grib2
  • 20250301/00z/ifs/0p25/oper/20250301000000-0h-oper-fc.grib2

The example below shows how to download yesterday’s data.

!pip3 install pystac_client planetary_computer
!pip3 install xarray cfgrib
import pystac_client
import planetary_computer
import requests
import xarray as xr

catalog = pystac_client.Client.open(
    "https://planetarycomputer.microsoft.com/api/stac/v1",
    modifier=planetary_computer.sign_inplace,
)
search = catalog.search(
    collections=["ecmwf-forecast"],
    query={
        "ecmwf:stream": {"eq": "oper"},
        "ecmwf:type": {"eq": "fc"},
        "ecmwf:step": {"eq": "0h"},
    },
)
items = search.item_collection()

# select the most recent item
item = max(items, key=lambda item: item.datetime)

url = item.assets["data"].href
url_ecmwf = url.rpartition('?')[0]
filename = str(url_ecmwf.rpartition('oper/')[-1])

r = requests.get(url, stream=True)
with open(filename, mode="wb") as file:
    for chunk in r.iter_content(chunk_size=10 * 1024):
        file.write(chunk)

ds = xr.open_dataset(f'./{filename}', engine='cfgrib',
                     decode_timedelta=True,
                     backend_kwargs={'filter_by_keys': {'typeOfLevel': 'soilLayer'}})
ds
Loading...
Retrieve data for the date range of your interest
import pystac_client
import planetary_computer
import requests
import earthkit.data as ekd

catalog = pystac_client.Client.open(
    "https://planetarycomputer.microsoft.com/api/stac/v1",
    modifier=planetary_computer.sign_inplace,
)
# specify a date range
time_range = "2025-06-01/2025-06-01"

search = catalog.search(
    collections=["ecmwf-forecast"],
    query={
        "ecmwf:stream": {"eq": "oper"},
        "ecmwf:type": {"eq": "fc"},
        "ecmwf:step": {"eq": "0h"},
        },
    datetime=time_range)
items = search.item_collection()

list_of_files = []
for item in items:
    url = item.assets["data"].href
    url_ecmwf = url.rpartition('?')[0]
    filename = str(url_ecmwf.rpartition('oper/')[-1])
    
    r = requests.get(url, stream=True)
    with open(filename, mode="wb") as file:
        for chunk in r.iter_content(chunk_size=10 * 1024):
            file.write(chunk)
    list_of_files.append(filename)

for file_ in list_of_files:
    print(f'File: {file_}')
    ds = ekd.from_source("file", f'./{file_}')
    ds.ls()
  • Open-Meteo offers a diverse range of APIs that will suit your weather-related needs:
  1. ECMWF Weather Forecast API
!pip3 install openmeteo-requests
!pip3 install requests-cache retry-requests numpy pandas
import openmeteo_requests
import pandas as pd
import requests_cache
from retry_requests import retry

# Setup the Open-Meteo API client with cache and retry on error
cache_session = requests_cache.CachedSession('.cache', expire_after = 3600)
retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2)
openmeteo = openmeteo_requests.Client(session = retry_session)

# Make sure all required weather variables are listed here
# The order of variables in hourly or daily is important to assign them correctly below
url = "https://api.open-meteo.com/v1/forecast"
params = {
	"latitude": 52.52,
	"longitude": 13.41,
	"hourly": "temperature_2m",
	"models": "ecmwf_ifs025"
}
responses = openmeteo.weather_api(url, params=params)

# Process first location. Add a for-loop for multiple locations or weather models
response = responses[0]
model = "IFS" if response.Model() == 60 else "AIFS"
print(f"ECMWF {model} model")
print(f"Coordinates {response.Latitude()}°N {response.Longitude()}°E")
print(f"Elevation {response.Elevation()} m asl")
print(f"Timezone {response.Timezone()}{response.TimezoneAbbreviation()}")
print(f"Timezone difference to GMT+0 {response.UtcOffsetSeconds()} s")

# Process hourly data. The order of variables needs to be the same as requested.
hourly = response.Hourly()
hourly_temperature_2m = hourly.Variables(0).ValuesAsNumpy()

hourly_data = {"date": pd.date_range(
	start = pd.to_datetime(hourly.Time(), unit = "s", utc = True),
	end = pd.to_datetime(hourly.TimeEnd(), unit = "s", utc = True),
	freq = pd.Timedelta(seconds = hourly.Interval()),
	inclusive = "left"
)}

hourly_data["temperature_2m"] = hourly_temperature_2m

hourly_dataframe = pd.DataFrame(data = hourly_data)
hourly_dataframe.to_csv(f'{model}_lat{response.Latitude()}-lon{response.Longitude()}_hourly.csv', index=False)
print(hourly_dataframe)
ECMWF IFS model
Coordinates 52.5°N 13.5°E
Elevation 38.0 m asl
Timezone NoneNone
Timezone difference to GMT+0 0 s
                         date  temperature_2m
0   2025-06-20 00:00:00+00:00       13.850000
1   2025-06-20 01:00:00+00:00       12.800000
2   2025-06-20 02:00:00+00:00       11.800000
3   2025-06-20 03:00:00+00:00       11.450000
4   2025-06-20 04:00:00+00:00       12.000000
..                        ...             ...
163 2025-06-26 19:00:00+00:00       26.250000
164 2025-06-26 20:00:00+00:00       25.450001
165 2025-06-26 21:00:00+00:00       24.700001
166 2025-06-26 22:00:00+00:00       23.950001
167 2025-06-26 23:00:00+00:00       23.250000

[168 rows x 2 columns]
  1. Forecast API allows you to access accurate weather forecasts for up to 16 days.
Example of three weather variables and two models for different locations
import openmeteo_requests
import pandas as pd
import requests_cache
from retry_requests import retry

# Setup the Open-Meteo API client with cache and retry on error
cache_session = requests_cache.CachedSession('.cache', expire_after = 3600)
retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2)
openmeteo = openmeteo_requests.Client(session = retry_session)

# Make sure all required weather variables are listed here
# The order of variables in hourly or daily is important to assign them correctly below
url = "https://api.open-meteo.com/v1/forecast"
params = {
	"latitude": [51.51, 55.95, 53.35],
	"longitude": [0.13, 3.19, 6.26],
	"daily": ["rain_sum", "precipitation_sum", "cloud_cover_mean"],
	"models": ["ecmwf_ifs025", "ecmwf_aifs025_single"],
	"timezone": "auto",
	"start_date": "2025-06-10",
	"end_date": "2025-06-17"
}
responses = openmeteo.weather_api(url, params=params)

# Process first location. Add a for-loop for multiple locations or weather models
for r in range(len(responses)):
    response = responses[r]
    model = "IFS" if response.Model() == 60 else "AIFS"
    print(f"ECMWF {model} model")
    print(f"Coordinates {response.Latitude()}°N {response.Longitude()}°E")
    print(f"Elevation {response.Elevation()} m asl")
    print(f"Timezone {response.Timezone()}{response.TimezoneAbbreviation()}")
    print(f"Timezone difference to GMT+0 {response.UtcOffsetSeconds()} s")

    # Process daily data. The order of variables needs to be the same as requested.
    daily = response.Daily()
    daily_rain_sum = daily.Variables(0).ValuesAsNumpy()
    daily_precipitation_sum = daily.Variables(1).ValuesAsNumpy()
    daily_cloud_cover_mean = daily.Variables(2).ValuesAsNumpy()

    daily_data = {"date": pd.date_range(
            start = pd.to_datetime(daily.Time(), unit = "s", utc = True),
            end = pd.to_datetime(daily.TimeEnd(), unit = "s", utc = True),
            freq = pd.Timedelta(seconds = daily.Interval()),
            inclusive = "left"
    )}

    daily_data["rain_sum"] = daily_rain_sum
    daily_data["precipitation_sum"] = daily_precipitation_sum
    daily_data["cloud_cover_mean"] = daily_cloud_cover_mean

    daily_dataframe = pd.DataFrame(data = daily_data)
    daily_dataframe.to_csv(f'{model}_lat{response.Latitude()}-lon{response.Longitude()}_daily.csv', index=False)
    print(daily_dataframe)

Table 1:The Forecast API provides you with data of ECMWF IFS and AIFS models.

Forecast Data ProviderModelResolutionForecast LengthFrequency
ECMWFIFS & AIFS25 km15 daysEvery 6 hours
  1. Historical Weather API provides access to archived ECMWF IFS model data from 2017 onwards or ECMWF IFS Assimilation Long-Window model data that are available since 2024.
Historical Data - IFS model

An example of retrieving open data from ECMWF IFS model for two sites: Port Elizabeth (33.91˚S, 25.58 °E) and East London (33.02˚S, 27.91°E), for year 2017. The selected daily weather variables are Maximum Wind Speed (10 m), Maximum Wind Gust (10 m), Mean Wind Gusts (10 m), and Mean Wind Speed (10 m). For hourly weather variables, we chose Wind Speed (10 m), Wind Speed (100 m), and Wind Gusts (10 m).

import openmeteo_requests
import pandas as pd
import requests_cache
from retry_requests import retry

# Setup the Open-Meteo API client with cache and retry on error
cache_session = requests_cache.CachedSession('.cache', expire_after = -1)
retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2)
openmeteo = openmeteo_requests.Client(session = retry_session)

# Make sure all required weather variables are listed here
# The order of variables in hourly or daily is important to assign them correctly below
url = "https://archive-api.open-meteo.com/v1/archive"
params = {
	"latitude": [33.91, 33.02],
	"longitude": [25.58, 27.91],
	"start_date": "2017-01-01",
	"end_date": "2017-12-31",
	"daily": ["wind_speed_10m_max", "wind_gusts_10m_max", "wind_gusts_10m_mean", "wind_speed_10m_mean"],
	"hourly": ["wind_speed_10m", "wind_gusts_10m", "wind_speed_100m"],
	"models": "ecmwf_ifs",
	"timezone": "auto"
}
responses = openmeteo.weather_api(url, params=params)

# Process first location. Add a for-loop for multiple locations or weather models
for r in range(len(responses)):
    response = responses[r]
    model = "IFS" if response.Model() == 60 else "AIFS"
    print(f"Coordinates {response.Latitude()}°N {response.Longitude()}°E")
    print(f"Elevation {response.Elevation()} m asl")
    print(f"Timezone {response.Timezone()}{response.TimezoneAbbreviation()}")
    print(f"Timezone difference to GMT+0 {response.UtcOffsetSeconds()} s")

    # Process hourly data. The order of variables needs to be the same as requested.
    hourly = response.Hourly()
    hourly_wind_speed_10m = hourly.Variables(0).ValuesAsNumpy()
    hourly_wind_gusts_10m = hourly.Variables(1).ValuesAsNumpy()
    hourly_wind_speed_100m = hourly.Variables(2).ValuesAsNumpy()

    hourly_data = {"date": pd.date_range(
            start = pd.to_datetime(hourly.Time(), unit = "s", utc = True),
            end = pd.to_datetime(hourly.TimeEnd(), unit = "s", utc = True),
            freq = pd.Timedelta(seconds = hourly.Interval()),
            inclusive = "left"
    )}

    hourly_data["wind_speed_10m"] = hourly_wind_speed_10m
    hourly_data["wind_gusts_10m"] = hourly_wind_gusts_10m
    hourly_data["wind_speed_100m"] = hourly_wind_speed_100m

    hourly_dataframe = pd.DataFrame(data = hourly_data)
    hourly_dataframe.to_csv(f'{model}_lat{response.Latitude()}-lon{response.Longitude()}_hourly.csv', index=False) 
    print(hourly_dataframe)

    # Process daily data. The order of variables needs to be the same as requested.
    daily = response.Daily()
    daily_wind_speed_10m_max = daily.Variables(0).ValuesAsNumpy()
    daily_wind_gusts_10m_max = daily.Variables(1).ValuesAsNumpy()
    daily_wind_gusts_10m_mean = daily.Variables(2).ValuesAsNumpy()
    daily_wind_speed_10m_mean = daily.Variables(3).ValuesAsNumpy()

    daily_data = {"date": pd.date_range(
            start = pd.to_datetime(daily.Time(), unit = "s", utc = True),
            end = pd.to_datetime(daily.TimeEnd(), unit = "s", utc = True),
            freq = pd.Timedelta(seconds = daily.Interval()),
            inclusive = "left"
    )}

    daily_data["wind_speed_10m_max"] = daily_wind_speed_10m_max
    daily_data["wind_gusts_10m_max"] = daily_wind_gusts_10m_max
    daily_data["wind_gusts_10m_mean"] = daily_wind_gusts_10m_mean
    daily_data["wind_speed_10m_mean"] = daily_wind_speed_10m_mean

    daily_dataframe = pd.DataFrame(data = daily_data)
    daily_dataframe.to_csv(f'{model}_lat{response.Latitude()}-lon{response.Longitude()}_daily.csv', index=False)
    print(daily_dataframe)

Table 2:The Historical Weather API allows users to retrieve historical weather data of ECMWF IFS (using simulation runs at 00z and 12z,) and IFS Assimilation Long-Window models for a specific location and time period.

Forecast Data ProviderModelSpatial ResolutionTemporal ResolutionFrequencyAvailable Since
ECMWFIFS9 kmhourlydaily with 2 days delay2017 to present
ECMWFIFS Assimilation Long-Window9 km6-hourlydaily with 2 days delay2024 to present

Historical Forecast API provides access to archived model data from the ECMWF Weather Forecast API.

Historical Data - IFS & AIFS

An example of retrieving open data from ECMWF IFS and AIFS models for Bern (46.95° N, 7.45° E) from 2024-02-03 to 2025-06-15. The selected daily weather variables are Maximum Temperature (2 m), Minimum Temperature (2 m), and Mean Temperature (2 m). For hourly weather variables, we chose Temperature (2 m), Rain, and Snowfall.

import openmeteo_requests
import pandas as pd
import requests_cache
from retry_requests import retry

# Setup the Open-Meteo API client with cache and retry on error
cache_session = requests_cache.CachedSession('.cache', expire_after = 3600)
retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2)
openmeteo = openmeteo_requests.Client(session = retry_session)

# Make sure all required weather variables are listed here
# The order of variables in hourly or daily is important to assign them correctly below
url = "https://historical-forecast-api.open-meteo.com/v1/forecast"
params = {
	"latitude": 46.95,
	"longitude": 7.45,
	"start_date": "2024-02-03",
	"end_date": "2025-06-15",
	"daily": ["temperature_2m_max", "temperature_2m_mean", "temperature_2m_min"],
	"hourly": ["rain", "snowfall", "temperature_2m"],
	"models": ["ecmwf_ifs025", "ecmwf_aifs025_single"],
	"timezone": "auto"
}
responses = openmeteo.weather_api(url, params=params)

# Process first location. Add a for-loop for multiple locations or weather models
for r in range(len(responses)):
    response = responses[r]
    model = "IFS" if response.Model() == 60 else "AIFS"
    print(f"ECMWF {model} model")
    print(f"Coordinates {response.Latitude()}°N {response.Longitude()}°E")
    print(f"Elevation {response.Elevation()} m asl")
    print(f"Timezone {response.Timezone()}{response.TimezoneAbbreviation()}")
    print(f"Timezone difference to GMT+0 {response.UtcOffsetSeconds()} s")

    # Process hourly data. The order of variables needs to be the same as requested.
    hourly = response.Hourly()
    hourly_rain = hourly.Variables(0).ValuesAsNumpy()
    hourly_snowfall = hourly.Variables(1).ValuesAsNumpy()
    hourly_temperature_2m = hourly.Variables(2).ValuesAsNumpy()

    hourly_data = {"date": pd.date_range(
            start = pd.to_datetime(hourly.Time(), unit = "s", utc = True),
            end = pd.to_datetime(hourly.TimeEnd(), unit = "s", utc = True),
            freq = pd.Timedelta(seconds = hourly.Interval()),
            inclusive = "left"
    )}

    hourly_data["rain"] = hourly_rain
    hourly_data["snowfall"] = hourly_snowfall
    hourly_data["temperature_2m"] = hourly_temperature_2m

    hourly_dataframe = pd.DataFrame(data = hourly_data)
    hourly_dataframe.to_csv(f'{model}_lat{response.Latitude()}-lon{response.Longitude()}_hourly.csv', index=False)
    print(hourly_dataframe)

    # Process daily data. The order of variables needs to be the same as requested.
    daily = response.Daily()
    daily_temperature_2m_max = daily.Variables(0).ValuesAsNumpy()
    daily_temperature_2m_mean = daily.Variables(1).ValuesAsNumpy()
    daily_temperature_2m_min = daily.Variables(2).ValuesAsNumpy()

    daily_data = {"date": pd.date_range(
            start = pd.to_datetime(daily.Time(), unit = "s", utc = True),
            end = pd.to_datetime(daily.TimeEnd(), unit = "s", utc = True),
            freq = pd.Timedelta(seconds = daily.Interval()),
            inclusive = "left"
    )}

    daily_data["temperature_2m_max"] = daily_temperature_2m_max
    daily_data["temperature_2m_mean"] = daily_temperature_2m_mean
    daily_data["temperature_2m_min"] = daily_temperature_2m_min

    daily_dataframe = pd.DataFrame(data = daily_data)
    daily_dataframe.to_csv(f'{model}_lat{response.Latitude()}-lon{response.Longitude()}_daily.csv', index=False)
    print(daily_dataframe)

Table 3:The Historical Forecast API archives comprehensive data of ECMWF IFS and AIFS models, data is available since 2022.

Forecast Data ProviderModelSpatial ResolutionTemporal ResolutionFrequencyAvailable Since
IFS 0.4°0.4° (~44 km)3-hourly2022-11-07
ECMWFIFS 0.25°0.25° (~25 km)3-hourlyevery 6 hours2024-02-03
AIFS 0.25° Single0.25° (~25 km)6-hourly2025-02-25
  1. Ensemble Models API
ECMWF IFS 0.25° Ensemble

An example shows how to retrieve weather data of daily mean temperature (2 m) and daily mean wind speed (10 m) in Berlin in the past 3 months.

https://ensemble-api.open-meteo.com/v1/ensemble?latitude=52.52&longitude=13.41&daily=temperature_2m_mean,wind_speed_10m_mean&models=ecmwf_ifs025&timezone=Europe%2FBerlin&past_days=92
import openmeteo_requests
from openmeteo_sdk.Variable import Variable
from openmeteo_sdk.Aggregation import Aggregation
import pandas as pd
import requests_cache
from retry_requests import retry

# Setup the Open-Meteo API client with cache and retry on error
cache_session = requests_cache.CachedSession('.cache', expire_after = 3600)
retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2)
openmeteo = openmeteo_requests.Client(session = retry_session)

# Make sure all required weather variables are listed here
# The order of variables in hourly or daily is important to assign them correctly below
url = "https://ensemble-api.open-meteo.com/v1/ensemble"
params = {
	"latitude": 52.52,
	"longitude": 13.41,
	"daily": ["temperature_2m_mean", "wind_speed_10m_mean"],
	"models": "ecmwf_ifs025",
	"timezone": "Europe/Berlin",
	"past_days": 92
}
responses = openmeteo.weather_api(url, params=params)

# Process first location. Add a for-loop for multiple locations or weather models
response = responses[0]
model = "IFS" if response.Model() == 60 else "AIFS"
print(f"ECMWF {model} model")
print(f"Coordinates {response.Latitude()}°N {response.Longitude()}°E")
print(f"Elevation {response.Elevation()} m asl")
print(f"Timezone {response.Timezone()}{response.TimezoneAbbreviation()}")
print(f"Timezone difference to GMT+0 {response.UtcOffsetSeconds()} s")

# Process daily data
daily = response.Daily()
daily_variables = list(map(lambda i: daily.Variables(i), range(0, daily.VariablesLength())))
daily_temperature_2m_mean = filter(lambda x: x.Variable() == Variable.temperature and x.Altitude() == 2 and x.Aggregation() == Aggregation.mean, daily_variables)
daily_wind_speed_10m_mean = filter(lambda x: x.Variable() == Variable.wind_speed and x.Altitude() == 10 and x.Aggregation() == Aggregation.mean, daily_variables)

daily_data = {"date": pd.date_range(
	start = pd.to_datetime(daily.Time(), unit = "s", utc = True),
	end = pd.to_datetime(daily.TimeEnd(), unit = "s", utc = True),
	freq = pd.Timedelta(seconds = daily.Interval()),
	inclusive = "left"
)}

# Process all members
for variable in daily_temperature_2m_mean:
	member = variable.EnsembleMember()
	daily_data[f"temperature_2m_mean_member{member}"] = variable.ValuesAsNumpy()
for variable in daily_wind_speed_10m_mean:
	member = variable.EnsembleMember()
	daily_data[f"wind_speed_10m_mean_member{member}"] = variable.ValuesAsNumpy()

daily_dataframe = pd.DataFrame(data = daily_data)
daily_dataframe.to_csv(f'{model}_lat{response.Latitude()}-lon{response.Longitude()}_daily.csv', index=False)
print(daily_dataframe)
  1. Copyright Statement: Copyright “© 2025 European Centre for Medium-Range Weather Forecasts (ECMWF)”.

  2. Source: www.ecmwf.int

  3. Licence Statement: This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/

  4. Disclaimer: ECMWF does not accept any liability whatsoever for any error or omission in the data, their availability, or for any loss or damage arising from their use.