Accessing PSE Daily Wind and Solar Data in Poland, with API v2

posts
Author

Pawel Cwiek

Published

September 13, 2025

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!

library(httr2)
library(scales)
library(tidyverse)

Now we can define elements of the request.

Base url of PSE API v2:

base_url <- "https://api.raporty.pse.pl/api/"

Name of the report we are interested in, with added ?:

report_name <- "his-wlk-cal?"

PSE API v2 allows to use $select for selecting fields interested for us:

our_select <- "$select=dtime,period,pv,wi"

Report date:

date <- "2025-09-12"

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:

our_filter <- paste0("$filter=business_date eq ",
                     "'",
                     date,
                     "'")
our_filter
[1] "$filter=business_date eq '2025-09-12'"

Now we can put all this elements together

pse_api_url <- paste0(base_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 <- 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
[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.

daily_data <- httr2::request(pse_api_url) |>
  httr2::req_perform() |> 
  httr2::resp_body_json() 

names(daily_data)
[1] "value"
names(daily_data$value[[1]])
[1] "pv"     "wi"     "dtime"  "period"

Now we convert JSON to a data frame

daily_data <- bind_rows(daily_data$value)

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.

plot_data <- daily_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!