Plants, units and portfolios

The power plants, their units and the market portfolios, as entities with cards, lists and their own timeseries.

A plant is a power plant at one location. A unit is one machine or group of machines inside it: a generator, a wind turbine, a battery. A portfolio is a group of plants that a market registers and settles as one. All three are entities, so every call in this API accepts them where it accepts a zone: the entity call, the timeseries listing, metadata and values.

KindExample idParentChildren
plantplant:11WD4SAEK2HS---Sa price zone or an areaits units
unitunit:11WD4SAEK2HD4--9its plant—
portfolioportfolio:bmu-2__AMRCY000a price zone or an area— (its plants are a field, see below)

A portfolio's portfolio_kind says what kind of grouping it is:

portfolio_kindIsPlants linked
aggregatean ENTSO-E production unit that is several plants under one registration: a hydro cascade, a developer's cluster of wind farms, a stacked solar siteyes, where the crosswalk has them (about a third today)
aggregatora party that trades or balances other owners' plants as one unit: an ENTSO-E aggregator's trading unit, an Elexon aggregatorrarely
virtualan Elexon virtual lead party BM unit (V__), a virtual power plantno: Elexon publishes no site list
supplier_basean Elexon supplier's base demand BM unit in one GSP group (2__, register type G)no: demand, not plants
supplier_additionalan Elexon supplier's additional BM unit in one GSP group (2__ or C__, type S): sometimes one embedded farm traded through the supplier, named by technology and fuel_typeno

The plant-to-portfolio link is a crosswalk, so a portfolio can carry capacity and series and still have plants: []. The card's plants count is the cheap way to tell before calling the entity.

The register a record comes from is its provider: entsoe (production and generation units), bmrs (Elexon BM units), mastr (the German Marktstammdatenregister), gem (Global Energy Monitor), epias (Turkey), vbk (Sweden), energistyrelsen (Denmark) and georisques (France). The register today holds about 57,000 plants, 98,000 units and 1,100 portfolios.

Ids and names

The id is the kind prefix and the register's key: plant: followed by an ENTSO-E EIC, a MaStR number (plant:mastr-SEL926745834017), a GEM location (plant:gem-L100000601739). A name is accepted after the same prefix: plant:Säckingen hydroelectric plant resolves to plant:11WD4SAEK2HS---S. Plant names match both the register name and the GEM common name. Names are not unique: plant:Schluchseewerk AG is 404 and lists the three candidate ids in closest. Responses always return the key in id, so use that for anything stored.

Cards

Wherever a plant, unit or portfolio appears, it appears as a card: a fixed set of fields per kind, the same in a parent, a child line and a list item, so listing a plant's units or a portfolio's plants does not cost one call per line.

KindCard fields
plantname, common_name, parent, technology, sub_technology, psr_type, capacity_mw, location (latitude, longitude, accuracy), portfolio, provider
unitname, parent, type, production_type, psr_type, capacity_kw, energy_mwh, fuel, provider — no location: a unit sits at its plant's point
portfolioname, parent, portfolio_kind (aggregate, aggregator, supplier_base, supplier_additional, virtual), technology, capacity_mw, demand_capacity_mw, lead_party, fuel_type, transmission_loss_factor and gsp_group (GB BM units), provider

A unit has no location of its own: it sits at its plant's location, and the plant's accuracy applies to it. accuracy is exact (the plant's own point), approximate (municipality precision, or a matched point rated so by its source) or placeholder (a stand-in the source uses when it has no position, a country or municipality centroid shared by many plants; skip these when mapping). A portfolio has no location at all.

Fields the register did not fill are omitted. The entity call adds the rest: external ids (eic, gem_location_id, mastr_unit_id, bmu_id, …), the series the plant publishes, commissioning, operator, voltage and, for GEM-merged plants, the provenance of the merge.

The entity call

GET /entities/{entity} on a plant returns its card, one hop up in parent (its zone, with that zone's timeseries count), its portfolio inline, and its units as cards under children. A unit returns its plant's card as parent and no children. A portfolio has no children either: a plant names its portfolio in its portfolio field, the way a site does on the platform, so the portfolio call returns the plants that name it under plants, as plant cards, with their summed capacity in plants_capacity_mw beside the register's own capacity_mw. The same plants are also the list GET /entities/{portfolio}/plants, and their units GET /entities/{portfolio}/units.

A zone's entity call does not list its plants. Its children carry one summary line per kind, with the count, the capacity and how many hold timeseries, and the path of the list:

{"kind": "plant", "count": 21107, "capacity_mw": 212583, "with_timeseries": 119, "list": "/entities/DE-LU/plants"}
curl "$API/entities/plant:11WD4SAEK2HS---S" -H "X-API-Key: $KEY"
r = requests.get(f"{API}/entities/plant:11WD4SAEK2HS---S", headers=HEADERS)
plant = r.json()
units = plant["children"]

Lists

Three paged lists return the cards under an entity, that entity and everything below it: a country, a zone, an area. On a plant, units lists its units; on a portfolio, plants lists the plants that name it and units the units of those plants. There is no Europe-wide list and a synchronous area holds nothing: to sweep everything, call once per country plus the zones with no country (GB, SEM), which are the top level of GET /entities. Results page with next: here it is the path and query of the next page (the same filters, with cursor set), to request as it is, not a value to pass as cursor. limit is 100 by default and at most 1000.

ListFilters
GET /entities/{entity}/plantstechnology, min_mw, has_timeseries, bbox, provider, portfolio
GET /entities/{entity}/unitstype, production_type, fuel, min_kw, has_timeseries, bbox, provider
GET /entities/{entity}/portfoliosportfolio_kind, min_mw, has_timeseries, provider

bbox is minLon,minLat,maxLon,maxLat. has_timeseries=true keeps the records that hold at least one timeseries, which is the usual first filter: most plants in a register publish nothing of their own.

# hydro plants of 100 MW or more in DE-LU
curl "$API/entities/DE-LU/plants?technology=hydro&min_mw=100" -H "X-API-Key: $KEY"

# batteries of 10 MW or more
curl "$API/entities/DE-LU/units?type=storage&min_kw=10000" -H "X-API-Key: $KEY"

# the GB suppliers' base demand portfolios
curl "$API/entities/GB/portfolios?portfolio_kind=supplier_base" -H "X-API-Key: $KEY"
# every plant in DE-LU that holds a timeseries
items, cursor = [], None
while True:
    r = requests.get(
        f"{API}/entities/DE-LU/plants",
        headers=HEADERS,
        params={"has_timeseries": "true", "limit": 1000, "cursor": cursor},
    )
    page = r.json()
    items += page["items"]
    if not page["next"]:
        break
    cursor = page["next"].split("cursor=")[1]

Timeseries on a plant

Series are held by the record that publishes them: installed capacity per production unit on the plant, actual generation per generation unit on the unit, a BM unit's metered output on the portfolio. A plant's timeseries.total counts its own series, not its units'.

GET /entities/{entity}/timeseries on a plant lists its own series and, under children, those of its units; on a portfolio, its own series only. Search scoped to a zone (GET /timeseries?entity=DE-LU) covers the plants, units and portfolios under it as well.

Metadata and values are read per owner, with the same routes and the same as_of forms as a zone:

curl "$API/entities/plant:11WD4SAEK2HS---S/timeseries" -H "X-API-Key: $KEY"

curl "$API/entities/plant:11WD4SAEK2HS---S/timeseries/capacity.installed.installed_capacity_per_unit_a71.11wd4saek2hs___s/values?provider=entsoe&start=2026-01-01T00:00:00Z&end=2026-12-31T23:00:00Z" \
  -H "X-API-Key: $KEY"
plant = "plant:11WD4SAEK2HS---S"
names = requests.get(f"{API}/entities/{plant}/timeseries", headers=HEADERS).json()
name = names["timeseries"][0]["name"]
r = requests.get(
    f"{API}/entities/{plant}/timeseries/{name}/values",
    headers=HEADERS,
    params={"provider": "entsoe", "start": "2026-01-01T00:00:00Z", "end": "2026-12-31T23:00:00Z"},
)
points = r.json()["points"]

On this page