The Transparency Platform gives you load, generation, prices and outages for every European bidding zone, for free. The official documentation tells you what the endpoints are. It does not tell you what breaks. This page covers both.
Access is free but not automatic, and it is a two-step process that catches everyone out the first time.
transparency@entsoe.eu with the subject "Restful API access", and put the e-mail address of the account you just registered in the body.The token is a 36-character UUID. It goes into every request as the securityToken query parameter. There is no OAuth, no refresh, no expiry that we have ever hit.
There is exactly one endpoint. Everything is a GET with query parameters, and the response is XML.
https://web-api.tp.entsoe.eu/api
?securityToken={TOKEN}
&documentType=A69
&processType=A01
&in_Domain=10YCZ-CEPS-----N
&periodStart=202609010000
&periodEnd=202609020000Four things decide what you get back: documentType (what kind of data), processType (forecast or realised), the domain parameter (which bidding zone or control area) and the time window.
Timestamps are UTC, formatted yyyyMMddHHmm. There is no timezone suffix and no way to ask for local time. If your market runs on CET/CEST, you convert on your side — and you handle the two days a year when that offset changes.
The full list has over eighty entries, most of which you will never touch. These are the ones that carry the data people actually build on.
| Code | What it returns | Typical use |
|---|---|---|
A44 | Day-ahead prices | Price series per bidding zone; the reference every spread is measured against. |
A65 | Total load | processType=A01 for the forecast, A16 for the realised value. |
A69 | Wind and solar forecast | A01 day-ahead, A18 intraday. The intraday one is fresher and far less used. |
A71 | Generation forecast | Total scheduled generation. |
A75 | Actual generation per production type | The realised counterpart to A69; how you measure forecast error. |
A80 | Generation unit unavailability | Outages. Published as urgent market messages — the interesting one for price spikes. |
A85 | Imbalance prices | Per imbalance settlement period. |
A86 | Imbalance volumes | System imbalance quantity. |
A83 | Activated balancing energy | aFRR / mFRR volumes by direction. |
The same document type means different things depending on processType: A01 day-ahead, A16 realised, A18 intraday total, A40 intraday process, A51 aFRR, A47 mFRR, A52 FCR. Getting this wrong returns valid XML with the wrong meaning, which is worse than an error.
Domains are EIC codes. Depending on the document type the parameter is in_Domain, outBiddingZone_Domain or controlArea_Domain — the documentation specifies which per document, and using the wrong one returns an empty response rather than an error.
| Zone | EIC code |
|---|---|
| Czech Republic | 10YCZ-CEPS-----N |
| Germany–Luxembourg | 10Y1001A1001A82H |
| Austria | 10YAT-APG------L |
| Slovakia | 10YSK-SEPS-----K |
| Poland | 10YPL-AREA-----S |
| Netherlands | 10YNL----------L |
| Hungary | 10YHU-MAVIR----U |
| France | 10YFR-RTE------C |
The XML nests a TimeSeries per series, a Period per contiguous block and a Point per value. Points carry a position, not a timestamp — you reconstruct the time from the period start and the resolution (PT60M, PT15M, sometimes PT30M).
import xml.etree.ElementTree as ET
import requests
NS = {"ns": "urn:iec62325.351:tc57wg16:451-6:generationloaddocument:3:0"}
def series(token, doc, proc, zone, start, end):
r = requests.get("https://web-api.tp.entsoe.eu/api", timeout=60, params={
"securityToken": token, "documentType": doc, "processType": proc,
"in_Domain": zone, "periodStart": start, "periodEnd": end})
r.raise_for_status()
root = ET.fromstring(r.content)
for ts in root.findall(".//ns:TimeSeries", NS):
for period in ts.findall(".//ns:Period", NS):
begin = period.find("ns:timeInterval/ns:start", NS).text
step = period.find("ns:resolution", NS).text
last = None
for point in period.findall("ns:Point", NS):
pos = int(point.find("ns:position", NS).text)
val = float(point.find("ns:quantity", NS).text)
yield begin, step, pos, val
last = posThe XML namespace differs between document families — generation and load use one, publication and balancing documents use others. Hard-coding one namespace works until the day you query a different document type. Read it off the root element instead.
429 with no retry-after worth trusting. Pace your backfills and fetch month by month rather than day by day.Acknowledgement_MarketDocument with a Reason code and text. Code 999 means "no matching data found" and is not a bug. Parse it; do not treat any 200 as success.This one is not in the documentation and it silently ruins quantitative work.
Forecasts are versioned, and the API serves you the latest version. When you download the day-ahead wind forecast for a date last year, you get the best forecast that existed for that date — not the forecast that was published before the market closed. If you feed that into a backtest, your model is trained on information nobody had at the time, and the results will look excellent and mean nothing.
There are only two honest ways around it: archive every forecast yourself at the moment it is published and backtest against your own archive, or treat forecast-derived features as unusable for anything but explanation. We learned this the expensive way.
The entsoe-py library wraps the endpoint and returns pandas objects. For exploration and one-off analysis it will save you an afternoon, and we recommend starting there.
For anything running unattended we ended up writing our own thin client, for three reasons: we needed control over retries and caching, we wanted the raw XML kept for audit, and we needed document types the wrapper did not cover. The endpoint is simple enough that a purpose-built client is a few hundred lines.
A rough rule: a wrapper for research, your own client for anything whose output somebody trades on.
If you want a starting point rather than a blank file, we published the thin version: entsoe-quickstart — one file, standard library only, MIT. It handles the three things listed above that a naive script gets wrong: per-period resolution, multi-day responses, and errors that arrive as XML with HTTP 200.
The Transparency Platform is a regulatory publication, not a trading feed. It is late by design, it is aggregated, and it says nothing about the order book.
For intraday work you need the exchange itself. In the Czech market that means OTE’s AMQP interface for the live XBID order book, authenticated with a client certificate — a different protocol, a different data model and a different set of problems. National TSOs publish their own faster feeds too: ČEPS for the Czech system imbalance, and regelleistung.net for German reserve auctions.
ENTSO-E is the right source for anything cross-border, comparative or historical. It is the wrong source for anything you need within the quarter hour.
We run ENTSO-E, OTE and ČEPS feeds in production — live dashboards, AMQP bridges to the intraday order book, and backtesting that respects publication times. If you need an integration that holds up under real trading, talk to us.
Get in touch