Skip to main content

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.

Best suited for simple energy plans

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.

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);
Example response
{
"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.

Tip

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

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");
Example response
{
"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.

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);
Example response
{
"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.

Usage profiles are binding once created

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).

User authorization required

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.

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);
Example response (202 Accepted)
{
"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 vs live

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.

Tip

We recommend using the usage_profile.completed and usage_profile.failed webhooks instead of polling. Learn more about webhooks

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);
Example response
{
"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 processed
  • completed: The usage profile is ready and will be applied to plan comparisons
  • failed: Processing failed. Check the error_message field 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.

Example simple compare response with usage profile
{
"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"
}
}

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.