library(httr2)
library(scales)
library(tidyverse)
On 15 June 2025, PSE introduced a new version of its API for retrieving data. This post updates the 21 April 2025 post to reflect the changes required to access wind and solar generation data in Poland.
Let’s see how we can download wind and solar generation in Poland using API v2 provided by PSE (Polskie Sieci Energetyczne, Polish Power System Operation).
PSE reports via API v2
PSE offers two sources for its data reports. Historical reports up to June 13, 2024, are available at https://www.pse.pl/raporty-historyczne, while current and subsequent reports (from June 14, 2024, onward) can be accessed via the API at https://api.raporty.pse.pl.
We are interested in the daily report “Polish Power System Operation - Fundamental Data” (identified as his-wlk-cal). This report includes many parameters, but we want to select only several:
- Total generation of Photovoltaic Sources: pv
- Total generation of Wind Sources: wi
- Time when production was registered: dtime
- Period to which the data relates: period
We will also need a filter in which we define the exact date
- Buisness date: business_date
Building request
Let’s load necessary packages!
Now we can define elements of the request.
Base url of PSE API v2:
<- "https://api.raporty.pse.pl/api/" base_url
Name of the report we are interested in, with added ?:
<- "his-wlk-cal?" report_name
PSE API v2 allows to use $select for selecting fields interested for us:
<- "$select=dtime,period,pv,wi" our_select
Report date:
<- "2025-09-12" date
PSE API v2 allows to use $filter for filtering data with the following parameters:
Operator | Description |
---|---|
eq | equal to |
ne | not equal to |
gt | greater than |
ge | greater than or equal |
lt | less than |
le | less than or equal |
and, or, not | logical |
In our case we want to get daily data from 2025-09-15. We need to remeber to add single quota character before and after the date, hence:
<- paste0("$filter=business_date eq ",
our_filter "'",
date,"'")
our_filter
[1] "$filter=business_date eq '2025-09-12'"
Now we can put all this elements together
<- paste0(base_url,
pse_api_url
report_name,
our_select,"&",
our_filter) pse_api_url
[1] "https://api.raporty.pse.pl/api/his-wlk-cal?$select=dtime,period,pv,wi&$filter=business_date eq '2025-09-12'"
One more thing to do - replace some special characters:
- spaces by %20
- commas by %2C
- single quotes by %27
and after that the request link is ready.
<- pse_api_url |> str_replace_all(pattern = "\\s", replace = "%20")
pse_api_url <- pse_api_url |> str_replace_all(pattern = "\\,", replace = "%2C")
pse_api_url <- pse_api_url |> str_replace_all(pattern = "\\'", replace = "%27")
pse_api_url pse_api_url
[1] "https://api.raporty.pse.pl/api/his-wlk-cal?$select=dtime%2Cperiod%2Cpv%2Cwi&$filter=business_date%20eq%20%272025-09-12%27"
Performing request
We will retrieve data using the httr2 package. The response will be a JSON object. The actual data for each time step will be found within the value element of this JSON object.
<- httr2::request(pse_api_url) |>
daily_data ::req_perform() |>
httr2::resp_body_json()
httr2
names(daily_data)
[1] "value"
names(daily_data$value[[1]])
[1] "pv" "wi" "dtime" "period"
Now we convert JSON to a data frame
<- bind_rows(daily_data$value)
daily_data
glimpse(daily_data)
Rows: 96
Columns: 4
$ pv <dbl> 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, …
$ wi <dbl> 2695.215, 2739.636, 2781.696, 2731.144, 2717.612, 2786.011, 287…
$ dtime <chr> "2025-09-12 00:15:00", "2025-09-12 00:30:00", "2025-09-12 00:45…
$ period <chr> "00:00 - 00:15", "00:15 - 00:30", "00:30 - 00:45", "00:45 - 01:…
Plot data
From the daily_data, we will extract the time column, as well as the columns containing wind and solar generation data. The time column will then be converted to the POSIXct format. We omit the rest of columns, however they can be also added if needed.
- Total generation of Photovoltaic Sources: pv
- Total generation of Wind Sources: wi
- Time: dtime
We will also reshape the data from wide to long format and then sort the records by the values in the newly created time column.
<- daily_data |>
plot_data mutate(time = as.POSIXct(dtime)) |>
select(time, pv, wi) |>
arrange(time) |>
pivot_longer(cols = c("pv", "wi"),
names_to = "generation_type",
values_to = "generation")
First hour of data for plotting
head(plot_data)
# A tibble: 6 × 3
time generation_type generation
<dttm> <chr> <dbl>
1 2025-09-12 00:15:00 pv 0
2 2025-09-12 00:15:00 wi 2695.
3 2025-09-12 00:30:00 pv 0
4 2025-09-12 00:30:00 wi 2740.
5 2025-09-12 00:45:00 pv 0
6 2025-09-12 00:45:00 wi 2782.
Now we can plot daily wind and solar generation on 2025-09-12.
ggplot(plot_data, aes(x = time, y = generation, color = generation_type)) +
geom_line() +
scale_y_continuous(
breaks = seq(0, 10000, 1000),
labels = label_number(big.mark = ","),
limits = c(0, 10000)
+
) labs(
x = (paste0("Business date, ", date)),
y = ("Wind and solar energy generation (MW) "),
position = "left"
+
) theme_minimal() +
theme(legend.position.inside = c(.95, .95),
legend.justification = c("right", "top"),
legend.box.just = "right",
legend.margin = margin(6, 6, 6, 6),
axis.text=element_text(size=12),
axis.title=element_text(size=14)) +
scale_color_manual(values = c("orange", "blue"),
name = "Generation type",
labels = c("solar", "wind"))
That’s all!