REST-API
Via https://api.pqopen.com, there is a REST-API available for gathering actual and historic measurement data from the PQopen Monitoring System. You can request snippets of Cycle-by-Cycle data from the last 30 days as well as aggregated data (1s, 600s) from selected Parameters.
Endpoints for Metadata
The Endpoints and detailed description of the API and experimental interface is available here: https://api.pqopen.com/docs
/v1/metadata/locations/{family} [GET]
Request available locatation tags for the specific data family (either cbc or aggregated)
Returns json-Object of available locations to be queried via /v1/data requests
/v1/metadata/fields/{family} [GET]
Request available fields (measurement channels) for the specific data family (either cbc or aggregated)
Returns json-Object of available fields to be queried via /v1/data requests
/v1/metadata/aggintervals [GET]
Requests available aggregation intervals from the aggregated-data collection.
Returns json-Object of available aggregation intervals to be queried via /v1/data requests
Endpoints for Measurement Data
/v1/data/aggregated [POST]
Query aggregated data for specified interval. The request body must be of type:
{
"range_start": "2025-12-21T13:00:21Z",
"range_stop": "2025-12-21T14:00:21Z",
"location": "AT/Graz",
"interval_sec": 1,
"fields": ["U1_rms", "U1_THD"]
}
If the fields list empty, all available fields for the selected combination are returned.
Returns a Parquet-File Stream in case of success.
/v1/data/cbc [POST]
Query cycle-by-cycle data for specified interval. The request body must be of type:
{
"range_start": "2025-12-21T13:00:21Z",
"range_stop": "2025-12-21T14:00:21Z",
"location": "AT/Graz",
"fields": ["Freq", "U1_1p_rms"]
}
If the fields list empty, all available fields for the selected combination are returned.
Returns a Parquet-File Stream in case of success.
Limitations
- Maximum number of measurement points: 1 Mio. (must be reduced by time span or number of fields)
- Maximum number of requests per hour: 50
Python Examples
If you are using python, I prepared short examples of programmatically requesting some data.
Requesting available Locations
import requests
base_url = "https://api.pqopen.com"
API_KEY = "YOUR-API-KEY"
headers={"X-API-Key": API_KEY}
res = requests.get(base_url+"/v1/meta/locations/aggregated", headers=headers)
if res.status_code == 200:
print("locations aggregated data:", res.json()["locations"])
Prints the following:
Requesting Fields
import requests
base_url = "https://api.pqopen.com"
API_KEY = "YOUR-API-KEY"
headers={"X-API-Key": API_KEY}
res = requests.get(base_url+"/v1/meta/fields/aggregated", headers=headers)
if res.status_code == 200:
print("fields aggregated data:", res.json()["fields"])
Prints the following:
Requesting Aggregated Data
Request last hour's data from selected fields
import requests
import polars as pl
import datetime
base_url = "https://api.pqopen.com"
API_KEY = "YOUR-API-KEY"
headers={"X-API-Key": API_KEY}
range_stop_dt = datetime.datetime.now(datetime.UTC)
range_start_dt = range_stop_dt - datetime.timedelta(hours=1)
request_body = {
"range_start": range_start_dt.isoformat(),
"range_stop": range_stop_dt.isoformat(),
"location": "AT/Graz",
"interval_sec": 1,
"fields": ["Freq", "U1_rms", "U2_rms", "U3_rms"]
}
res = requests.post(base_url+"/v1/data/aggregated", json=request_body, headers=headers)
if res.status_code == 200:
agg_data = pl.read_parquet(res.content)
print(agg_data)
else:
print(res.reason, res.text)
Prints the following:
shape: (3_600, 3)
┌─────────────────────────┬─────────┬──────────┐
│ _time ┆ Freq ┆ U1_rms │
│ --- ┆ --- ┆ --- │
│ datetime[μs, UTC] ┆ f64 ┆ f64 │
╞═════════════════════════╪═════════╪══════════╡
│ 2025-12-22 07:33:21 UTC ┆ 49.9873 ┆ 230.019 │
│ 2025-12-22 07:33:22 UTC ┆ 49.9872 ┆ 230.1252 │
│ 2025-12-22 07:33:23 UTC ┆ 49.9871 ┆ 230.1179 │
│ 2025-12-22 07:33:24 UTC ┆ 49.9865 ┆ 230.1027 │
Requesting Cycle-by-Cycle Data
Request last 10 minutes data from frequency field
import requests
import polars as pl
import datetime
base_url = "https://api.pqopen.com"
API_KEY = "YOUR-API-KEY"
headers={"X-API-Key": API_KEY}
range_stop_dt = datetime.datetime.now(datetime.UTC)
range_start_dt = range_stop_dt - datetime.timedelta(minutes=10)
request_body = {
"range_start": range_start_dt.isoformat(),
"range_stop": range_stop_dt.isoformat(),
"location": "AT/Graz",
"fields": ["Freq"]
}
res = requests.post(base_url+"/v1/data/cbc", json=request_body, headers=headers)
if res.status_code == 200:
cbc_data = pl.read_parquet(res.content)
print(cbc_data)
else:
print(res.reason, res.text)
Prints the following:
shape: (179_987, 2)
┌────────────────────────────────┬─────────┐
│ _time ┆ Freq │
│ --- ┆ --- │
│ datetime[μs, UTC] ┆ f64 │
╞════════════════════════════════╪═════════╡
│ 2025-12-22 07:33:21.009101 UTC ┆ 49.988 │
│ 2025-12-22 07:33:21.029117 UTC ┆ 49.9881 │
│ 2025-12-22 07:33:21.049116 UTC ┆ 49.9869 │
│ 2025-12-22 07:33:21.069115 UTC ┆ 49.9888 │