Activating
When a bid is accepted, the resource is activated and should start providing flexibility. This guide explains the activation process and the different methods to get notified about it.
Acceptance​
Bids can be accepted at any time before or during their time window, up until 30 seconds before the end time.
Your resources can receive multiple activations for the same bid. This allows us to:
- Update the setpoint to a different value within your defined bounds
- Cancel the activation by sending a null value for the setpoint
Your integration must be able to handle these dynamic changes to maximize flexibility potential. For more information about cancellations, see the Cancellation guide.
Timing​
Most activations happen either right before or during the quarter-hour of activation. Make sure your system can handle these near real-time activations.
If this timing doesn't work for your setup, you can use the expiration feature to ensure sufficient preparation time.
A bid between 14:00:00 - 14:15:00
for an extra 12kW
is submitted,
and activated at 14:10:00
.
Instead of the original 3kWh
of extra energy that would've been consumed,
only 1kWh
of extra energy will actually have been consumed since we lost 10 minutes of the entire quarter-hour.
When bids get accepted, other bids for that same site can become invalid because the schedule has been affected.
For this reason, upon acceptance of a bid, all other bids for that site will be cancelled, no matter the time window. You should resubmit all bids from this site.
Notification Methods​
There are two ways to get notified about your bid, MQTT or webhooks.
The default notification method is MQTT.
If the resource or bid has an acceptance webhook configured, the platform will use that webhook instead.
MQTT​
Each resource has its dedicated setpoint
topic: resource/{id}/commands/setpoint
.
The message that will be sent upon activation contains two fields:
- A
value
field that specifies the desired setpoint in Watt (W) - An optional
timing
field, with astart
andend
time in ISO 8601 format
{
"timing": {
"start": "2024-07-01T14:00:00Z",
"end": "2024-07-01T14:15:00Z"
},
"value": 5812
}
For the same bid, you may receive:
- Multiple messages with different
value
fields to adjust the setpoint - A message with
value: null
to cancel the activation
See the Cancellation guide for more details on handling cancellations.
The timing
field is optional.
Without timing, we expect the resource to follow the setpoint as quickly as possible.
To connect to MQTT, follow the same approach as described in Streaming data
The specification for this real-time data flow is also available in the AsyncApi specification.
Webhooks​
Webhooks can be configured on bid
and on resource
level.
If an accepted
is available on bid
and an accepted
has been configured on resource
as well, the platform will use the the value found on the bid
.
A webhook will automatically notify your system when your bid is accepted. This seamless method requests your energy assets to adjust their output/input as required.
The system automatically marks the activation as complete once the webhook is handled successfully.
Webhooks are always made using a POST
HTTP request.
Registering a webhook​
You can register a webhook upon bid submission.
- 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": "1",
"price": "4"
}
],
"webhooks": {
"accepted": "https://domain.tld/bid/accepted?param=value"
}
})
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": "1",
"price": "4"
}
],
"webhooks": {
"accepted": "https://domain.tld/bid/accepted?param=value"
}
});
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\":\"1\",\"price\":\"4\"}],\"webhooks\":{\"accepted\":\"https://domain.tld/bid/accepted?param=value\"}}");
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":"1","price":"4"}],"webhooks":{"accepted":"https://domain.tld/bid/accepted?param=value"}}`)
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\":\"1\",\"price\":\"4\"}],\"webhooks\":{\"accepted\":\"https://domain.tld/bid/accepted?param=value\"}}", 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":"1","price":"4"}],"webhooks":{"accepted":"https://domain.tld/bid/accepted?param=value"}}'
As an alternative, you can also register the webhook on resource
level. Keep in mind that a webhook configured on bid
level will override this configuration.
- Python
- JavaScript
- Java
- Go
- C#
- cURL
import requests
import json
url = "https://api.powernaut.io/v1/connect/resources"
payload = json.dumps({
"connection_point_id": "<uuid>",
"type": "electric_vehicle_charging_point",
"power": {
"active": {
"minimum": "0",
"maximum": "7.4"
}
},
"webhooks": {
"accepted": "https://domain.tld/bid/accepted?param=value"
}
})
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({
"connection_point_id": "<uuid>",
"type": "electric_vehicle_charging_point",
"power": {
"active": {
"minimum": "0",
"maximum": "7.4"
}
},
"webhooks": {
"accepted": "https://domain.tld/bid/accepted?param=value"
}
});
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow"
};
fetch("https://api.powernaut.io/v1/connect/resources", 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, "{\"connection_point_id\":\"<uuid>\",\"type\":\"electric_vehicle_charging_point\",\"power\":{\"active\":{\"minimum\":\"0\",\"maximum\":\"7.4\"}},\"webhooks\":{\"accepted\":\"https://domain.tld/bid/accepted?param=value\"}}");
Request request = new Request.Builder()
.url("https://api.powernaut.io/v1/connect/resources")
.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"
method := "POST"
payload := strings.NewReader(`{"connection_point_id":"<uuid>","type":"electric_vehicle_charging_point","power":{"active":{"minimum":"0","maximum":"7.4"}},"webhooks":{"accepted":"https://domain.tld/bid/accepted?param=value"}}`)
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");
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Authorization", "Bearer <token>");
var content = new StringContent("{\"connection_point_id\":\"<uuid>\",\"type\":\"electric_vehicle_charging_point\",\"power\":{\"active\":{\"minimum\":\"0\",\"maximum\":\"7.4\"}},\"webhooks\":{\"accepted\":\"https://domain.tld/bid/accepted?param=value\"}}", 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' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <token>' \
--data '{"connection_point_id":"<uuid>","type":"electric_vehicle_charging_point","power":{"active":{"minimum":"0","maximum":"7.4"}},"webhooks":{"accepted":"https://domain.tld/bid/accepted?param=value"}}'
You can provide query parameters to the webhook, which allows for state tracking.
Fetching details​
When a bid is accepted, your webhook will be called using the POST
method.
You will receive the following JSON payload:
{
"id": "5aaeae38-70f7-41e5-8e58-01b4d79fabb7"
}
This is the ID of the bid that has been accepted, you should look up the bid to receive the activation details as follows:
- Python
- JavaScript
- Java
- Go
- C#
- cURL
import requests
url = "https://api.powernaut.io/v1/connect/bids/5aaeae38-70f7-41e5-8e58-01b4d79fabb7"
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/bids/5aaeae38-70f7-41e5-8e58-01b4d79fabb7", 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/bids/5aaeae38-70f7-41e5-8e58-01b4d79fabb7")
.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/bids/5aaeae38-70f7-41e5-8e58-01b4d79fabb7"
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/bids/5aaeae38-70f7-41e5-8e58-01b4d79fabb7");
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/bids/5aaeae38-70f7-41e5-8e58-01b4d79fabb7' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <token>'
The activation details can be found in the activation
property of the response.
{
activation: {
timing: {
start: "2024-07-01T14:00:00Z",
end: "2024-07-01T14:15:00Z",
},
value: "5",
},
resourceId: "6dcc8f5d-2786-4672-816c-5ce221db62f5",
// ... other fields for this bid
}
Your webhook may be called multiple times for the same bid with:
- Different
value
numbers to adjust the setpoint within your defined bounds value: null
to indicate a cancellation
Your integration must be able to handle these dynamic changes. See the Cancellation guide for details on handling cancellations.
The value
of 5
is the requested power value that the resource referenced by 6dcc8f5d-2786-4672-816c-5ce221db62f5
must provide in the given time window.
Note that the timing
property tells you when to activate that power value,
which could be immediately.
We do not send over the activation details to avoid common attack vectors on webhooks.
Retries​
If you fail to reply to within 5 seconds with a successful response, this webhook will be retried up to 3 times.
To be on the safe side, make sure your integration can handle potential duplicate calls ("at least once"). Each activation for a bid contains a unique ID, which you can use to identify duplicate events.
If you know that a specific activation request cannot be satisfied, you send a 406 (Not Acceptable) response. Webhooks returning this status code will not be retried.
This is useful, for example, when you notice that a car has suddenly been disconnected.