Voltus API Reference
Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu.
Welcome to the Voltus API Reference, where you can learn all about Voltus' API and developer tooling.
The live production API hostname is api.voltus.co
.
Introduction to the Voltus platform
Voltus gives partners a seamless way to monetize their DER assets in wholesale energy markets, and in some cases, directly with utilities.
Voltus helps you monetize your assets end-to-end:
- Register your assets: Tell us which DERs you want to monetize, and we'll register them with grid operators and utilities on your behalf.
- Share your availability and capacity: Let us know when and how much flexible energy capacity you want to sell into the market.
- Monitor your consumption: Give us real-time visibility into your energy usage, so we can calculate your performance, and you can get paid. We can provide hardware solutions if necessary.
- Get dispatched: Our market-integrated platform communicates demand response events and pricing signals to you and your DERs.
- Get paid: We handle settlement with the markets or utilities and send cash your way. We also provide you with detailed performance and financial reporting.
You will work with a dedicated account manager to support integration activities. Currently, the Voltus API supports steps 3 and 4. Future versions of the API will support this flow end-to-end.
In addition to our REST API, we also offer OpenADR capabilities (OpenADR 2.0a), the industry standard for Automated Demand Response. Read more about our implementation at the end of the documentation.
Versioning
We use date-based versioning. api.voltus.co
will always redirect to the latest version.
Current version: api.voltus.co/2022-04-15
Changelog
* Renamed "Facilities" to "Sites * Entity IDs are now strings instead of ints * New Authorization header `X-Voltus-API-Key` * Removed support for `Authorization: Bearer` headerPrevious versions:
- api.voltus.co/2020-12-30
Authentication
- API Key (api_key_header_X-Voltus-API-Key)
- Parameter Name: X-Voltus-API-Key, in: header.
The API uses API key authentication. API keys are provided by your account manager. Each request must include an
X-Voltus-API-Key
header where the value is your API key. Code examples for each endpoint will demonstrate how this header can be added in various languages.
- Parameter Name: X-Voltus-API-Key, in: header.
The API uses API key authentication. API keys are provided by your account manager. Each request must include an
Errors
Example error responses
{
"message": "A create webhook request must contain an 'events' field",
"type": "Bad Request"
}
{
"message": "Permission denied",
"type": "Unauthorized"
}
Voltus uses conventional HTTP response codes to indicate the success or failure of an API request. In general: Codes in the 2xx
range indicate success. Codes in the 4xx
range indicate an error that failed given the information provided (e.g., a required parameter was omitted, an api key has been revoked, etc.). Codes in the 5xx
range indicate an error with Voltus's servers (these are rare).
Attributes
message
string
A human-readable message providing more details about the error.
type
string
The error type. These types will always match the name of the name of the http status code that is used in the response. One of Unauthorized
, Bad Request
, Internal Server Error
, Too Many Requests
or Not Found
.
Dispatches
A Dispatch
is a discrete period of time that Voltus, a grid operator, or a utility calls upon a participant to reduce electricity consumption.
Along with dispatches related to regular program events, Voltus will also send test events to validate that a customer can receive notifications, and that they can curtail their load. These types of test events are coordinated in advance.
Dispatch notification timing varies depending on the programs you participate in. Some are sent as far as 36hrs in advance, while fast response programs will dispatch with only 10 minutes of notice before the resource must be fully curtailed. Make sure to work with your Voltus account manager to better understand the specific dispatch timing for the programs that you participate in.
When all dispatch notifications are sent they will always contain a start_time
that marks the beginning of the event. Some dispatches have a null end_time
when they are created, and you must listen for future updates to understand when the dispatch will end.
List recent dispatches
Code samples
curl -X GET https://sandbox.voltus.co/2022-04-15/dispatches \
-H 'Accept: application/json' \
-H 'X-Voltus-API-Key: API_KEY' \
import requests
headers = {
'Accept': 'application/json',
'X-Voltus-API-Key': 'API_KEY',
}
r = requests.get('https://sandbox.voltus.co/2022-04-15/dispatches', headers=headers)
print(r.json())
URL obj = new URL("https://sandbox.voltus.co/2022-04-15/dispatches");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
const headers = {
'Accept':'application/json',
'X-Voltus-API-Key':'API_KEY',
};
fetch('https://sandbox.voltus.co/2022-04-15/dispatches',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Accept": []string{"application/json"},
"X-Voltus-API-Key": []string{"API_KEY"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("GET", "https://sandbox.voltus.co/2022-04-15/dispatches", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
GET /2022-04-15/dispatches
Returns a list of dispatches.
By default, the list dispatches endpoint returns all dispatch events from the previous 24 hours. This endpoint is commonly used to implement a polling pattern to listen for new dispatch notifications.
If you would like to implement a polling pattern to check for new dispatches, we recommend the following approach:
- Make requests to this endpoint every 30 seconds in a loop. You may make requests more quickly if you'd like, but if you need very fast response times, we recommend using Webhooks instead. Excessive polling requests made to this endpoint maybe result in a
429 - Too Many Requests
response. - When you make a request, check for new dispatches in the response body. Each dispatch has a unique
id
attribute that can be used for identification. - Check each new dispatch's
start_time
and list ofsites
to see if you need to respond. - If you need to respond, begin curtailment for the affected sites.
- On subsequent requests, make sure you're checking for any updates that have been made to the active dispatch.
end_time
could be populated or be updated, and in extremely rare cases, the dispatch may be cancelled. If the dispatch is cancelled, thecancelled
attribute would be set totrue
. - Once the dispatch
end_time
has passed, curtailment can stop.
Parameters
Example responses
200 Response
{
"dispatches": [
{
"authorized": true,
"end_time": "2020-12-22T05:04:02Z",
"id": "asdf",
"program": {
"market": "MISO",
"name": "MISO EDR",
"timezone": "US/Eastern"
},
"sites": [
{
"customer_location_id": "f4",
"dispatch_group": "f4",
"id": "asdf",
"name": "2 Park Ave"
},
{
"customer_location_id": "f4",
"dispatch_group": "f4",
"id": "asdf",
"name": "2 Park Ave"
}
],
"start_time": "2020-12-22T05:04:02Z"
},
{
"authorized": true,
"end_time": "2020-12-22T05:04:02Z",
"id": "asdf",
"program": {
"market": "MISO",
"name": "MISO EDR",
"timezone": "US/Eastern"
},
"sites": [
{
"customer_location_id": "f4",
"dispatch_group": "f4",
"id": "asdf",
"name": "2 Park Ave"
},
{
"customer_location_id": "f4",
"dispatch_group": "f4",
"id": "asdf",
"name": "2 Park Ave"
}
],
"start_time": "2020-12-22T05:04:02Z"
},
{
"authorized": true,
"end_time": "2020-12-22T05:04:02Z",
"id": "asdf",
"program": {
"market": "MISO",
"name": "MISO EDR",
"timezone": "US/Eastern"
},
"sites": [
{
"customer_location_id": "f4",
"dispatch_group": "f4",
"id": "asdf",
"name": "2 Park Ave"
},
{
"customer_location_id": "f4",
"dispatch_group": "f4",
"id": "asdf",
"name": "2 Park Ave"
}
],
"start_time": "2020-12-22T05:04:02Z"
}
],
"page": 1,
"perPage": 50
}
Response Attributes
dispatches
array
authorized
boolean
The authorization status of the dispatch. If this value is false, a dispatch is cancelled, and curtailment can stop immediately.
end_time
string
End time of the dispatch. This attribute can be null, but all dispatches will eventually have an end time unless they are cancelled.
id
string
Primary key of the dispatch.
program
object
market
string
The market the program is participating in
name
string
The program's name
timezone
string
The program's time zone
sites
array
The sites that must curtail as a result of this dispatch
customer_location_id
string
An identifier for this site provided by a customer. This can be any identifier supplied by the site owner, such as a store number.
dispatch_group
string
The group that this site is a part of
id
string
Primary key of the site
name
string
Name of the site
start_time
string
Start time of the dispatch
page
integer
Page number
perPage
integer
Number of items per page
Retrieve a dispatch
Code samples
curl -X GET https://sandbox.voltus.co/2022-04-15/dispatches/{id} \
-H 'Accept: application/json' \
-H 'X-Voltus-API-Key: API_KEY' \
import requests
headers = {
'Accept': 'application/json',
'X-Voltus-API-Key': 'API_KEY',
}
r = requests.get('https://sandbox.voltus.co/2022-04-15/dispatches/{id}', headers=headers)
print(r.json())
URL obj = new URL("https://sandbox.voltus.co/2022-04-15/dispatches/{id}");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
const headers = {
'Accept':'application/json',
'X-Voltus-API-Key':'API_KEY',
};
fetch('https://sandbox.voltus.co/2022-04-15/dispatches/{id}',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Accept": []string{"application/json"},
"X-Voltus-API-Key": []string{"API_KEY"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("GET", "https://sandbox.voltus.co/2022-04-15/dispatches/{id}", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
GET /2022-04-15/dispatches/{id}
Retrieves the dispatch with the given ID.
Can be used to explicitly check for the status of an individual dispatch.
Parameters
id
string
The dispatch ID
Example responses
200 Response
{
"authorized": false,
"end_time": "2020-12-22T05:04:02Z",
"id": "asdf",
"program": {
"market": "MISO",
"name": "MISO EDR",
"timezone": "US/Eastern"
},
"sites": [
{
"customer_location_id": "f4",
"dispatch_group": "f4",
"id": "asdf",
"name": "2 Park Ave"
},
{
"customer_location_id": "f4",
"dispatch_group": "f4",
"id": "asdf",
"name": "2 Park Ave"
}
],
"start_time": "2020-12-22T05:04:02Z"
}
Response Attributes
authorized
boolean
The authorization status of the dispatch. If this value is false, a dispatch is cancelled, and curtailment can stop immediately.
end_time
string
End time of the dispatch. This attribute can be null, but all dispatches will eventually have an end time unless they are cancelled.
id
string
Primary key of the dispatch.
program
object
market
string
The market the program is participating in
name
string
The program's name
timezone
string
The program's time zone
sites
array
The sites that must curtail as a result of this dispatch
customer_location_id
string
An identifier for this site provided by a customer. This can be any identifier supplied by the site owner, such as a store number.
dispatch_group
string
The group that this site is a part of
id
string
Primary key of the site
name
string
Name of the site
start_time
string
Start time of the dispatch
Schedule
Make changes to a site's schedule
Code samples
curl -X POST https://sandbox.voltus.co/2022-04-15/schedule/sites/{site_id} \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'X-Voltus-API-Key: API_KEY' \
import requests
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Voltus-API-Key': 'API_KEY',
}
r = requests.post('https://sandbox.voltus.co/2022-04-15/schedule/sites/{site_id}', headers=headers)
print(r.json())
URL obj = new URL("https://sandbox.voltus.co/2022-04-15/schedule/sites/{site_id}");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
const inputBody = '{
"available": false,
"end_time": "2020-12-22T16:00:00Z",
"start_time": "2020-12-22T14:00:00-07:00"
}';
const headers = {
'Content-Type':'application/json',
'Accept':'application/json',
'X-Voltus-API-Key':'API_KEY',
};
fetch('https://sandbox.voltus.co/2022-04-15/schedule/sites/{site_id}',
{
method: 'POST',
body: inputBody,
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Content-Type": []string{"application/json"},
"Accept": []string{"application/json"},
"X-Voltus-API-Key": []string{"API_KEY"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("POST", "https://sandbox.voltus.co/2022-04-15/schedule/sites/{site_id}", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
POST /2022-04-15/schedule/sites/{site_id}
Changes the availability of a site.
On a successful response (status code 200), the site’s availability has been updated. If availability was turned off, the site will no longer be dispatched during the requested time period. turning on availability is currently not supported.
A non-200 status code response indicates the site's availability has not been updated. Outside of authentication issues and unexpected technical failures, this could happen if it is too late to propagate the requested schedule change to market. Cutoff times vary from market to market. Request failure means that the site may still be dispatched.
Body parameter
{
"available": false,
"end_time": "2020-12-22T16:00:00Z",
"start_time": "2020-12-22T14:00:00-07:00"
}
Parameters
site_id
string
The id of the site.
body
object
none
available
boolean
Whether or not the site is dispatchable during the given time period period. Setting availability to true is currently not supported.
end_time
string
An RFC 3339 date-time string (with time-offsets) indicating the start time of availability block.
start_time
string
An RFC 3339 date-time string (with time-offsets) indicating the start time of availability block.
Example responses
Sites
A site represents a single physical location that can be curtailed in response to a demand response event.
List all sites
Code samples
curl -X GET https://sandbox.voltus.co/2022-04-15/sites \
-H 'Accept: application/json' \
-H 'X-Voltus-API-Key: API_KEY' \
import requests
headers = {
'Accept': 'application/json',
'X-Voltus-API-Key': 'API_KEY',
}
r = requests.get('https://sandbox.voltus.co/2022-04-15/sites', headers=headers)
print(r.json())
URL obj = new URL("https://sandbox.voltus.co/2022-04-15/sites");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
const headers = {
'Accept':'application/json',
'X-Voltus-API-Key':'API_KEY',
};
fetch('https://sandbox.voltus.co/2022-04-15/sites',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Accept": []string{"application/json"},
"X-Voltus-API-Key": []string{"API_KEY"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("GET", "https://sandbox.voltus.co/2022-04-15/sites", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
GET /2022-04-15/sites
Returns a list of your sites.
The sites endpoint is important because a given API key will only get dispatch notifications that include sites visible to the account. You should make sure that all expected sites are returned by this endpoint.
Sites include id
and customer_location_id
fields that can be used to identify sites in a dispatch communication. A site's name
value may change, and should not be used as a static identifier.
Parameters
Example responses
200 Response
{
"page": 1,
"perPage": 50,
"sites": [
{
"customer_location_id": "f4",
"id": "asdf",
"meters": [
{
"id": "asdf",
"name": "Generator"
},
{
"id": "asdf",
"name": "Generator"
}
],
"name": "2 Park Ave"
},
{
"customer_location_id": "f4",
"id": "asdf",
"meters": [
{
"id": "asdf",
"name": "Generator"
},
{
"id": "asdf",
"name": "Generator"
}
],
"name": "2 Park Ave"
}
]
}
Response Attributes
page
integer
Page number
perPage
integer
Number of items per page
sites
array
customer_location_id
string
An identifier for this site provided by a customer. This can be any identifier supplied by the site owner, such as a store number.
id
string
Primary key of the site
meters
array
Meters associated with this site
id
string
Primary key of the meter
name
string
Name of the meter
name
string
Name of the site
Telemetry
Telemetry is a critical component of energy market participation. Voltus, grid operators, and utilities use telemetry data to verify that a DER is delivering expected performance.
Today, these telemetry endpoints only deal with power consumption. kW
values can be posted along with interval length to communicate the rate that energy is consumed over a period of time. Other telemetry information, like power factor, state of charge, and power consumption with flow direction will be added at a later date. If providing or consuming this information is important to you, please reach out to your account manager.
Upload telemetry data
Code samples
curl -X POST https://sandbox.voltus.co/2022-04-15/telemetry \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'X-Voltus-API-Key: API_KEY' \
import requests
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Voltus-API-Key': 'API_KEY',
}
r = requests.post('https://sandbox.voltus.co/2022-04-15/telemetry', headers=headers)
print(r.json())
URL obj = new URL("https://sandbox.voltus.co/2022-04-15/telemetry");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
const inputBody = '{
"telemetry": [
{
"interval_seconds": 300,
"kW": 50.6,
"meter_id": "asdf",
"site_id": "asdf",
"timestamp": "2020-12-22T05:04:02Z"
},
{
"interval_seconds": 300,
"kW": 50.6,
"meter_id": "asdf",
"site_id": "asdf",
"timestamp": "2020-12-22T05:04:02Z"
},
{
"interval_seconds": 300,
"kW": 50.6,
"meter_id": "asdf",
"site_id": "asdf",
"timestamp": "2020-12-22T05:04:02Z"
},
{
"interval_seconds": 300,
"kW": 50.6,
"meter_id": "asdf",
"site_id": "asdf",
"timestamp": "2020-12-22T05:04:02Z"
}
]
}';
const headers = {
'Content-Type':'application/json',
'Accept':'application/json',
'X-Voltus-API-Key':'API_KEY',
};
fetch('https://sandbox.voltus.co/2022-04-15/telemetry',
{
method: 'POST',
body: inputBody,
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Content-Type": []string{"application/json"},
"Accept": []string{"application/json"},
"X-Voltus-API-Key": []string{"API_KEY"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("POST", "https://sandbox.voltus.co/2022-04-15/telemetry", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
POST /2022-04-15/telemetry
In order to send a single telemetry point, you'll need four pieces of information:
meter_id
: A meter ID that belongs to a site. In order to retrieve a valid ID you'll need to make a request to the/sites
endpointkW
: The amount of power consumed over the given time period. A positive value represents load/consumption. For meters capable of export to the grid (solar net metering, for example), exported energy is represented by a negative value.timestamp
: This timestamp is the "interval end" timestamp. It should be the RFC3339 format timestamp that marks the final moment of the telemetry interval.interval_seconds
: This is the length of time that the interval represents. If you are posting the totalkW
consumed in 5 minutes of data this value will be 300.
With that information you're ready to make a request. This endpoint is intended for bulk-data submission and can receive many telemetry points in the same request. You can also post non-contiguous intervals, intervals for multiple meters, and intervals that have been submitted before, all without issue. We'll handle the de-duplication of double-submitted points, and you can take this into consideration when implementing retry and failure logic.
There are some restrictions when posting to the telemetry endpoint.
- You can only post
interval_seconds
values that are divisible by 30. - You can't post an
interval_seconds
that is larger than 300.
Requests where any interval violates these rules will fail completely and will need to be corrected and resubmitted.
Body parameter
{
"telemetry": [
{
"interval_seconds": 300,
"kW": 50.6,
"meter_id": "asdf",
"site_id": "asdf",
"timestamp": "2020-12-22T05:04:02Z"
},
{
"interval_seconds": 300,
"kW": 50.6,
"meter_id": "asdf",
"site_id": "asdf",
"timestamp": "2020-12-22T05:04:02Z"
},
{
"interval_seconds": 300,
"kW": 50.6,
"meter_id": "asdf",
"site_id": "asdf",
"timestamp": "2020-12-22T05:04:02Z"
},
{
"interval_seconds": 300,
"kW": 50.6,
"meter_id": "asdf",
"site_id": "asdf",
"timestamp": "2020-12-22T05:04:02Z"
}
]
}
Parameters
body
object
none
telemetry
array
interval_seconds
integer
Number of seconds in this interval
kW
number
kW
meter_id
string
ID of the meter that you're writing telemetry for. Cannot be used with site_id.
site_id
string
ID of the site that you're writing telemetry for. Cannot be used with meter_id.
timestamp
string
The timestamp of the end of this telemetry interval
Example responses
Webhooks
Webhooks can be used to receive instant notifications about the creation or change of a given resource. Today, we only support webhooks that listen for new dispatches and updated dispatches.
Example of a webhook request body
{
"event":{
"name":"dispatch.create"
},
"resource":"/2022-04-15/dispatches/asdf"
}
Voltus uses light webhooks. The webhook request body will usually contain a single link to the resource that has been updated. The end-user must then make an additional request to retrieve the related resource.
If you are creating a webhook to listen for dispatch events, make sure to create a webhook with {"events": [{"name": "dispatch.update"}, {"name": "dispatch.create"}]}
to ensure that you get dispatch events on new event creation, and when any updates are made.
When making a webhook request only http status responses 200
, 201
, 202
, and 204
will be accepted as valid responses. All other status responses will be considered failures. The webhook request will follow HTTP redirects if the endpoint responds with status 301
or 302
and a valid Location
header.
If we encounter an error when trying to send a real event to a webhook endpoint, we'll retry 4 times before marking the event delivery as failed. The time between requests will increase exponentially between each retry.
Retrieve a webhook
Code samples
curl -X GET https://sandbox.voltus.co/2022-04-15/webhooks \
-H 'Accept: application/json' \
-H 'X-Voltus-API-Key: API_KEY' \
import requests
headers = {
'Accept': 'application/json',
'X-Voltus-API-Key': 'API_KEY',
}
r = requests.get('https://sandbox.voltus.co/2022-04-15/webhooks', headers=headers)
print(r.json())
URL obj = new URL("https://sandbox.voltus.co/2022-04-15/webhooks");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
const headers = {
'Accept':'application/json',
'X-Voltus-API-Key':'API_KEY',
};
fetch('https://sandbox.voltus.co/2022-04-15/webhooks',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Accept": []string{"application/json"},
"X-Voltus-API-Key": []string{"API_KEY"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("GET", "https://sandbox.voltus.co/2022-04-15/webhooks", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
GET /2022-04-15/webhooks
Returns a list of all webhooks that have been created.
Parameters
Example responses
200 Response
{
"page": 1,
"perPage": 50,
"webhooks": [
{
"events": [
{
"name": "dispatch.create"
},
{
"name": "dispatch.update"
}
],
"id": "dsft58ga",
"url": "https://example.com/listeners/voltus"
},
{
"events": [
{
"name": "dispatch.create"
},
{
"name": "dispatch.update"
}
],
"id": "dsft58ga",
"url": "https://example.com/listeners/voltus"
},
{
"events": [
{
"name": "dispatch.create"
},
{
"name": "dispatch.update"
}
],
"id": "dsft58ga",
"url": "https://example.com/listeners/voltus"
}
]
}
Response Attributes
page
integer
Page number
perPage
integer
Number of items per page
webhooks
array
events
array
List of events that will be posted to this webhook. Currently only `dispatch.create` and `dispatch.update` are supported.
name
string
Name of the event
id
string
Unique identifier for this webhook
url
string
URL that this webhook will send requests to
Create a webhook
Code samples
curl -X POST https://sandbox.voltus.co/2022-04-15/webhooks \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'X-Voltus-API-Key: API_KEY' \
import requests
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Voltus-API-Key': 'API_KEY',
}
r = requests.post('https://sandbox.voltus.co/2022-04-15/webhooks', headers=headers)
print(r.json())
URL obj = new URL("https://sandbox.voltus.co/2022-04-15/webhooks");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
const inputBody = '{
"events": [
{
"name": "dispatch.create"
},
{
"name": "dispatch.update"
}
],
"url": "https://example.com/listeners/voltus"
}';
const headers = {
'Content-Type':'application/json',
'Accept':'application/json',
'X-Voltus-API-Key':'API_KEY',
};
fetch('https://sandbox.voltus.co/2022-04-15/webhooks',
{
method: 'POST',
body: inputBody,
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Content-Type": []string{"application/json"},
"Accept": []string{"application/json"},
"X-Voltus-API-Key": []string{"API_KEY"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("POST", "https://sandbox.voltus.co/2022-04-15/webhooks", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
POST /2022-04-15/webhooks
Create a webhook by providing the events you'd like to listen for, and the URL where you'd like to receive notifications. When a webhook is created, we will send a test request to the provided URL. If it does not succeed, the webhook creation request will fail.
Body parameter
{
"events": [
{
"name": "dispatch.create"
},
{
"name": "dispatch.update"
}
],
"url": "https://example.com/listeners/voltus"
}
Parameters
body
object
none
events
array
List of events that will be posted to this webhook. Currently only `dispatch.create` and `dispatch.update` are supported.
name
string
Name of the event
url
string
URL that this webhook will send requests to
Example responses
200 Response
{
"events": [
{
"name": "dispatch.create"
},
{
"name": "dispatch.update"
}
],
"id": "dsft58ga",
"url": "https://example.com/listeners/voltus"
}
Response Attributes
events
array
List of events that will be posted to this webhook. Currently only `dispatch.create` and `dispatch.update` are supported.
name
string
Name of the event
id
string
Unique identifier for this webhook
url
string
URL that this webhook will send requests to
Delete a webhook
Code samples
curl -X DELETE https://sandbox.voltus.co/2022-04-15/webhooks/{id} \
-H 'Accept: application/json' \
-H 'X-Voltus-API-Key: API_KEY' \
import requests
headers = {
'Accept': 'application/json',
'X-Voltus-API-Key': 'API_KEY',
}
r = requests.delete('https://sandbox.voltus.co/2022-04-15/webhooks/{id}', headers=headers)
print(r.json())
URL obj = new URL("https://sandbox.voltus.co/2022-04-15/webhooks/{id}");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("DELETE");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
const headers = {
'Accept':'application/json',
'X-Voltus-API-Key':'API_KEY',
};
fetch('https://sandbox.voltus.co/2022-04-15/webhooks/{id}',
{
method: 'DELETE',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Accept": []string{"application/json"},
"X-Voltus-API-Key": []string{"API_KEY"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("DELETE", "https://sandbox.voltus.co/2022-04-15/webhooks/{id}", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
DELETE /2022-04-15/webhooks/{id}
Permanently deletes a webhook. It cannot be undone.
Parameters
id
string
Unique identifier for this webhook
Example responses
200 Response
{
"events": [
{
"name": "dispatch.create"
},
{
"name": "dispatch.update"
}
],
"id": "dsft58ga",
"url": "https://example.com/listeners/voltus"
}
Response Attributes
events
array
List of events that will be posted to this webhook. Currently only `dispatch.create` and `dispatch.update` are supported.
name
string
Name of the event
id
string
Unique identifier for this webhook
url
string
URL that this webhook will send requests to
OpenADR
Introduction
“OpenADR was created to automate and simplify DR and DER for the power industry” (OpenADR alliance)
OpenADR is an industry standard that stands for Automated Demand Response. It provides a standardized two-way communication protocol between entities that seek to act on demand - utilities, aggregators - and electric devices or more generally decentralized energy resources (DERs).
The protocol hinges on two types of entities that interact with one another: a server or command module, called Virtual Top Node (VTN), and clients that represent one or multiple DERs, called Virtual End Nodes (VENs).
From the specification:
Virtual Top Node (VTN): An entity that is responsible for communicating grid conditions (e.g. prices, reliability events, etc.) to other entities (i.e. VEN’s) that control demand side resources. [...]
Virtual End Node (VEN): The VEN has operational control of a set of resources [...] and is able to control the electrical energy demand of these in response to an understood set of smart grid messages (i.e. DR signals). The VEN may be either a producer or consumer of energy. The VEN is able to communicate (2-way) with a VTN receiving and transmitting smart grid messages that relay grid situations, conditions, or events. [...]
This documentation only provides Voltus-specific information and must be complemented by the OpenADR specification which can be downloaded on the OpenADR website.
Voltus Implementation
<?xml version="1.0" encoding="utf-8"?>
<oadr:oadrDistributeEvent
xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110"
xmlns:emix="http://docs.oasis-open.org/ns/emix/2011/06"
xmlns:oadr="http://openadr.org/oadr-2.0a/2012/07"
xmlns:pyld="http://docs.oasis-open.org/ns/energyinterop/201110/payloads"
xmlns:strm="urn:ietf:params:xml:ns:icalendar-2.0:stream"
xmlns:xcal="urn:ietf:params:xml:ns:icalendar-2.0">
<ei:eiResponse>
<ei:responseCode>200</ei:responseCode>
<ei:responseDescription>OK</ei:responseDescription>
<pyld:requestID>2019-06-20T18:59:56-0500--ALC_VEN</pyld:requestID>
</ei:eiResponse>
<pyld:requestID>5b445014-6163-46fe-b005-1b386f1f760d</pyld:requestID>
<ei:vtnID>voltus_partner_vtn</ei:vtnID>
<oadr:oadrEvent>
<ei:eiEvent>
<ei:eventDescriptor>
<ei:eventID>377</ei:eventID>
<ei:modificationNumber>0</ei:modificationNumber>
<ei:priority>0</ei:priority>
<ei:eiMarketContext>
<emix:marketContext>*</emix:marketContext>
</ei:eiMarketContext>
<ei:createdDateTime>2019-06-20T18:04:11Z</ei:createdDateTime>
<ei:eventStatus>completed</ei:eventStatus>
<ei:testEvent>false</ei:testEvent>
<ei:vtnComment>Voltus OpenADR 2.0a</ei:vtnComment>
</ei:eventDescriptor>
<ei:eiActivePeriod>
<xcal:properties>
<xcal:dtstart>
<xcal:date-time>2019-06-20T19:00:00Z</xcal:date-time>
</xcal:dtstart>
<xcal:duration>
<xcal:duration>PT4H0M0S</xcal:duration>
</xcal:duration>
<xcal:tolerance>
<xcal:tolerate>
<xcal:startafter>PT0H0M0S</xcal:startafter>
</xcal:tolerate>
</xcal:tolerance>
<ei:x-eiNotification>
<xcal:duration>PT0H0M0S</xcal:duration>
</ei:x-eiNotification>
<ei:x-eiRampUp>
<xcal:duration>PT0H0M0S</xcal:duration>
</ei:x-eiRampUp>
<ei:x-eiRecovery>
<xcal:duration>PT0H0M0S</xcal:duration>
</ei:x-eiRecovery>
</xcal:properties>
<xcal:components
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
</ei:eiActivePeriod>
<ei:eiEventSignals>
<ei:eiEventSignal>
<strm:intervals>
<ei:interval>
<xcal:duration>
<xcal:duration>PT4H0M0S</xcal:duration>
</xcal:duration>
<xcal:uid>
<xcal:text>0</xcal:text>
</xcal:uid>
<ei:signalPayload>
<ei:payloadFloat>
<ei:value>1.0</ei:value>
</ei:payloadFloat>
</ei:signalPayload>
</ei:interval>
</strm:intervals>
<ei:signalName>simple</ei:signalName>
<ei:signalType>level</ei:signalType>
<ei:signalID>SIGO</ei:signalID>
<ei:currentValue>
<ei:payloadFloat>
<ei:value>0.0</ei:value>
</ei:payloadFloat>
</ei:currentValue>
</ei:eiEventSignal>
</ei:eiEventSignals>
<ei:eiTarget>
<ei:resourceID>resource_1</ei:resourceID>
<ei:resourceID>resource_2</ei:resourceID>
<ei:resourceID>resource_3</ei:resourceID>
</ei:eiTarget>
</ei:eiEvent>
<oadr:oadrResponseRequired>always</oadr:oadrResponseRequired>
</oadr:oadrEvent>
</oadr:oadrDistributeEvent>
Type and Scope
There are several variations of OpenADR. Voltus implements an OpenADR 2.0a VTN.
Voltus only supports DR dispatch through OpenADR (EiEvent endpoint). Voltus integrates with energy markets to forward timely DR events to partners using OpenADR.
Our implementation being OpenADR 2.0a at this time, we only use “Simple” signals.
Payload Configuration
This section describes how Voltus uses some of the main payload fields. Some level of customization is possible depending on partner needs.
- Signal level
- Definition: we use this parameter to differentiate between programs with short events -often associated with more aggressive curtailment strategies, and long events. (As events can be "open-ended", event duration is not an option to differentiate)
- Values:
- 0.0 (no event)
- 1.0 (short)
- 2.0 (long)
marketContext
- Definition: We highly recommend using a single marketContext value for all dispatches (e.g., “*”). This approach avoids undefined behavior in some VENs if a resource is dispatched in multiple events in different programs or markets by explicitly relying on the ei:priority field. An advantage of this approach is that there is no work needed to modify the integration to support new programs.
Priority
- Definition: specifies the priority of events within a market context. By default, events are prioritized in the order they are created. This value is guaranteed to be unique within a marketContext.
eiTarget
- Definition: defines target resources that are to be dispatched for a particular event. By default, this field will contain an array of resourceID objects.
Security
- SSL Host and Peer verification are enabled on both production and testing VTNs.
- Voltus will sign your certificate: customer to send a Certificate Signing Request (CSR)
- As an alternative, Voltus can also provide a signed certificate of its own.