Plan comparison
Intro
Comparison invoices and usage profiles let you show customers how your plans compare to their current electricity provider, personalized with their actual usage data. A comparison invoice captures what a customer currently pays, and a usage profile pulls their historical interval usage to produce more accurate rate comparisons.
Comparison invoice endpoints use AccountToken authentication, while usage profile endpoints use AppToken authentication. Refer to the authentication docs for more details on each.
Comparison invoices currently work best for customers on simple energy plans. Results are less accurate for customers with solar panels, battery storage, or those in the process of installing solar, since export credits and net metering charges are not yet fully accounted for in the comparison. We plan to enhance this in the future.
API
1. Upload a comparison invoice
The customer uploads a PDF of their current electricity bill. Use POST /v1/account/comparison-invoice to upload the invoice. The invoice will be parsed asynchronously in the background.
- Node.js
- Python
- cURL
const formData = new FormData();
formData.append("file", fs.createReadStream("invoice.pdf"));
const response = await fetch(
"https://api.light.dev/v1/account/comparison-invoice",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LIGHT_ACCOUNT_TOKEN}`,
},
body: formData,
},
);
const invoice = await response.json();
console.log("Invoice UUID:", invoice.uuid);
import requests
import os
with open("invoice.pdf", "rb") as f:
response = requests.post(
"https://api.light.dev/v1/account/comparison-invoice",
headers={
"Authorization": f'Bearer {os.getenv("LIGHT_ACCOUNT_TOKEN")}',
},
files={"file": ("invoice.pdf", f, "application/pdf")},
)
invoice = response.json()
print(f"Invoice UUID: {invoice['uuid']}")
$ curl -X POST "https://api.light.dev/v1/account/comparison-invoice" \
-H "Authorization: Bearer $LIGHT_ACCOUNT_TOKEN" \
-F "file=@invoice.pdf"
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"status": "pending",
"utility_account_number": null,
"processed_at": null,
"processing_error": null
}
2. Wait for invoice processing
After upload, the invoice is processed asynchronously. You can poll the status with GET /v1/account/comparison-invoice or listen for the comparison_invoice.processed webhook.
We recommend using the comparison_invoice.processed webhook instead of polling. Your system will be notified as soon as the invoice is ready. Learn more about webhooks
- Node.js
- Python
- cURL
const response = await fetch(
"https://api.light.dev/v1/account/comparison-invoice",
{
headers: {
Authorization: `Bearer ${process.env.LIGHT_ACCOUNT_TOKEN}`,
},
},
);
const invoice = await response.json();
console.log("Status:", invoice.processed_at ? "processed" : "pending");
response = requests.get(
"https://api.light.dev/v1/account/comparison-invoice",
headers={
"Authorization": f'Bearer {os.getenv("LIGHT_ACCOUNT_TOKEN")}',
},
)
invoice = response.json()
status = "processed" if invoice["processed_at"] else "pending"
print(f"Status: {status}")
$ curl "https://api.light.dev/v1/account/comparison-invoice" \
-H "Authorization: Bearer $LIGHT_ACCOUNT_TOKEN"
{
"uuid": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"status": "processed",
"utility_account_number": "10443720000000000",
"processed_at": "2025-06-15T14:32:00Z",
"processing_error": null
}
If the processing_error field is set, the invoice could not be parsed. Check the error message for details and consider re-uploading a clearer copy of the invoice.
3. Run a simple comparison
Once the invoice is processed, compare the customer's current bill against one of your plan groups using GET /v1/account/comparison-invoice/simple-compare.
At this stage, the comparison uses the usage data extracted from the invoice itself. This provides a baseline comparison showing what the customer would have paid on your plan.
- Node.js
- Python
- cURL
const params = new URLSearchParams({
plan_group: "PLAN_GROUP_UUID",
uuid: invoice.uuid,
});
const response = await fetch(
`https://api.light.dev/v1/account/comparison-invoice/simple-compare?${params}`,
{
headers: {
Authorization: `Bearer ${process.env.LIGHT_ACCOUNT_TOKEN}`,
},
},
);
const comparison = await response.json();
console.log("Invoice total:", comparison.parsed_invoice_data.total_dollars);
console.log("Plan total:", comparison.plan_comparison.total_dollars);
response = requests.get(
"https://api.light.dev/v1/account/comparison-invoice/simple-compare",
headers={
"Authorization": f'Bearer {os.getenv("LIGHT_ACCOUNT_TOKEN")}',
},
params={
"plan_group": "PLAN_GROUP_UUID",
"uuid": invoice["uuid"],
},
)
comparison = response.json()
print(f"Invoice total: {comparison['parsed_invoice_data']['total_dollars']}")
print(f"Plan total: {comparison['plan_comparison']['total_dollars']}")
$ curl "https://api.light.dev/v1/account/comparison-invoice/simple-compare?plan_group=PLAN_GROUP_UUID&uuid=INVOICE_UUID" \
-H "Authorization: Bearer $LIGHT_ACCOUNT_TOKEN"
{
"processing_error": null,
"parsed_invoice_data": {
"contract_end_date": "2024-01-01",
"invoice_date": "2023-06-15",
"provider_display_name": "TXU Energy",
"total_dollars": "187.43",
"total_kwh": "1523.0"
},
"plan_comparison": {
"group_uuid": "4e7acc79-58af-4be1-881f-c9ccbbe02a80",
"name": "Clean Energy",
"total_dollars": "162.18",
"usage_profile_uuid": null
}
}
See the API reference for the complete response format.
Enhancing comparisons with usage profiles
Without a usage profile, the rates offered to a customer are based on an average of all customers on your plan. This means some customers pay more than their actual cost to serve, and others pay less. Usage profiles let you offer personalized rates based on a customer's real energy consumption patterns. Customers who are lower cost to serve (for example, those with efficient homes or favorable usage patterns) will receive lower rates, while higher cost customers will receive rates that reflect their actual usage.
This creates more accurate and fair pricing, and lets you reward customers for better energy habits. If the customer authorizes access to their utility meter data, you can fetch their actual 15-minute interval usage history to build a usage profile.
Once a usage profile has been successfully fetched for a customer, all subsequent rates for that utility account number will use the personalized rates. You cannot fall back to average rates for that customer. This prevents selectively offering personalized rates only when they are cheaper, which would skew the average cost over time.
Fetch a usage profile
Use POST /v1/app/usage-profiles/fetch to trigger a data fetch from Smart Meter Texas (SMT), building a usage profile spanning the customer's last 12+ months of electricity consumption. This endpoint uses AppToken authentication (not AccountToken).
You must obtain explicit authorization from the customer before fetching their usage data. In Texas, this authorizes access to their Smart Meter Texas data. Set user_authorizes_usage_fetch to true only after the customer has consented.
- Node.js
- Python
- cURL
const response = await fetch(
"https://api.light.dev/v1/app/usage-profiles/fetch",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LIGHT_APP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
comparison_invoice_uuid: invoice.uuid,
user_authorizes_usage_fetch: true,
}),
},
);
const usageProfile = await response.json();
console.log("Usage profile UUID:", usageProfile.uuid);
console.log("Status:", usageProfile.status);
response = requests.post(
"https://api.light.dev/v1/app/usage-profiles/fetch",
headers={
"Authorization": f'Bearer {os.getenv("LIGHT_APP_TOKEN")}',
"Content-Type": "application/json",
},
json={
"comparison_invoice_uuid": invoice["uuid"],
"user_authorizes_usage_fetch": True,
},
)
usage_profile = response.json()
print(f"Usage profile UUID: {usage_profile['uuid']}")
print(f"Status: {usage_profile['status']}")
$ curl -X POST "https://api.light.dev/v1/app/usage-profiles/fetch" \
-H "Authorization: Bearer $LIGHT_APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"comparison_invoice_uuid": "INVOICE_UUID",
"user_authorizes_usage_fetch": true
}'
{
"uuid": "b2c3d4e5-6789-01ab-cdef-234567890abc",
"status": "pending",
"utility_account_number": "10443720000000000",
"error_message": null,
"created_at": "2025-06-15T14:35:00Z",
"start_date": null,
"end_date": null
}
Sandbox apps use synthetic usage data instead of real SMT data. This lets you test the full flow without requiring a real utility account.
Check usage profile status
The usage profile is processed asynchronously. Poll the status with GET /v1/app/usage-profiles/{usage_profile_uuid} or use webhooks to be notified when processing completes.
We recommend using the usage_profile.completed and usage_profile.failed webhooks instead of polling. Learn more about webhooks
- Node.js
- Python
- cURL
const response = await fetch(
`https://api.light.dev/v1/app/usage-profiles/${usageProfile.uuid}`,
{
headers: {
Authorization: `Bearer ${process.env.LIGHT_APP_TOKEN}`,
},
},
);
const profile = await response.json();
console.log("Status:", profile.status);
response = requests.get(
f"https://api.light.dev/v1/app/usage-profiles/{usage_profile['uuid']}",
headers={
"Authorization": f'Bearer {os.getenv("LIGHT_APP_TOKEN")}',
},
)
profile = response.json()
print(f"Status: {profile['status']}")
$ curl "https://api.light.dev/v1/app/usage-profiles/USAGE_PROFILE_UUID" \
-H "Authorization: Bearer $LIGHT_APP_TOKEN"
{
"uuid": "b2c3d4e5-6789-01ab-cdef-234567890abc",
"status": "completed",
"utility_account_number": "10443720000000000",
"error_message": null,
"created_at": "2025-06-15T14:35:00Z",
"start_date": "2024-06-01",
"end_date": "2025-06-14"
}
The status field will be one of:
pending: The usage profile is still being processedcompleted: The usage profile is ready and will be applied to plan comparisonsfailed: Processing failed. Check theerror_messagefield for details.
How usage profiles affect comparisons and rates
You don't need to re-run anything after a usage profile completes. Once a usage profile is available, it is automatically applied going forward. Any subsequent calls to the simple compare endpoint or the enrollment plans endpoint for this utility account number will use the customer's actual interval usage data to calculate customized energy rates.
You can tell that a usage profile is being used by checking the usage_profile_uuid field in the response. When it is set, the rates reflect the customer's real usage pattern rather than generalized assumptions.
{
"processing_error": null,
"parsed_invoice_data": {
"contract_end_date": "2024-01-01",
"invoice_date": "2023-06-15",
"provider_display_name": "TXU Energy",
"total_dollars": "187.43",
"total_kwh": "1523.0"
},
"plan_comparison": {
"group_uuid": "4e7acc79-58af-4be1-881f-c9ccbbe02a80",
"name": "Clean Energy",
"total_dollars": "158.92",
"usage_profile_uuid": "b2c3d4e5-6789-01ab-cdef-234567890abc"
}
}
Related webhooks
Webhooks allow your application to receive real-time notifications about events that occur within the Light platform. This enables you to build responsive and up-to-date integrations without the need for constant polling. Learn more
comparison_invoice.processed
Triggered when a comparison invoice is done being processed (either successfully or unsuccessfully). Check the invoice status to determine if processing was successful.
usage_profile.completed
Triggered when a usage profile has been successfully processed and is ready to use for plan comparisons.
usage_profile.failed
Triggered when a usage profile fails to process. The webhook payload includes an error_message field with details about the failure.