Python client for the ENTSO-E Transparency Platform API.
Typed, namespace-organized access to European electricity market data — load, prices, generation, transmission, and balancing.
pip install python-entsoefrom entsoe import Client
client = Client() # reads ENTSOE_API_KEY from environment
df = client.load.actual("2024-06-01", "2024-06-08", country="FR")Strings are interpreted as timestamps in Europe/Brussels (CET — the ENTSO-E standard). You can override this per-client or pass pd.Timestamp objects directly:
client = Client(tz="UTC") # override default timezone
# pd.Timestamp still works — its timezone takes priority
import pandas as pd
start = pd.Timestamp("2024-06-01", tz="Europe/Paris")
df = client.load.actual(start, "2024-06-08", country="FR")Every method returns a pandas.DataFrame with a timestamp column (UTC) and a value column.
Get a free API key at https://transparency.entsoe.eu/ (register, then request a token via email).
Set it as an environment variable:
export ENTSOE_API_KEY=your-token-hereOr pass it directly:
client = Client(api_key="your-token-here")| Method | Description | Parameters |
|---|---|---|
actual(start, end, country) |
Actual total system load | country: ISO code (e.g., "FR") |
forecast(start, end, country) |
Day-ahead load forecast | country: ISO code |
df = client.load.actual(start, end, country="FR")
df = client.load.forecast(start, end, country="FR")| Method | Description | Parameters |
|---|---|---|
day_ahead(start, end, country) |
Day-ahead market prices (EUR/MWh) | country: ISO code |
df = client.prices.day_ahead(start, end, country="FR")
# Returns: timestamp, value, currency, price_unit| Method | Description | Parameters |
|---|---|---|
actual(start, end, country, psr_type=None) |
Actual generation per type | psr_type: filter by fuel (optional) |
forecast(start, end, country, psr_type=None) |
Wind & solar forecast | psr_type: filter by fuel (optional) |
installed_capacity(start, end, country, psr_type=None) |
Installed capacity per type | psr_type: filter by fuel (optional) |
per_plant(start, end, country, psr_type=None) |
Generation per production unit | psr_type: filter by fuel (optional) |
# All generation types
df = client.generation.actual(start, end, country="FR")
# Solar only
df = client.generation.actual(start, end, country="FR", psr_type="B16")
# Installed capacity
df = client.generation.installed_capacity(start, end, country="FR")| Method | Description | Parameters |
|---|---|---|
crossborder_flows(start, end, country_from, country_to) |
Physical cross-border flows | Two country codes |
scheduled_exchanges(start, end, country_from, country_to) |
Scheduled commercial exchanges | Two country codes |
net_transfer_capacity(start, end, country_from, country_to) |
Day-ahead NTC | Two country codes |
df = client.transmission.crossborder_flows(
start, end, country_from="FR", country_to="ES"
)| Method | Description | Parameters |
|---|---|---|
imbalance_prices(start, end, country) |
System imbalance prices | country: ISO code |
imbalance_volumes(start, end, country) |
System imbalance volumes | country: ISO code |
df = client.balancing.imbalance_prices(start, end, country="FR")Use standard ISO codes. Some bidding zones have specific codes:
| Code | Area |
|---|---|
FR |
France |
DE_LU |
Germany/Luxembourg (bidding zone) |
ES |
Spain |
NL |
Netherlands |
BE |
Belgium |
IT |
Italy |
IT_NORTH |
Italy North |
NO_1 .. NO_5 |
Norway zones |
SE_1 .. SE_4 |
Sweden zones |
DK_1, DK_2 |
Denmark zones |
Note: For day-ahead prices and balancing data, use
DE_LUinstead ofDE. See data availability notes for details.
Full list of 60+ supported areas in _mappings.py.
Use PSR codes to filter generation by fuel type:
| Code | Fuel Type |
|---|---|
B01 |
Biomass |
B04 |
Fossil Gas |
B05 |
Fossil Hard coal |
B06 |
Fossil Oil |
B10 |
Hydro Pumped Storage |
B11 |
Hydro Run-of-river |
B12 |
Hydro Water Reservoir |
B14 |
Nuclear |
B16 |
Solar |
B18 |
Wind Offshore |
B19 |
Wind Onshore |
Full list in _mappings.py. Human-readable names available via:
from entsoe._mappings import PSR_TYPES
print(PSR_TYPES["B16"]) # "Solar"All start and end parameters accept date strings or tz-aware pd.Timestamp objects:
# Simple — just strings (uses client's default tz: Europe/Brussels)
df = client.load.actual("2024-01-01", "2024-01-07", country="FR")
# pd.Timestamp with explicit timezone — takes priority over default
df = client.load.actual(
pd.Timestamp("2024-01-01", tz="Europe/Paris"),
pd.Timestamp("2024-01-07", tz="Europe/Paris"),
country="FR",
)
# Mixing is fine
df = client.load.actual("2024-01-01", pd.Timestamp("2024-01-07", tz="UTC"), country="FR")
# Naive pd.Timestamp (no tz) — still raises InvalidParameterError
start = pd.Timestamp("2024-01-01") # ← no tz, will errorReturned timestamps are always in UTC.
- Autocomplete-friendly — type
client.and see all domains, then drill into methods - Automatic year-splitting — requests spanning more than 1 year are split transparently
- ZIP handling — endpoints returning compressed responses are decompressed automatically
- Retry with backoff — rate-limited requests (HTTP 429) are retried with exponential backoff
- Clear errors —
NoDataError,InvalidParameterError,RateLimitErrorwith descriptive messages
from entsoe import Client, NoDataError, InvalidParameterError
client = Client()
try:
df = client.prices.day_ahead(start, end, country="FR")
except NoDataError:
print("No data available for this period")
except InvalidParameterError as e:
print(f"Bad parameters: {e}")See the examples/ directory for Jupyter notebooks with plotly visualizations:
load.ipynb— actual load, forecast comparison, multi-country profilesprices.ipynb— day-ahead prices, distributions, hourly heatmapgeneration.ipynb— generation mix, solar vs wind, installed capacitytransmission.ipynb— cross-border flows, bidirectional charts, NTCbalancing.ipynb— imbalance prices, multi-country, distribution
git clone https://github.com/jsulopzs/python-entsoe.git
cd python-entsoe
uv sync
# Run tests (requires ENTSOE_API_KEY in .env)
uv run pytest tests/ -v
# Regenerate example notebooks
uv run python scripts/generate_notebooks.pyMIT