Getting Forecasts
Once you have sites and resources connected to the Powernaut platform, you can retrieve forecasts to help plan your flexibility offerings and understand expected power consumption or production patterns.
Available endpoints
You can retrieve forecasts for both sites and resources:
- Sites:
GET /connect/sites/{id}/forecast
- Resources:
GET /connect/resources/{id}/forecast
Site vs Resource Forecasts
Understanding the difference between site and resource forecasts is crucial for effective flexibility management:
Site Forecasts
- Scope: Represent the total power flow at the site's connection point to the grid (the head meter).
- Composition: Include the sum of all resources at the site plus any baseload consumption (background loads like lighting, heating, appliances)
- Use Case: Essential for understanding overall grid interaction and total flexibility potential at a location
- Example: A home with a 5kW solar panel and 10kWh battery might have a site forecast showing net off-take from the grid when accounting for household baseload
Resource Forecasts
- Scope: Represent the individual resource's expected power consumption or production
- Composition: Only the specific resource's behaviour, isolated from other site activity
- Use Case: Critical for resource-specific flexibility planning and optimisation
- Example: The same 5kW solar panel's resource forecast would show only its expected generation, separate from household consumption
- Use site forecasts when planning grid-level flexibility services or understanding total site impact
- Use resource forecasts when optimising individual resource behaviour or calculating resource-specific flexibility potential
Query parameters
All forecast endpoints require the following parameters:
Parameter | Type | Required | Description |
---|---|---|---|
start | string | Yes | Start time (ISO 8601 with timezone) |
amount | number | Yes | Number of forecast data points to retrieve |
granularity | string | Yes | Time resolution (only PT15M supported) |
signal | string | Yes | Signal type (only net-meter supported currently) |
Time resolution
Currently, only PT15M
(15-minute intervals) is supported for granularity. This provides granular forecast data that aligns with most European electricity market intervals.
Time range limits
Powernaut offers forecasts up to 48 hours in the future. However, by default, requests are limited to 4 hours (16 data points at 15-minute intervals). To access the full 48-hour forecast horizon, please contact our support team to enable extended forecasting for your account.
Example request - Site Forecast
- Python
- JavaScript
- Java
- Go
- C#
- cURL
import requests
url = "https://api.powernaut.io/v1/connect/sites/<uuid>/forecast?start=<string>&amount=<number>&granularity=<string>&signal=<string>"
payload = {}
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer <token>'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
const myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Authorization", "Bearer <token>");
const requestOptions = {
method: "GET",
headers: myHeaders,
redirect: "follow"
};
fetch("https://api.powernaut.io/v1/connect/sites/<uuid>/forecast?start=<string>&amount=<number>&granularity=<string>&signal=<string>", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "");
Request request = new Request.Builder()
.url("https://api.powernaut.io/v1/connect/sites/<uuid>/forecast?start=<string>&amount=<number>&granularity=<string>&signal=<string>")
.method("GET", body)
.addHeader("Accept", "application/json")
.addHeader("Authorization", "Bearer <token>")
.build();
Response response = client.newCall(request).execute();
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
url := "https://api.powernaut.io/v1/connect/sites/<uuid>/forecast?start=%3Cstring%3E&amount=%3Cnumber%3E&granularity=%3Cstring%3E&signal=%3Cstring%3E"
method := "GET"
client := &http.Client {
}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Authorization", "Bearer <token>")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.powernaut.io/v1/connect/sites/<uuid>/forecast?start=<string>&amount=<number>&granularity=<string>&signal=<string>");
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Authorization", "Bearer <token>");
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
curl --location 'https://api.powernaut.io/v1/connect/sites/<uuid>/forecast?start=%3Cstring%3E&amount=%3Cnumber%3E&granularity=%3Cstring%3E&signal=%3Cstring%3E' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <token>'
Example request - Resource Forecast
- Python
- JavaScript
- Java
- Go
- C#
- cURL
import requests
url = "https://api.powernaut.io/v1/connect/resources/<uuid>/forecast?start=<string>&amount=<number>&granularity=<string>&signal=<string>"
payload = {}
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer <token>'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
const myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Authorization", "Bearer <token>");
const requestOptions = {
method: "GET",
headers: myHeaders,
redirect: "follow"
};
fetch("https://api.powernaut.io/v1/connect/resources/<uuid>/forecast?start=<string>&amount=<number>&granularity=<string>&signal=<string>", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "");
Request request = new Request.Builder()
.url("https://api.powernaut.io/v1/connect/resources/<uuid>/forecast?start=<string>&amount=<number>&granularity=<string>&signal=<string>")
.method("GET", body)
.addHeader("Accept", "application/json")
.addHeader("Authorization", "Bearer <token>")
.build();
Response response = client.newCall(request).execute();
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
url := "https://api.powernaut.io/v1/connect/resources/<uuid>/forecast?start=%3Cstring%3E&amount=%3Cnumber%3E&granularity=%3Cstring%3E&signal=%3Cstring%3E"
method := "GET"
client := &http.Client {
}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Authorization", "Bearer <token>")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.powernaut.io/v1/connect/resources/<uuid>/forecast?start=<string>&amount=<number>&granularity=<string>&signal=<string>");
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Authorization", "Bearer <token>");
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
curl --location 'https://api.powernaut.io/v1/connect/resources/<uuid>/forecast?start=%3Cstring%3E&amount=%3Cnumber%3E&granularity=%3Cstring%3E&signal=%3Cstring%3E' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <token>'
We accept an ISO 8601 start parameter with either UTC (Z
) or an offset (e.g. +01:00
). Responses are always returned in UTC.
Response format
{
"data": [
{
"start": "2024-01-15T10:00:00Z",
"end": "2024-01-15T10:15:00Z",
"value": "2500.000000",
"unit": "kW",
"last_updated": "2024-01-15T09:45:00Z",
"model_id": "default-20240115"
},
{
"start": "2024-01-15T10:15:00Z",
"end": "2024-01-15T10:30:00Z",
"value": "2750.000000",
"unit": "kW",
"last_updated": "2024-01-15T09:45:00Z",
"model_id": "default-20240115"
},
{
"start": "2024-01-15T10:30:00Z",
"end": "2024-01-15T10:45:00Z",
"value": null,
"unit": null,
"last_updated": null,
"model_id": null
}
]
}
- start/end: ISO 8601 timestamps defining the forecast interval
- value: forecasted power value in kW as a string, may be
null
- unit: unit of the value (always
kW
for power) - last_updated: when this data point was last updated
- model_id: identifier of the model used (mostly for diagnostics)
Units and interpretation
- Values are in kW (kilowatts) and represent the average power over each interval.
- Forecasts describe the baseline behaviour of the site or resource without any flexibility activations applied. (For more info, see Forecasts vs Baselines.)
For sites:
- Positive values indicate offtake (import) from the grid
- Negative values indicate injection (export) into the grid
For resources:
- Positive values indicate consumption
- Negative values indicate production
Missing data behaviour
- If a data point is unavailable, we still return an array item for that interval with the correct
start
andend
timestamps. - In that case,
value
,unit
,last_updated
andmodel_id
will benull
for those intervals.
You should check for these null
values in the response, and handle them accordingly. Usually this happens if we haven't collected enough high-quality data from the underlying site or resource to make a forecast or when the requested interval is too far in the future.
For missing data points, consider implementing interpolation techniques (forward, linear, spline, ...) to estimate values between available forecast points.
Models, provenance and update frequency
- Under the hood, multiple models can be combined depending on data availability. A single forecasted time series may contain values from different models across intervals. Use
model_id
to track provenance. - Update rates vary by underlying model. There is no global fixed cadence; some models update more frequently than others.
Error handling
Common error responses
Status Code | Error | Description |
---|---|---|
400 | Bad Request | Invalid time range or parameters |
403 | Forbidden | Forecasting feature not enabled |
404 | Not Found | Site or resource not found |
422 | Unprocessable Entity | Time range exceeds allowed horizon |
Example Error Response
{
"error": "Forecast horizon exceeded",
"message": "Forecast requests are limited to 4 hours in the future. Contact support to enable extended forecasting.",
"code": "HORIZON_LIMIT_EXCEEDED"
}
Best practices
Follow these guidelines to build robust, efficient forecast integrations that perform well in production.
⏱️ Reasonable Time Ranges
Request forecasts in manageable chunks to avoid requesting data that you do not need.
- Standard: 1-hour windows (4 data points)
- Maximum: 48-hour windows (192 data points)
- Avoid: Fetching the same timestamp multiple times in short succession. Forecasts do get updated periodically, but you should not expect significant changes within a few minutes of the previous request.
Example: If you fetch a forecast every hour, you should request 4 data points (1 hour) rather than 192 data points (48 hours).
🔍 Handle Missing Data
Always validate forecast data and implement graceful fallbacks for missing values.
// Check for null values and implement fallbacks
const getForecastValue = (point, fallbackValue = 0) => {
return point.value !== null ? parseFloat(point.value) : fallbackValue;
};
- ✅ Check for
null
values in response - ✅ Implement fallback / interpolation strategies
- ✅ Log missing data for monitoring
🚀 Cache Appropriately
Implement smart caching to balance data freshness with API efficiency.
- TTL: 15 minutes for most use cases
- Key strategy: Include site/resource ID and time window
- Invalidation: Clear cache when uploading new forecasts
- Memory management: Remove oldest cached entries when memory limits are reached
Implementation: Use a simple cache with 15-minute TTL, keyed by ${siteId}-${startTime}
to avoid redundant API calls.
Next steps
Now that you can retrieve forecasts, learn how to upload your own forecasts to improve our understanding of your resources and unlock better flexibility opportunities.