Introduction
The entsoeapi package provides a standardized R
interface to the ENTSO-E (European Network of Transmission System
Operators for Electricity) Transparency Platform API. This platform
publishes electricity market, generation, load, transmission, outage,
and balancing data for European countries.
This vignette will guide you through:
- Setting up the package and obtaining API access
- Checking connectivity and platform news
- Finding Energy Identification Codes (EICs) for areas of interest
- Making your first API call
- Understanding the output structure
Prerequisites
Obtaining an API Token
To use the ENTSO-E Transparency Platform API, you need a security token:
- Register at https://transparency.entsoe.eu/
- After registration, your token should be available in your profile
- Set the
ENTSOE_PATenvironment variable in your .Renviron file:
usethis::edit_r_environ()For persistent access, add this line to your .Renviron
file:
Installation
Install the development version from GitHub:
# Install from GitHub
remotes::install_github("krose/entsoeapi")
# Or install from CRAN (when available)
install.packages("entsoeapi")Load the packages:
library(entsoeapi)
suppressPackageStartupMessages(library(dplyr))
library(cli)
suppressPackageStartupMessages(library(lubridate))
suppressPackageStartupMessages(library(kableExtra))Testing Connectivity
The there_is_provider() function checks if the ENTSO-E
API is reachable:
# Check if the API is accessible
there_is_provider()
#> [1] TRUEChecking Platform News
The get_news() function fetches the latest announcements
from the ENTSO-E Transparency Platform RSS feed. This is useful for
checking planned maintenance windows or data publication delays before
running a batch of queries:
# Show the latest 3 news items
get_news(n = 3L)
#>
#> ── ENTSO-E Transparency Platform News ──────────────────────────────────────────────────────────────────────────────────
#>
#> ── TP scheduled maintenance on Thursday, 15 October, between 18:00 and 20:00 CEST ──
#>
#> ℹ Thu, 15 Oct 2026 12:25:33 GMT
#> Dear Transparency Platform users,The Transparency Platform Production environment will be under scheduled maintenance
#> on Thursday, 15 October, between 18:00 and 20:00 CEST.During this maintenance, you may experience brief service
#> interruptions or short connectivity issues.We apologize for any inconvenience and appreciate your understanding.Best
#> regards,Transparency Platform team
#>
#> ── TP ECP maintenance 13.10.2026 between 13:00 and 13:30 CEST ──
#>
#> ℹ Mon, 12 Oct 2026 10:03:36 GMT
#> Dear Transparency Platform users,The Transparency Platform Service Provider will perform ECP maintenance on
#> 13/10/2026 at 13:00 CEST in the Production environment, which is expected to last up to 30 minutes.During this period,
#> the Transparency Platform will not be able to send or receive messages via ECP up to 15 min.Best regards,Transparency
#> Platform team
#>
#> ── RTE – IT issue impacting publication of GL EB 12.3.E and TR 17.1.F for “mFRR Specific” and “RR Specific” products ──
#>
#> ℹ Thu, 08 Oct 2026 10:33:03 GMT
#> Dear Transparency Platform users,Please be informed that since Monday, 05 October 2026, at approximately 10:00 CET, RTE
#> has been experiencing an internal IT issue affecting some data delivery to the Transparency Platform.As a result, two
#> Data Items scheduled for publication since that time has not been successfully submitted and is therefore unavailable
#> on the platform:[GL EB 12.3.E] Aggregated Balancing Energy Bids for “mFRR Specific” and “RR Specific” products[TR
#> 17.1.F] Prices of Activated Balancing Energy for “mFRR Specific” and “RR Specific” productsThe other types of products
#> (aFRR LS Standard, aFRR CS Standard, mFRR Standard and mFRR SA Standard) are not impacted by this IT issue and are well
#> published on the Transparency Platform.Our teams are actively working to resolve the issue and restore normal data
#> delivery as quickly as possible.We sincerely apologize for any inconvenience caused.Kind regards,Transparency Platform
#> team on behalf of RTEThe result is returned invisibly as a tibble, so you can also capture and filter it:
Finding EIC Codes
Energy Identification Codes (EICs) are 16-character codes that uniquely identify market participants, bidding zones, transmission lines, and other entities on the ENTSO-E platform.
The all_approved_eic() function returns a comprehensive
list of approved EIC codes:
# Find Germany's bidding zone EIC
germany_eic <- all_approved_eic() |>
filter(eic_long_name == "Germany_Luxemburg") |>
pull(eic_code)
#>
#> ── public download ─────────────────────────────────────────────────────────────────────────────────────────────────────
#> ℹ downloading X_eicCodes.csv file ...
#>
#> ── public download ─────────────────────────────────────────────────────────────────────────────────────────────────────
#> ℹ downloading Y_eicCodes.csv file ...
#>
#> ── public download ─────────────────────────────────────────────────────────────────────────────────────────────────────
#> ℹ downloading Z_eicCodes.csv file ...
#>
#> ── public download ─────────────────────────────────────────────────────────────────────────────────────────────────────
#> ℹ downloading T_eicCodes.csv file ...
#>
#> ── public download ─────────────────────────────────────────────────────────────────────────────────────────────────────
#> ℹ downloading V_eicCodes.csv file ...
#>
#> ── public download ─────────────────────────────────────────────────────────────────────────────────────────────────────
#> ℹ downloading W_eicCodes.csv file ...
#>
#> ── public download ─────────────────────────────────────────────────────────────────────────────────────────────────────
#> ℹ downloading A_eicCodes.csv file ...
cli_h1("Germany Bidding Zone EIC")
#>
#> ── Germany Bidding Zone EIC ────────────────────────────────────────────────────────────────────────────────────────────
cli_text(germany_eic)
#> 10Y1001A1001A82HThe area_eic() function provides a focused lookup for
bidding zones:
# Get all bidding zones
bidding_zones <- area_eic()
#>
#> ── public download ─────────────────────────────────────────────────────────────────────────────────────────────────────
#> ℹ pulling Y_eicCodes.csv file from cache
cli_h1("Available Bidding Zones")
#>
#> ── Available Bidding Zones ─────────────────────────────────────────────────────────────────────────────────────────────
cli_text("Total zones: {nrow(bidding_zones)}")
#> Total zones: 1829
bidding_zones |>
select(eic_code, eic_long_name) |>
head(10) |>
kbl(format = "pipe") |>
cat(sep = "\n")
#> |eic_code |eic_long_name |
#> |:----------------|:---------------------------------------------------|
#> |10Y1001A1001A008 |Market coupling area Belgium - France - Netherlands |
#> |10Y1001A1001A016 |Northern Ireland |
#> |10Y1001A1001A39I |Estonia |
#> |10Y1001A1001A42T |CB Spain |
#> |10Y1001A1001A44P |Swedish Elspot Area 1 |
#> |10Y1001A1001A45N |Swedish Elspot Area 2 |
#> |10Y1001A1001A46L |Swedish Elspot Area 3 |
#> |10Y1001A1001A47J |Swedish Elspot Area 4 |
#> |10Y1001A1001A48H |Norwegian Area Elspot Area 5 |
#> |10Y1001A1001A49F |Russian area |Your First API Call
Let’s fetch day-ahead energy prices for Germany. The
energy_prices() function queries the market data
endpoint:
# Define time range (one week of data)
from_ts <- ymd("2026-01-01", tz = "CET")
till_ts <- from_ts + weeks(1L)
cli_h1("Querying Day-Ahead Energy Prices")
#>
#> ── Querying Day-Ahead Energy Prices ────────────────────────────────────────────────────────────────────────────────────
cli_inform("Period: {from_ts} to {till_ts}")
#> Period: 2026-01-01 to 2026-01-08
cli_inform("Area: {germany_eic}")
#> Area: 10Y1001A1001A82H
# Fetch the data
da_prices <- energy_prices(
eic = germany_eic,
period_start = from_ts,
period_end = till_ts,
contract_type = "A01", # Day-ahead
tidy_output = TRUE,
security_token = Sys.getenv("ENTSOE_PAT")
)
#>
#> ── API call ────────────────────────────────────────────────────────────────────────────────────────────────────────────
#> → https://web-api.tp.entsoe.eu/api?documentType=A44&in_Domain=10Y1001A1001A82H&out_Domain=10Y1001A1001A82H&periodStart=202512312300&periodEnd=202601072300&contract_MarketAgreement.type=A01&securityToken=<...>
#> <- HTTP/1.1 200 OK
#> <- Date: Tue, 20 Oct 2026 10:29:11 GMT
#> <- Content-Type: text/xml
#> <- Transfer-Encoding: chunked
#> <- Connection: keep-alive
#> <- Content-Disposition: inline; filename="Energy_Prices_202512312300-202601072300.xml"
#> <- Content-Encoding: gzip
#> <- Strict-Transport-Security: max-age=15724800
#> <- Vary: accept-encoding
#> <- X-Content-Type-Options: nosniff
#> <- X-Xss-Protection: 0
#> <-
#> ✔ response has arrived
#> ✔ Additional type names have been added!
#>
#> ── public download ─────────────────────────────────────────────────────────────────────────────────────────────────────
#> ℹ pulling Y_eicCodes.csv file from cache
#> ✔ Additional eic names have been added!
cli_alert_success("Retrieved {nrow(da_prices)} rows")
#> ✔ Retrieved 1344 rowsUnderstanding the Output
The tidy_output = TRUE format returns one row per data
point:
# Examine the structure
glimpse(da_prices)
#> Rows: 1,344
#> Columns: 23
#> $ ts_in_domain_mrid <chr> "10Y1001A1001A82H", "10Y1001A1001A82H", "10Y1001A1001A82H", "10Y1001A1001A…
#> $ ts_in_domain_name <chr> "Germany_Luxemburg", "Germany_Luxemburg", "Germany_Luxemburg", "Germany_Lu…
#> $ ts_out_domain_mrid <chr> "10Y1001A1001A82H", "10Y1001A1001A82H", "10Y1001A1001A82H", "10Y1001A1001A…
#> $ ts_out_domain_name <chr> "Germany_Luxemburg", "Germany_Luxemburg", "Germany_Luxemburg", "Germany_Lu…
#> $ type <chr> "A44", "A44", "A44", "A44", "A44", "A44", "A44", "A44", "A44", "A44", "A44…
#> $ type_def <chr> "Price Document", "Price Document", "Price Document", "Price Document", "P…
#> $ market_agreement_type <chr> "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01…
#> $ market_agreement_type_def <chr> "Daily contract", "Daily contract", "Daily contract", "Daily contract", "D…
#> $ ts_auction_type <chr> "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01…
#> $ ts_auction_type_def <chr> "Implicit", "Implicit", "Implicit", "Implicit", "Implicit", "Implicit", "I…
#> $ ts_business_type <chr> "A62", "A62", "A62", "A62", "A62", "A62", "A62", "A62", "A62", "A62", "A62…
#> $ ts_business_type_def <chr> "Spot price", "Spot price", "Spot price", "Spot price", "Spot price", "Spo…
#> $ created_date_time <dttm> 2026-10-20 10:29:11, 2026-10-20 10:29:11, 2026-10-20 10:29:11, 2026-10-20…
#> $ revision_number <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
#> $ ts_resolution <chr> "PT15M", "PT15M", "PT15M", "PT15M", "PT15M", "PT15M", "PT15M", "PT15M", "P…
#> $ ts_time_interval_start <dttm> 2025-12-31 23:00:00, 2025-12-31 23:00:00, 2025-12-31 23:00:00, 2025-12-31…
#> $ ts_time_interval_end <dttm> 2026-01-01 23:00:00, 2026-01-01 23:00:00, 2026-01-01 23:00:00, 2026-01-01…
#> $ ts_mrid <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
#> $ ts_point_dt_start <dttm> 2025-12-31 23:00:00, 2025-12-31 23:15:00, 2025-12-31 23:30:00, 2025-12-31…
#> $ ts_point_price_amount <dbl> 51.28, 52.08, 63.00, 41.08, 37.07, 40.03, 36.01, 25.20, 34.03, 21.83, 13.0…
#> $ ts_currency_unit_name <chr> "EUR", "EUR", "EUR", "EUR", "EUR", "EUR", "EUR", "EUR", "EUR", "EUR", "EUR…
#> $ ts_price_measure_unit_name <chr> "MWH", "MWH", "MWH", "MWH", "MWH", "MWH", "MWH", "MWH", "MWH", "MWH", "MWH…
#> $ ts_classification_sequence_position <dbl> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,…Key columns in the output:
| Column | Description |
|---|---|
ts_point_dt_start |
Timestamp for each data point |
ts_point_price_amount |
Price value |
ts_resolution |
Time resolution (e.g., PT60M for hourly) |
ts_business_type |
Type of price (Spot price = A62) |
ts_market_agreement_type |
Contract type (A01 = Daily contract) |
The Nested Alternative
With tidy_output = FALSE, the data is structured
differently:
# Same query with nested output
da_prices_nested <- energy_prices(
eic = germany_eic,
period_start = from_ts,
period_end = till_ts,
contract_type = "A01",
tidy_output = FALSE
)
#>
#> ── API call ────────────────────────────────────────────────────────────────────────────────────────────────────────────
#> → https://web-api.tp.entsoe.eu/api?documentType=A44&in_Domain=10Y1001A1001A82H&out_Domain=10Y1001A1001A82H&periodStart=202512312300&periodEnd=202601072300&contract_MarketAgreement.type=A01&securityToken=<...>
#> <- HTTP/1.1 200 OK
#> <- Date: Tue, 20 Oct 2026 10:29:14 GMT
#> <- Content-Type: text/xml
#> <- Transfer-Encoding: chunked
#> <- Connection: keep-alive
#> <- Content-Disposition: inline; filename="Energy_Prices_202512312300-202601072300.xml"
#> <- Content-Encoding: gzip
#> <- Strict-Transport-Security: max-age=15724800
#> <- Vary: accept-encoding
#> <- X-Content-Type-Options: nosniff
#> <- X-Xss-Protection: 0
#> <-
#> ✔ response has arrived
#> ✔ Additional type names have been added!
#> ✔ Additional eic names have been added!
glimpse(da_prices_nested)
#> Rows: 14
#> Columns: 22
#> $ ts_in_domain_mrid <chr> "10Y1001A1001A82H", "10Y1001A1001A82H", "10Y1001A1001A82H", "10Y1001A1001A…
#> $ ts_in_domain_name <chr> "Germany_Luxemburg", "Germany_Luxemburg", "Germany_Luxemburg", "Germany_Lu…
#> $ ts_out_domain_mrid <chr> "10Y1001A1001A82H", "10Y1001A1001A82H", "10Y1001A1001A82H", "10Y1001A1001A…
#> $ ts_out_domain_name <chr> "Germany_Luxemburg", "Germany_Luxemburg", "Germany_Luxemburg", "Germany_Lu…
#> $ type <chr> "A44", "A44", "A44", "A44", "A44", "A44", "A44", "A44", "A44", "A44", "A44…
#> $ type_def <chr> "Price Document", "Price Document", "Price Document", "Price Document", "P…
#> $ market_agreement_type <chr> "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01…
#> $ market_agreement_type_def <chr> "Daily contract", "Daily contract", "Daily contract", "Daily contract", "D…
#> $ ts_auction_type <chr> "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01", "A01…
#> $ ts_auction_type_def <chr> "Implicit", "Implicit", "Implicit", "Implicit", "Implicit", "Implicit", "I…
#> $ ts_business_type <chr> "A62", "A62", "A62", "A62", "A62", "A62", "A62", "A62", "A62", "A62", "A62…
#> $ ts_business_type_def <chr> "Spot price", "Spot price", "Spot price", "Spot price", "Spot price", "Spo…
#> $ created_date_time <dttm> 2026-10-20 10:29:14, 2026-10-20 10:29:14, 2026-10-20 10:29:14, 2026-10-20…
#> $ revision_number <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
#> $ ts_resolution <chr> "PT15M", "PT15M", "PT15M", "PT15M", "PT15M", "PT15M", "PT15M", "PT15M", "P…
#> $ ts_time_interval_start <dttm> 2025-12-31 23:00:00, 2025-12-31 23:00:00, 2026-01-01 23:00:00, 2026-01-01 …
#> $ ts_time_interval_end <dttm> 2026-01-01 23:00:00, 2026-01-01 23:00:00, 2026-01-02 23:00:00, 2026-01-02…
#> $ ts_mrid <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
#> $ ts_point <list> [<tbl_df[96 x 2]>], [<tbl_df[96 x 2]>], [<tbl_df[96 x 2]>], [<tbl_df[96 x…
#> $ ts_currency_unit_name <chr> "EUR", "EUR", "EUR", "EUR", "EUR", "EUR", "EUR", "EUR", "EUR", "EUR", "EUR…
#> $ ts_price_measure_unit_name <chr> "MWH", "MWH", "MWH", "MWH", "MWH", "MWH", "MWH", "MWH", "MWH", "MWH", "MW…
#> $ ts_classification_sequence_position <dbl> 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1With nested output, each row represents a time period, and all data
points are stored in the ts_point list-column. This format
preserves the original API structure and can be more efficient for
certain operations.
Common Patterns
The One-Year Limit
Most ENTSO-E endpoints enforce a maximum query range of one year. The package handles this transparently through automatic pagination:
# Query close to the one-year limit
year_start <- ymd("2024-01-01", tz = "CET")
year_end <- ymd("2024-12-31", tz = "CET")
# The package will handle pagination automatically if needed
# This may take longer for large datasets
cli_warn("Querying a full year - this may take a moment")
da_prices_year <- energy_prices(
eic = germany_eic,
period_start = year_start,
period_end = year_end,
contract_type = "A01",
tidy_output = TRUE
)
#>
#> ── API call ────────────────────────────────────────────────────────────────────────────────────────────────────────────
#> → https://web-api.tp.entsoe.eu/api?documentType=A44&in_Domain=10Y1001A1001A82H&out_Domain=10Y1001A1001A82H&periodStart=202312312300&periodEnd=202412302300&contract_MarketAgreement.type=A01&securityToken=<...>
#> <- HTTP/1.1 200 OK
#> <- Date: Tue, 20 Oct 2026 10:29:17 GMT
#> <- Content-Type: text/xml
#> <- Transfer-Encoding: chunked
#> <- Connection: keep-alive
#> <- Content-Disposition: inline; filename="Energy_Prices_202312312300-202412302300.xml"
#> <- Content-Encoding: gzip
#> <- Strict-Transport-Security: max-age=15724800
#> <- Vary: accept-encoding
#> <- X-Content-Type-Options: nosniff
#> <- X-Xss-Protection: 0
#> <-
#> ✔ response has arrived
#> ✔ Additional type names have been added!
#> ✔ Additional eic names have been added!
cli_alert_success("Retrieved {nrow(da_prices_year)} rows for the year")
#> ✔ Retrieved 43800 rows for the yearTimezone Considerations
All timestamps are returned in UTC. Use lubridate to convert:
# Convert timestamps to CET/CEST
da_prices |>
mutate(
ts_point_dt_start_cet = with_tz(time = ts_point_dt_start, tzone = "CET")
) |>
select(ts_point_dt_start, ts_point_dt_start_cet, ts_point_price_amount) |>
head(n = 5L) |>
kbl(format = "pipe") |>
cat(sep = "\n")
#> |ts_point_dt_start |ts_point_dt_start_cet | ts_point_price_amount|
#> |:-------------------|:---------------------|---------------------:|
#> |2025-12-31 23:00:00 |2026-01-01 00:00:00 | 51.28|
#> |2025-12-31 23:15:00 |2026-01-01 00:15:00 | 52.08|
#> |2025-12-31 23:30:00 |2026-01-01 00:30:00 | 63.00|
#> |2025-12-31 23:45:00 |2026-01-01 00:45:00 | 41.08|
#> |2026-01-01 00:00:00 |2026-01-01 01:00:00 | 37.07|What’s Next?
Now that you’ve made your first API call, explore these topics:
-
EIC Codes: The
area_eic(),tie_line_eic(), and other EIC functions help you find codes for specific regions and entities -
Time Series Restructuring: Understanding the
tidy_outputparameter helps you work with the data effectively -
Generation Mix: Analyze generation by production
type with
gen_per_prod_type() - Architecture: See the package architecture vignette for details on how the package works internally