curl --request POST \
--url https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve \
--header 'Authorization: Bearer <token>'import requests
url = "https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"id": "enr_7c2f1a9e4b0d8f3a6e2c5d10",
"object": "vpp_enrollment",
"live_mode": true,
"device_id": "dev_63a6087272941ef077a8fd3e",
"program_id": "vpr_5c8d1f2a7b3e4d6c9a0f1b2c",
"oem_id": "oem_6a3a8dada72cd0a249ff9528",
"enrollment_state": "ACTIVE",
"enrollment_step": "ACTIVE",
"suspension_reasons": [],
"terms": {
"rewards": [
{
"basis": "FIXED",
"rate": 15,
"period": "QUARTER"
},
{
"basis": "DELIVERED_KWH",
"rate": 0.35
}
],
"opt_out_policy": "ALLOWED",
"notice_days": 30
},
"time_enrolled": "2026-01-24T14:15:22Z",
"time_unenrolled": null,
"time_created": "2026-01-20T09:30:00Z",
"time_updated": "2026-08-12T09:30:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}Approve an enrollment request
Approves what the enrollment is waiting on from you. At step AWAITING_APPROVAL it approves the request to join, and the enrollment goes ACTIVE. At step UNENROLLING it approves the request to leave, and the enrollment goes ENDED. Any other step returns 422 enrollment_not_awaiting_approval. Fires vpp_enrollment.update.
You cannot refuse a request to leave: one left unanswered ends on its own once the notice_days of the enrollment’s terms have passed.
curl --request POST \
--url https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve \
--header 'Authorization: Bearer <token>'import requests
url = "https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://fleet-api.flatpeak.com/vpp-enrollments/{enrollment_id}/approve")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"id": "enr_7c2f1a9e4b0d8f3a6e2c5d10",
"object": "vpp_enrollment",
"live_mode": true,
"device_id": "dev_63a6087272941ef077a8fd3e",
"program_id": "vpr_5c8d1f2a7b3e4d6c9a0f1b2c",
"oem_id": "oem_6a3a8dada72cd0a249ff9528",
"enrollment_state": "ACTIVE",
"enrollment_step": "ACTIVE",
"suspension_reasons": [],
"terms": {
"rewards": [
{
"basis": "FIXED",
"rate": 15,
"period": "QUARTER"
},
{
"basis": "DELIVERED_KWH",
"rate": 0.35
}
],
"opt_out_policy": "ALLOWED",
"notice_days": 30
},
"time_enrolled": "2026-01-24T14:15:22Z",
"time_unenrolled": null,
"time_created": "2026-01-20T09:30:00Z",
"time_updated": "2026-08-12T09:30:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}{
"object": "error",
"type": "invalid_request",
"code": "access_denied",
"message": "The requested resource could not be found.",
"context": "api.price_get",
"live_mode": true,
"time_created": "2026-01-01T00:00:00Z"
}Authorizations
Authenticate with the bearer_token obtained from the authentication endpoint.
Response
Enrollment response.
A device's membership of a VPP program. When the program has enrollment.approval_required, requests wait for your decision at AWAITING_APPROVAL. Changes fire vpp_enrollment.update.
The enrollment lifecycle state. enrollment_step gives the detail.
| Value | Meaning |
|---|---|
PENDING | Onboarding: consent, a connected tariff, telemetry, the dispatch test and approval |
ACTIVE | Enrolled and dispatchable |
SUSPENDED | Not dispatchable until every reason in suspension_reasons clears |
LEAVING | A request to leave is waiting for your approval. The device serves only the commitments it already has |
ENDED | Unenrolled. Terminal |
REJECTED | Ineligible, turned down by you, or onboarding failed. Terminal |
PENDING, ACTIVE, SUSPENDED, LEAVING, ENDED, REJECTED "ACTIVE"
The step within enrollment_state.
| Step | State | Meaning |
|---|---|---|
REQUESTED | PENDING | The customer asked to join |
AWAITING_CONSENT | PENDING | Waiting for the consent scopes the program needs |
AWAITING_TARIFF | PENDING | Waiting for a connected tariff at the device's location |
AWAITING_TELEMETRY | PENDING | Waiting for the feeds the program requires |
TESTING_DISPATCH | PENDING | Running the program's dispatch test |
AWAITING_APPROVAL | PENDING | Waiting for your approval |
ACTIVE | ACTIVE | Enrolled |
SUSPENDED | SUSPENDED | Suspended |
UNENROLLING | LEAVING | The customer or the OEM asked to leave. The request waits for your approval |
UNENROLLED | ENDED | Unenrolled |
INELIGIBLE | REJECTED | The device does not qualify, or you unenrolled it while it was PENDING |
FAILED | REJECTED | Onboarding could not complete, for example a failed dispatch test |
REQUESTED, AWAITING_CONSENT, AWAITING_TARIFF, AWAITING_TELEMETRY, TESTING_DISPATCH, AWAITING_APPROVAL, ACTIVE, SUSPENDED, UNENROLLING, UNENROLLED, INELIGIBLE, FAILED "ACTIVE"
Why the enrollment is suspended: an empty list unless enrollment_state is SUSPENDED.
| Value | Meaning |
|---|---|
DISPATCH_UNRESPONSIVE | The device is not acting on instructions |
CONSENT_REVOKED | The customer withdrew a consent scope the program requires |
TELEMETRY_STALLED | A required feed is present but silent beyond its cadence |
USER_PAUSED | Paused by the customer |
DISPATCH_UNRESPONSIVE, CONSENT_REVOKED, TELEMETRY_STALLED, USER_PAUSED Was this page helpful?

