GraphQL · U.S. retail electricity

Plan Data API

A single GraphQL API for U.S. retail electricity across the deregulated markets PowerHQ supports — residential & business plans, component-level pricing, disclosure documents (EFL, TOS, YRAC, and more), utility (TDU) lookup, and enrollment hand-off.

One endpoint, ask for what you need
GraphQL exposes every query at a single URL. Send a query naming exactly the fields you want; the server returns just those, as JSON. The reference below is generated from the live schema.
Coverage
The API spans the deregulated U.S. electricity markets PowerHQ supports — pass any supported ZIP or state code. The examples on this page use Texas ZIPs (e.g. 75231, 77002), but the same queries work for other supported markets (e.g. PA 19103, IL 60602, NJ 07030). Plan availability varies by market.

API Access

You need an API key. Contact your PowerHQ representative to get one. IP allow-listing is no longer required — the key alone authenticates the request, so you can call the API from anywhere.

EnvironmentEndpoint
Productionhttps://eapi.prod.powerhq.co/graphql
Certification (staging)https://eapi.cert.powerhq.co/graphql

Pass your key in the x-api-key header. Requests without a valid key receive 403 Forbidden.

Endpoint note
The API is served from powerhq.co. If you previously integrated against eapi.energybot.com, that endpoint keeps working during the transition — new integrations should use the powerhq.co endpoints. Enrollment URLs returned by the API resolve to powerhq.co.

Making a request

Any GraphQL client works. A minimal example with curl:

$ curl -s -X POST https://eapi.prod.powerhq.co/graphql \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "{ utilities(zipCode: \"75231\") { id name } }"}'
{ "data": { "utilities": [ { "id": "ONCOR", "name": "Oncor" } ] } }

Queries

residentialPlans

Returns residential plans for a location. Provide zipCode or utilityId — if both are given, utilityId wins; if neither resolves, an empty list is returned (not an error).

residentialPlans(
  zipCode: String        # e.g. "75231"
  utilityId: ID          # e.g. "ONCOR" — takes precedence
  monthlyUsage: Long     # kWh/mo the price is calculated at (default 1000)
  priceType: String      # Default "SEASONALIZED": spreads monthlyUsage across a real
                          #   12-month shape (higher summer, lower spring/autumn) and bills
                          #   each month at that month's usage, so a bill credit only counts
                          #   in the months the customer would actually qualify for it.
                          # "FLAT": same usage every month, so credits always apply.
                          # Affects price, allInRateUsdPerKwh and avgMonthlyBillUsd.
): [ResidentialPlan!]!
Example query
{
  residentialPlans(zipCode: "77002", monthlyUsage: 1000) {
    title
    price
    term
    rateType
    supplier { name }
    rates { usageKwh advertisedPriceUsdPerKwh allInRateUsdPerKwh avgMonthlyBillUsd }
    feeBreakdown { feeType amountUsd applicability threshold }
    supplierScores { powerHqRating plansAndRates customerService renewablePlans pucRating }
    headlessEnrollmentUrl
  }
}
Show response
{
  "data": {
    "residentialPlans": [
      {
        "title": "12 Month Usage Bill Credit",
        "price": 0.134,
        "term": 12,
        "rateType": "FIXED",
        "supplier": { "name": "Constellation NewEnergy, Inc." },
        "rates": [
          { "usageKwh": 500,  "advertisedPriceUsdPerKwh": 0.151, "allInRateUsdPerKwh": 0.15066, "avgMonthlyBillUsd": 75.33 },
          { "usageKwh": 1000, "advertisedPriceUsdPerKwh": 0.111, "allInRateUsdPerKwh": 0.13415, "avgMonthlyBillUsd": 134.15 },
          { "usageKwh": 2000, "advertisedPriceUsdPerKwh": 0.119, "allInRateUsdPerKwh": 0.12342, "avgMonthlyBillUsd": 246.83 }
        ],
        "feeBreakdown": [
          { "feeType": "UTILITY_PASS_THRU", "amountUsd": 4.9,   "applicability": "MONTHLY", "threshold": null },
          { "feeType": "UTILITY_PASS_THRU", "amountUsd": 0.051, "applicability": "PER_KWH", "threshold": null },
          { "feeType": "BILL_CREDIT",       "amountUsd": 35.0,  "applicability": "CREDIT_MONTHLY_ABOVE_USAGE", "threshold": 1000.0 },
          { "feeType": "BILL_CREDIT",       "amountUsd": 15.0,  "applicability": "CREDIT_MONTHLY_ABOVE_USAGE", "threshold": 2000.0 }
        ],
        "supplierScores": { "powerHqRating": 4.7, "plansAndRates": 4.0, "customerService": 5.0, "renewablePlans": 2.0, "pucRating": 5.0 },
        "headlessEnrollmentUrl": "https://www.cert.powerhq.co/partner-app.html?utm_source=YOUR_PARTNER_CODE&utm_medium=referral&utm_campaign=partners#/redirect?ftype=RESIDENTIAL_PARTNER&plan_id=7a8c1d9f-f83a-4322-850f-c6939ccf4fb3&zip_code=77002&ref=YOUR_PARTNER_CODE"
      }
    ]
  }
}

businessPlans

Commercial plans for a zip code. zipCode required; annualUsageInkWh selects the pricing assumption. For new service set isMoveIn: true (default false = switch). A startDate earlier than the resolved minimum returns a validation error.

businessPlans(zipCode: String!, annualUsageInkWh: Float, isMoveIn: Boolean, startDate: Date): [BusinessPlan!]
Example query
{
  businessPlans(zipCode: "75070", annualUsageInkWh: 23000, isMoveIn: true) {
    id
    term
    price
    monthlyFee
    supplier { name }
    enrollmentUrl
    agreementUrl
  }
}
Show response
{
  "data": {
    "businessPlans": [
      {
        "id": "58b2e47e-91d7-4b72-9b5d-9bc9136613d8",
        "term": 3,
        "price": 0.0705,
        "monthlyFee": 4.95,
        "supplier": { "name": "NRG Energy, Inc." },
        "enrollmentUrl": "https://www.cert.powerhq.co/app.html?utm_source=YOUR_PARTNER_CODE&utm_medium=referral&utm_campaign=partners#/redirect?ftype=BUSINESS_PARTNER&plan_id=58b2e47e-91d7-4b72-9b5d-9bc9136613d8&zip_code=75070&start_date=2026-08-06&annual_usage=23000.0&is_move_in=true&ref=YOUR_PARTNER_CODE",
        "agreementUrl": "https://www.cert.powerhq.co/api/document/sample/template/v3/AgreementPreview.pdf?key=TX/NRG/ONCOR&type=supplier_contract"
      }
    ]
  }
}

residentialEnrollmentUrl / businessEnrollmentUrl Optional

You do not need these queries to enroll a customer. Every plan returned by residentialPlans and businessPlans already carries a working enrollmentUrl and headlessEnrollmentUrl. Use those and you are done.

Reach for these two queries only when you want to add shopper detail to the link after the plan search — a name, email, phone, service location or start date you collected once the shopper had already picked a plan. They return the same two links, with that context attached so the customer does not re-enter it.

residentialEnrollmentUrl(
  planId: String!
  zipCode: String!
  startDate: Date
  utilityAccountNumber: String
  customer: EnrollmentCustomerInput
  address: EnrollmentAddressInput
): EnrollmentUrl!

businessEnrollmentUrl(
  planId: String!
  zipCode: String!
  isMoveIn: Boolean!
  annualUsageInkWh: Float
  startDate: Date
  utilityAccountNumber: String
  customer: EnrollmentCustomerInput
  address: EnrollmentAddressInput
  businessName: String
  position: String       # the shopper's role at the business
): EnrollmentUrl!
Identifying the service location
Pass either utilityAccountNumber or address. Only one is used for the lookup, and utilityAccountNumber takes precedence if you supply both. When using address, provide street, city and state; if the address does not resolve you get a link back with no service location and no error, so check that the returned URL contains prospect_id. Use utilityAccounts to look up an account number from an address first.
Apartments and units
Write the unit as a bare number on the end of street ("1441 East St 315") and never send street2. #, unit, APT and Ste prefixes do not match. Even then, a multi-unit building often will not resolve from the address alone — for apartments and units, look the account up with utilityAccounts and pass utilityAccountNumber instead. That resolves reliably where the address does not.
Example query — residential, by utility account number
{
  residentialEnrollmentUrl(
    planId: "51ef348d-280a-451a-9d04-1d9593dccc8d"
    zipCode: "75201"
    startDate: "2026-09-01"
    utilityAccountNumber: "10443720009406225"
    customer: { firstName: "Jane", lastName: "Doe", email: "jane@example.com", phone: "2145550100" }
  ) {
    enrollmentUrl
    headlessEnrollmentUrl
  }
}
Show response
{
  "data": {
    "residentialEnrollmentUrl": {
      "enrollmentUrl": "https://www.cert.powerhq.co/app.html?utm_source=YOUR_PARTNER_CODE&utm_medium=referral&utm_campaign=partners#/redirect?ftype=RESIDENTIAL_PARTNER&plan_id=51ef348d-280a-451a-9d04-1d9593dccc8d&zip_code=75201&prospect_id=v2-A373483C2E2B04230479696DD8EB7E11&state=TX&start_date=2026-09-01&ref=YOUR_PARTNER_CODE&fname=Jane&lname=Doe&email=jane%40example.com&phone=2145550100",
      "headlessEnrollmentUrl": "https://www.cert.powerhq.co/partner-app.html?utm_source=YOUR_PARTNER_CODE&utm_medium=referral&utm_campaign=partners#/redirect?ftype=RESIDENTIAL_PARTNER&plan_id=51ef348d-280a-451a-9d04-1d9593dccc8d&zip_code=75201&prospect_id=v2-A373483C2E2B04230479696DD8EB7E11&state=TX&start_date=2026-09-01&ref=YOUR_PARTNER_CODE&fname=Jane&lname=Doe&email=jane%40example.com&phone=2145550100"
    }
  }
}
Example query — business, by address
{
  businessEnrollmentUrl(
    planId: "860b4865-1025-46c1-95c7-d783befcef1c"
    zipCode: "75220"
    isMoveIn: false
    annualUsageInkWh: 63965
    startDate: "2026-09-01"
    businessName: "Acme Roofing"
    position: "Owner"
    customer: { firstName: "Jane", lastName: "Doe", email: "jane@acmeroofing.com", phone: "2145550100" }
    address: { street: "10590 King William Dr", city: "Dallas", state: "TX" }
  ) {
    enrollmentUrl
    headlessEnrollmentUrl
  }
}
Show response
{
  "data": {
    "businessEnrollmentUrl": {
      "enrollmentUrl": "https://www.cert.powerhq.co/app.html?utm_source=YOUR_PARTNER_CODE&utm_medium=referral&utm_campaign=partners#/redirect?ftype=BUSINESS_PARTNER&plan_id=860b4865-1025-46c1-95c7-d783befcef1c&zip_code=75220&prospect_id=v2-4F17BC6F325BBB9F921B183CCCAE2DF8&state=TX&start_date=2026-09-01&ref=YOUR_PARTNER_CODE&fname=Jane&lname=Doe&email=jane%40acmeroofing.com&phone=2145550100&annual_usage=63965.0&is_move_in=false&bname=Acme+Roofing&position=Owner",
      "headlessEnrollmentUrl": "https://www.cert.powerhq.co/partner-app.html?utm_source=YOUR_PARTNER_CODE&utm_medium=referral&utm_campaign=partners#/redirect?ftype=BUSINESS_PARTNER&plan_id=860b4865-1025-46c1-95c7-d783befcef1c&zip_code=75220&prospect_id=v2-4F17BC6F325BBB9F921B183CCCAE2DF8&state=TX&start_date=2026-09-01&ref=YOUR_PARTNER_CODE&fname=Jane&lname=Doe&email=jane%40acmeroofing.com&phone=2145550100&annual_usage=63965.0&is_move_in=false&bname=Acme+Roofing&position=Owner"
    }
  }
}
Not sure whether you need this?
If you are happy sending the customer to a plan's existing enrollmentUrl, you do not need these queries at all. They exist purely to save the shopper from retyping details you already have.

What carries into the link. Residential: customer name, email, phone, service location and start date. Business: all of those plus annual_usage, is_move_in, bname and position. Residential links do not carry usage or move-in vs. switch.

utilities

The utility/TDU(s) serving a zip code — optionally with their residential plans.

utilities(zipCode: String!): [Utility!]
Example query
{
  utilities(zipCode: "75231") {
    id
    name
  }
}
Show response
{
  "data": {
    "utilities": [
      { "id": "ONCOR", "name": "Oncor" }
    ]
  }
}

nextStartDate

Earliest available service start date for a zip code.

nextStartDate(zipCode: String!, isMoveIn: Boolean): PlanNextStartDate!
Example query
{
  nextStartDate(zipCode: "75070", isMoveIn: false) {
    nextStartDate
  }
}
Show response
{
  "data": {
    "nextStartDate": { "nextStartDate": "2026-08-04" }
  }
}

utilityAccounts

Search utility service accounts by account number and/or address. Either street or accountNumber is required.

utilityAccounts(street: String, street2: String, city: String, state: String,
  zipCode: String, accountNumber: String, isBusiness: Boolean): [UtilityAccount!]
Writing the service address
Put the apartment or unit number on the end of street as a bare number, with nothing in front of it. "1441 East St 315" matches. "#315", "unit 315", "Unit 315", "APT 315" and "Ste 315" all return nothing. Do not send street2 at all — any value, including an empty string, causes the lookup to miss. Omit the unit entirely and you get every account in the building.
Example query
{
  utilityAccounts(street: "947 STREETLIGHT STLG 250HPS", city: "BROWNSVILLE", state: "TX", zipCode: "78521") {
    id
    accountNumber
    utilityCode
    isBusiness
    accountStatus
    displayAddress
    serviceAddress { street street2 city state zipCode }
  }
}
Show response
{
  "data": {
    "utilityAccounts": [
      {
        "id": "v2-CB1C1D302FC4B19F58D4F89F775AAFD6",
        "accountNumber": "10032789448946468",
        "utilityCode": "AEPCC",
        "isBusiness": true,
        "accountStatus": "ACTIVE",
        "displayAddress": "947 STREETLIGHT STLG 250HPS,  CAMERON COUNTY ST LIGHTS, BROWNSVILLE, TX",
        "serviceAddress": { "street": "947 STREETLIGHT STLG 250HPS", "street2": " CAMERON COUNTY ST LIGHTS", "city": "BROWNSVILLE", "state": "TX", "zipCode": "78521" }
      }
    ]
  }
}

energyInfoByState

Average rate and generation mix for a state. isBusiness: true = business, false = residential.

energyInfoByState(stateCode: String!, isBusiness: Boolean!): EnergyInfo
Example query
{
  energyInfoByState(stateCode: "TX", isBusiness: false) {
    averagePrice
    stateName
    electricityGenerationPercentage { renewableGeneration nonRenewableGeneration }
  }
}
Show response
{
  "data": {
    "energyInfoByState": {
      "averagePrice": 16.44,
      "stateName": "Texas",
      "electricityGenerationPercentage": { "renewableGeneration": 34, "nonRenewableGeneration": 66 }
    }
  }
}

Reading pricing: advertised vs. all-in

Each plan's rates[] returns, at 500 / 1,000 / 2,000 kWh, an advertised rate and an all-in rate (both per kWh), plus the estimated monthly bill. What those rates include depends on the market.

Texas

  • Both rates include TDU delivery charges.
  • advertisedPriceUsdPerKwh — effective rate at a static, standard usage (a flat 500 / 1,000 / 2,000 kWh every month).
  • allInRateUsdPerKwh — the actual bill on the customer's usage, seasonalized by default.

All other states (PA, OH, IL, NJ, …)

  • Neither rate includes TDU delivery — the utility bills delivery separately.
  • advertisedPriceUsdPerKwh — the variable supply rate per kWh only (excludes any fixed monthly fee).
  • allInRateUsdPerKwh — the supply rate plus the fixed monthly fee (amortized per kWh); still excludes TDU.
Outside Texas, all-in ≠ the total bill
Because non-TX rates exclude TDU delivery (billed separately by the utility), a customer's total cost is the all-in supply rate plus the utility's delivery charges. In Texas, TDU is already included in both rates.
Only one rate field ignores priceType
advertisedPriceUsdPerKwh never changes with priceType. It is always the supplier's flat display price, the same figure shown on the EFL, whether you request FLAT or seasonalized. Only allInRateUsdPerKwh, avgMonthlyBillUsd and the top-level price respond. priceType is a request-wide setting, not a per-field toggle.
Why all-in exists
Plan structures can be tricky — bill credits, fixed monthly fees — and the all-in rate folds those in so a shopper compares a real per-kWh number, where a bare advertised figure can mislead. The two are equal only on plans with no such adjustments. Use allInRateUsdPerKwh (and the seasonalized top-level price) for the effective supply cost. avgMonthlyBillUsd is the estimated bill at each usage level.

Example — residential plan query

{
  residentialPlans(zipCode: "77002", monthlyUsage: 1000) {
    title
    rateType
    term
    supplier { name shortName registrationId }
    rates { usageKwh advertisedPriceUsdPerKwh allInRateUsdPerKwh avgMonthlyBillUsd }
    feeBreakdown { feeType amountUsd applicability }
    documents { type url }
    earlyTerminationFeeUsd
    isBillCreditPlan
    supplierScores { powerHqRating plansAndRates customerService renewablePlans pucRating }
    headlessEnrollmentUrl
  }
}
{
  "title": "12 Month (No Min Usage Fee)",
  "rateType": "FIXED", "term": 12,
  "supplier": { "name": "Constellation NewEnergy, Inc.", "shortName": "Constellation", "registrationId": "10014" },
  "rates": [
    { "usageKwh": 500,  "advertisedPriceUsdPerKwh": 0.143, "allInRateUsdPerKwh": 0.1428,  "avgMonthlyBillUsd": 71.4 },
    { "usageKwh": 1000, "advertisedPriceUsdPerKwh": 0.138, "allInRateUsdPerKwh": 0.1379,  "avgMonthlyBillUsd": 137.9 },
    { "usageKwh": 2000, "advertisedPriceUsdPerKwh": 0.136, "allInRateUsdPerKwh": 0.13545, "avgMonthlyBillUsd": 270.9 }
  ],
  "feeBreakdown": [
    { "feeType": "UTILITY_PASS_THRU", "amountUsd": 4.9,   "applicability": "MONTHLY" },
    { "feeType": "UTILITY_PASS_THRU", "amountUsd": 0.051, "applicability": "PER_KWH" }
  ],
  "documents": [
    { "type": "EFL",  "url": "https://www.constellation.com/bin/residential/GetContractVersionPDF?versionNum=4972691" },
    { "type": "TOS",  "url": "https://www.constellation.com/bin/residential/GetContractVersionPDF?versionNum=4977905" },
    { "type": "YRAC", "url": "https://www.constellation.com/bin/residential/GetContractVersionPDF?versionNum=4977876" }
  ],
  "earlyTerminationFeeUsd": 150.0,
  "isBillCreditPlan": false,
  "supplierScores": { "powerHqRating": 4.7, "plansAndRates": 4.0, "customerService": 5.0, "renewablePlans": 2.0, "pucRating": 5.0 },
  "headlessEnrollmentUrl": "https://www.cert.powerhq.co/partner-app.html?utm_source=YOUR_PARTNER_CODE&utm_medium=referral&utm_campaign=partners#/redirect?ftype=RESIDENTIAL_PARTNER&plan_id=7a8c1d9f-f83a-4322-850f-c6939ccf4fb3&zip_code=77002&ref=YOUR_PARTNER_CODE"
}
Enrollment & attribution
Each plan returns enrollmentUrl (hosted) and headlessEnrollmentUrl (embeddable). Both are stamped with your partner code (utm_source / ref) so conversions attribute to you. An ENROLLMENT document, when present, links to the supplier's own site — not a PowerHQ flow.
Disclosure documents vary by state — and must be shown
The types in documents[] vary by market and by supplier. Texas plans typically return EFL (Electricity Facts Label), TOS and YRAC. Outside Texas the mix is inconsistent: in Pennsylvania (19103), of 14 plans, 9 return only TOS or TOS + ENROLLMENT, 4 include CONTRACT_SUMMARY, and one supplier returns a full Texas-style EFL + YRAC + TOS set. Never assume a given type is present, and never assume it is absent — read documents[] per plan. These disclosure documents must be accessible to the customer in your UI whenever a plan is displayed (and before enrollment) — surface the relevant documents[] link(s) for each plan.

Types & enums

ResidentialPlan

FieldTypeNotes
idID!Stable; matches ingested plan id
title / descriptionString!
priceFloat!$/kWh at the requested monthlyUsage (seasonalized)
termInt!Contract length, months
renewablePercentageInt!
rateTypeRateTypeFIXED · VARIABLE · PREPAID · SUBSCRIPTION
rates[PlanRate!]!Advertised + all-in + est. bill at 500/1000/2000
feeBreakdown[PlanFee!]!Component charges & credits
documents[PlanDocument!]!EFL / TOS / YRAC / ENROLLMENT links (may be empty)
supplierSupplier!
supplierScoresSupplierScoresNullable; sourced separately from pricing
earlyTerminationFeeUsd / …Type / isEarlyTerminationPenalizedFloat / enum / Boolean!
isBillCreditPlan · isPetFriendly · timeOfUseBoolean!
minimumStartDate / maximumStartDateDateNullable
tags[PlanTag!]!
utilityCode / stateCodeStringTDU / state; nullable
createdAtDateTime
enrollmentUrl / headlessEnrollmentUrlString!Hosted / embeddable hand-off

Supporting object types

type PlanRate { usageKwh: Int!  advertisedPriceUsdPerKwh: Float!  allInRateUsdPerKwh: Float!  avgMonthlyBillUsd: Float! }
                        # advertisedPriceUsdPerKwh is fixed regardless of priceType
type PlanFee { feeType: FeeType!  amountUsd: Float!  applicability: FeeApplicability!  threshold: Float  rules: [RuleEntry!] }
type RuleEntry { key: String!  value: String! }  # qualifying condition on a fee; almost always null
type PlanDocument { type: LinkType!  url: String!  title: String }
type Supplier { id: ID!  name: String!  shortName: String  logoUrl: String!  registrationId: String }
type SupplierScores { powerHqRating: Float  plansAndRates: Float  customerService: Float  renewablePlans: Float  pucRating: Float }
type PlanTag { key: String!  value: String  label: String  reasons: [String!] }
type BusinessPlan { id: ID!  term: Int!  price: Float!  monthlyFee: Float!  renewablePercentage: Int!  supplier: Supplier!  agreementUrl: String!  enrollmentUrl: String!  headlessEnrollmentUrl: String! }
type Utility { id: ID!  name: String!  residentialPlans: [ResidentialPlan!] }
type UtilityAccount { id: ID!  accountNumber: String  accountStatus: AccountStatus  serviceAddress: Address!  displayAddress: String  isBusiness: Boolean!  utilityCode: String! }
type EnergyInfo { averagePrice: Float!  stateCode: String!  stateName: String!  electricityGenerationPercentage: ElectricityGenerationPercentage }
                          # averagePrice is CENTS per kWh (e.g. 16.44), unlike plan price which is DOLLARS per kWh
type EnrollmentUrl { enrollmentUrl: String!  headlessEnrollmentUrl: String! }
input EnrollmentCustomerInput { firstName: String  lastName: String  email: String  phone: String }
input EnrollmentAddressInput { street: String  street2: String  city: String  state: String }
What rules is for
feeBreakdown[].rules carries a qualifying condition on a fee, for the rare plan where the fee only applies to certain customers. It is null on almost every fee. The one populated case in Texas today is Octopus Energy's "Octo EV 12", where the per-kWh credit only applies to EV owners:
{ "feeType": "CREDIT_PER_KWH", "applicability": "DEVICE", "rules": [ { "key": "ELECTRIC_DEVICE_TYPE", "value": "EV" } ] }

Enums

RateType FIXEDVARIABLEPREPAIDSUBSCRIPTION

LinkType EFLTOSYRACENROLLMENTPREPAID_DISCLOSURE_STATEMENTCONTRACT_SUMMARYENVIRONMENTAL_DISCLOSUREARBITRATION_ADDENDUMCOMM_POLICYPAYMENT_TERMS

FeeApplicability PER_KWHMONTHLYPER_KWH_BELOW_USAGEMONTHLY_BELOW_USAGEMONTHLY_ABOVE_USAGECREDIT_MONTHLY_ABOVE_USAGECREDIT_MONTHLY_BELOW_USAGESCHEDULEDEVICEPER_KW

EarlyTerminationFeeType FIXEDPER_MONTH_REMAINING_PERIODNONEUNKNOWN

AccountStatus ACTIVEINACTIVEDE_ENERGIZED

FeeType includes residential values (ENERGY_CHARGE, UTILITY_PASS_THRU, BASE_CHARGE, BILL_CREDIT, MIN/MAX_USAGE_CHARGE, CREDIT_PER_KWH, UPCHARGE_PER_KWH) plus commercial BUS_* charges.

Errors

Query and validation problems return HTTP 200 with an errors[] array — not a REST-style error envelope:

{
  "errors": [ {
    "message": "Exception while fetching data (/businessPlans) : Invalid zipcode 1234",
    "locations": [ { "line": 1, "column": 3 } ],
    "path": [ "businessPlans" ]
  } ],
  "data": { "businessPlans": null }
}
The shape of data on error depends on the field
GraphQL nullability decides whether the failed field is nulled or the whole data key disappears. Handle both.
FieldSignatureOn error
residentialPlans[ResidentialPlan!]!no data key at all
utilities[Utility!]{"data":{"utilities":null}}
businessPlans[BusinessPlan!]{"data":{"businessPlans":null}}
Reading response.data.residentialPlans without checking errors first will throw rather than return null.

Transport/auth failures return an HTTP status: 403 (missing or invalid key), 400 (malformed request). A utilityAccounts query with neither street nor accountNumber returns "No street address or account number specified for utility accounts search".

Field-level notes

  • documents[] and tags[] can legitimately be empty arrays.
  • feeBreakdown[].rules is null, not an empty array, on almost every fee. Across 74 plans in 75201 it came back null 238 times and [] zero times. Null-check before iterating.
  • supplierScores is nullable when a supplier has no ratings on file, and individual categories inside it may be null independently.
  • earlyTerminationFeeUsd, earlyTerminationFeeType, minimumStartDate, maximumStartDate, utilityCode, stateCode and createdAt are nullable; absence is not an error.
  • Plan id values regenerate on each refresh. Do not persist them, and do not match plans by id across calls.