Photovoltaic
Full curtailment​
During a typical day of PV production, energy is used in two ways:
- Injected into the grid
- Used for self-consumption
Injection into the grid can be curtailed at any time, meaning production can be stopped. For self-consumption scenarios, we can switch to consuming power from the grid while curtailing the PV production. Both actions provide flexibility equal to the power produced by the PV installation.
The baseline for a typical day is shown in green. While forecasting PV production can be challenging, using simple rolling windows based on the last 15 minutes can provide reasonable estimates for the next quarter-hour.

Once the baseline is established, we can submit downward bids for the full amount of the forecasted production.
Stopping PV production reduces the amount of energy on the grid, which is why this is called a "downward" bid.
Providing upward flexibility with a PV installation is also possible. If you have planned curtailment, for example on moments of negative market prices, you can still offer to start producing again.
Bid Curve​
The bid curve is a single point, where the red line indicates the baseline production (negative), and the green dot indicates the bid (0kW = curtailment).
The API call to make this bid is as follows:
- Python
- JavaScript
- Java
- Go
- C#
- cURL
import requests
import json
url = "https://api.powernaut.io/v1/connect/resources/<uuid>/bid"
payload = json.dumps({
"timing": {
"start": "2024-07-01T14:00:00Z",
"end": "2024-07-01T14:15:00Z"
},
"curve": [
{
"value": "0"
}
]
})
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer <token>'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Accept", "application/json");
myHeaders.append("Authorization", "Bearer <token>");
const raw = JSON.stringify({
"timing": {
"start": "2024-07-01T14:00:00Z",
"end": "2024-07-01T14:15:00Z"
},
"curve": [
{
"value": "0"
}
]
});
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://api.powernaut.io/v1/connect/resources/<uuid>/bid", 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("application/json");
RequestBody body = RequestBody.create(mediaType, "{\"timing\":{\"start\":\"2024-07-01T14:00:00Z\",\"end\":\"2024-07-01T14:15:00Z\"},\"curve\":[{\"value\":\"0\"}]}");
Request request = new Request.Builder()
.url("https://api.powernaut.io/v1/connect/resources/<uuid>/bid")
.method("POST", body)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.addHeader("Authorization", "Bearer <token>")
.build();
Response response = client.newCall(request).execute();
package main
import (
"fmt"
"strings"
"net/http"
"io/ioutil"
)
func main() {
url := "https://api.powernaut.io/v1/connect/resources/<uuid>/bid"
method := "POST"
payload := strings.NewReader(`{"timing":{"start":"2024-07-01T14:00:00Z","end":"2024-07-01T14:15:00Z"},"curve":[{"value":"0"}]}`)
client := &http.Client {
}
req, err := http.NewRequest(method, url, payload)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Content-Type", "application/json")
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.Post, "https://api.powernaut.io/v1/connect/resources/<uuid>/bid");
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Authorization", "Bearer <token>");
var content = new StringContent("{\"timing\":{\"start\":\"2024-07-01T14:00:00Z\",\"end\":\"2024-07-01T14:15:00Z\"},\"curve\":[{\"value\":\"0\"}]}", null, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
curl --location 'https://api.powernaut.io/v1/connect/resources/<uuid>/bid' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{"timing":{"start":"2024-07-01T14:00:00Z","end":"2024-07-01T14:15:00Z"},"curve":[{"value":"0"}]}'
Self-consumption​
If the PV energy is partly used for self-consumption, it makes sense to send two points in your bid curve:
- The first point is to curtail all injected energy at a price of 0€/kWh.
- The second point is to curtail the additionally self-consumed energy, for example at a price of €0.10/kWh.
Bid Curve​
Assuming the PV installation is producing 5kW, and self-consuming 3kW, the bid curve would be as follows:
The API call to make this bid is then as follows:
- Python
- JavaScript
- Java
- Go
- C#
- cURL
import requests
import json
url = "https://api.powernaut.io/v1/connect/resources/<uuid>/bid"
payload = json.dumps({
"timing": {
"start": "2024-07-01T14:00:00Z",
"end": "2024-07-01T14:15:00Z"
},
"curve": [
{
"value": "-3"
},
{
"value": "0",
"price": "0.10"
}
]
})
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer <token>'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Accept", "application/json");
myHeaders.append("Authorization", "Bearer <token>");
const raw = JSON.stringify({
"timing": {
"start": "2024-07-01T14:00:00Z",
"end": "2024-07-01T14:15:00Z"
},
"curve": [
{
"value": "-3"
},
{
"value": "0",
"price": "0.10"
}
]
});
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://api.powernaut.io/v1/connect/resources/<uuid>/bid", 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("application/json");
RequestBody body = RequestBody.create(mediaType, "{\"timing\":{\"start\":\"2024-07-01T14:00:00Z\",\"end\":\"2024-07-01T14:15:00Z\"},\"curve\":[{\"value\":\"-3\"},{\"value\":\"0\",\"price\":\"0.10\"}]}");
Request request = new Request.Builder()
.url("https://api.powernaut.io/v1/connect/resources/<uuid>/bid")
.method("POST", body)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.addHeader("Authorization", "Bearer <token>")
.build();
Response response = client.newCall(request).execute();
package main
import (
"fmt"
"strings"
"net/http"
"io/ioutil"
)
func main() {
url := "https://api.powernaut.io/v1/connect/resources/<uuid>/bid"
method := "POST"
payload := strings.NewReader(`{"timing":{"start":"2024-07-01T14:00:00Z","end":"2024-07-01T14:15:00Z"},"curve":[{"value":"-3"},{"value":"0","price":"0.10"}]}`)
client := &http.Client {
}
req, err := http.NewRequest(method, url, payload)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Content-Type", "application/json")
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.Post, "https://api.powernaut.io/v1/connect/resources/<uuid>/bid");
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Authorization", "Bearer <token>");
var content = new StringContent("{\"timing\":{\"start\":\"2024-07-01T14:00:00Z\",\"end\":\"2024-07-01T14:15:00Z\"},\"curve\":[{\"value\":\"-3\"},{\"value\":\"0\",\"price\":\"0.10\"}]}", null, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
curl --location 'https://api.powernaut.io/v1/connect/resources/<uuid>/bid' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{"timing":{"start":"2024-07-01T14:00:00Z","end":"2024-07-01T14:15:00Z"},"curve":[{"value":"-3"},{"value":"0","price":"0.10"}]}'