Skip to main content
API v1 · Stable

EnergyCode Enterprise API

Query GB electricity and gas network charge data programmatically. DUoS, TNUoS, BSUoS, policy levies, Gas LDZ transportation, Line Loss Factors and MDD reference data. Every rate returned raw with provenance and source document.

your first request
curl "https://www.energycode.ai/api/v1/reference/dnos" \
  -H "X-Api-Key: ec_live_..."

What is the EnergyCode API?

The EnergyCode API provides access to UK electricity and gas network charges data, including:

  • DUoS (Distribution Use of System) - charges from all 14 DNO regions
  • TNUoS (Transmission Use of System) - NESO transmission charges
  • BSUoS (Balancing Services Use of System) - balancing costs
  • Capacity Market - capacity market levy
  • CfD (Contracts for Difference) - low-carbon generation levy (funds renewables and nuclear)
  • CCL (Climate Change Levy) - environmental tax rates (non-domestic supply only)
  • AAHEDC (Assistance for Areas with High Electricity Distribution Costs)
  • Nuclear RAB (Regulated Asset Base) levy - live from December 2025
  • Gas LDZ transportation - gas distribution Use of System charges (the gas analogue to DUoS), the five canonical UNC-governed components across the GB LDZs
  • LLF (Line Loss Factors) - seasonal network loss factors from Elexon ISD
  • Reference data - DNO regions, LLFC mappings, and MDD meter reference (MTCs, SSCs, TPRs, D0018 profile coefficients)

All data is validated against published charging statements and updated for each charging year.

Quick Start (5 Minutes)

1. Get Your API Key

Contact your account manager or email [email protected] to receive your API key. You'll receive a key in the format:

text
ec_live_a1b2c3d4e5f6...

For testing, you may also receive a test key prefixed with ec_test_.

2. Make Your First Request

List all DNO regions:

bash
curl -X GET "https://www.energycode.ai/api/v1/reference/dnos" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

Expected Response:

json
{
  "success": true,
  "data": [
    {
      "id": 10,
      "name": "Eastern Power Networks",
      "operator": "UKPN"
    },
    {
      "id": 11,
      "name": "East Midlands",
      "operator": "NGED"
    }
    // ... all 14 DNO regions
  ]
}

3. Fetch DUoS Rates

Now retrieve DUoS rates for a specific DNO:

bash
curl -X GET "https://www.energycode.ai/api/v1/rates/duos?dno_id=10&charging_year=2025-26" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

Expected Response:

json
{
  "success": true,
  "data": {
    "dno": {
      "id": 10,
      "name": "Eastern Power Networks"
    },
    "charging_year": "2025-26",
    "rates": [
      {
        "tariff_category": "DOM_AGG",
        "red_rate_pkwh": 12.34,
        "amber_rate_pkwh": 5.67,
        "green_rate_pkwh": 1.23,
        "fixed_rate_pmpan_day": 45.67,
        "capacity_rate_pkva_day": null,
        "exceeded_capacity_rate_pkva_day": null,
        "reactive_rate_pkvarh": null
      }
      // ... more tariff categories
    ]
  }
}

You've made your first API calls.

MPANs are strings. Keep the leading zeros.

An MPAN Core begins with 0 (e.g. 008450831071094911008). Spreadsheets and JSON number types silently strip leading zeros, turning a valid 21-digit MPAN into a 19- or 20-digit value the API rejects with 400 UNEXPECTED_LENGTH, whose message names dropped leading zeros as the likely cause. A 6-10 digit value (a gas MPRN pasted by mistake) is rejected with 400 LIKELY_MPRN. Always send mpan as a quoted string, zero-padded to 21 (legacy) or 22 (MHHS) digits.

Authentication

All API requests require an API key passed via the X-Api-Key header.

How to Authenticate

Include your API key in every request:

bash
-H "X-Api-Key: ec_live_abc123..."

API Key Types

Live

Format
ec_live_*
Purpose
Production use with real data

Test

Format
ec_test_*
Purpose
Testing and development (same data, separate quota)

Plan Scopes

Your plan may restrict your API key to specific datasets. A key with no scopes set has full access to every endpoint. When your key is scoped, only the matching datasets are reachable:

duos

Datasets
Red, amber and green unit rates, fixed, capacity, exceeded-capacity and reactive charges, Line Loss Factors, DNO regions, LLFC mappings

tnuos

Datasets
Locational (zonal) demand tariffs, TDR fixed-band charges, embedded export credits

balancing

Datasets
BSUoS fixed six-monthly tariffs

policy

Datasets
CCL, CfD, Capacity Market, AAHEDC, Nuclear RAB rates

gas

Datasets
Gas LDZ transportation rate card by MPRN (ZCA, ZCO, CCA, ECN, CFI, SOLR)

metering

Datasets
SSC, TPR and MTC meter-configuration reference, D0018 profile coefficients

calculate

Datasets
The POST /calculate compute endpoint (itemised computed charges)
  • /usage is always accessible, regardless of scopes.
  • A request to a dataset outside your key's scopes returns 403 with error code SCOPE_DENIED. Email [email protected] to upgrade your plan.

Security Best Practices

  1. Never expose API keys in client-side code (JavaScript, mobile apps)
  2. Use environment variables to store keys securely
  3. Rotate keys periodically (contact support to generate new keys)
  4. Use test keys for development - keep live keys for production only
  5. Monitor usage - contact support if you suspect unauthorized use

Rate Limits and Quotas

Usage is governed by three limits, agreed individually as part of your contract:

  1. Per-minute rate limit - short-burst throttle (requests per minute)
  2. Monthly call quota - billable calls consumed per calendar month
  3. Daily reference-row quota - rows returned per day from the reference endpoints (/reference/*), which protects the bulk MDD and mapping datasets from wholesale extraction

Your specific allowances are agreed individually as part of your contract. Your current allowance and remaining balance are returned in the response headers on every call.

Billable Calls

A call is the billable unit of quota. A successful rates or reference request costs one call, however much data it returns; a successful POST /calculate computation costs five calls (it runs a full multi-charge calculation), or seven when the consumption was platform-sourced (the five plus two for the sourcing). Failed requests (4xx/5xx), the free /usage meta call and the /electralink-key key-management calls cost nothing. Each response reports the cost of that call in the X-Request-Cost header.

ResponseCost
Any successful (2xx) rates or reference request1 call
A successful (2xx) POST /calculate computation5 calls
A successful (2xx) platform-sourced POST /calculate computation7 calls
/usage and /electralink-key key management0
Any error (4xx / 5xx)0

Reference endpoints (/reference/*) also count the rows they return against a separate daily reference-row quota, on top of the one-call cost.

Billing Mode

Every account is either capped or uncapped. A capped account stops with 429 QUOTA_EXCEEDED once the monthly call allowance is reached; an uncapped account keeps serving and meters calls beyond the allowance as overage, billed per your contract. The active mode is returned on every response in X-Quota-Mode, with any overage in X-Overage-Calls.

Response Headers

Every successful response carries the per-minute rate limit state, the monthly call quota state, and the cost of the call:

http
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1738348800
X-Request-Cost: 1
X-Quota-Limit: 15000
X-Quota-Remaining: 13760
X-Quota-Mode: capped
HeaderDescription
X-RateLimit-LimitMaximum requests per minute
X-RateLimit-RemainingRemaining requests in the current per-minute window
X-RateLimit-ResetUnix timestamp when the per-minute window resets
X-Request-CostBillable calls this request consumed (1 for most endpoints, 5 for a successful POST /calculate, 0 on errors)
X-Quota-LimitMonthly call allowance for your account
X-Quota-RemainingCalls remaining in the current calendar month
X-Quota-ModeBilling mode for your account: capped or uncapped
X-Overage-CallsCalls beyond the allowance this month (uncapped accounts only)

Handling 429 Responses

When you exceed your per-minute rate limit, the API returns 429 Too Many Requests with code RATE_LIMIT_EXCEEDED:

json
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please try again later."
  }
}

When a capped account reaches its monthly call quota or its daily reference-row quota, the API returns 429 with code QUOTA_EXCEEDED:

json
{
  "success": false,
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Monthly call allowance reached for your account."
  }
}

Response Headers on 429:

http
Retry-After: 42

The Retry-After header tells you how many seconds to wait before retrying. Check your own current consumption and remaining quota at any time via GET /usage.

Best Practices for Avoiding Rate Limits

  1. Implement exponential backoff - wait longer between retries
  2. Cache responses - store frequently accessed data locally
  3. Batch requests - fetch data in bulk rather than making many small requests
  4. Monitor X-RateLimit-Remaining - proactively slow down before hitting the limit
  5. Contact support if you need higher limits

Examples

1. DUoS rates for a DNO region

All DUoS tariff categories and rates for a DNO region and charging year - Eastern Power Networks (DNO ID 10), 2025-26:

bash
curl -X GET "https://www.energycode.ai/api/v1/rates/duos?dno_id=10&charging_year=2025-26" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

Filter by tariff category:

bash
curl -X GET "https://www.energycode.ai/api/v1/rates/duos?dno_id=10&charging_year=2025-26&tariff_category=ND_AGG_B1" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

2. TNUoS rates

bash
curl -X GET "https://www.energycode.ai/api/v1/rates/tnuos?charging_year=2025-26" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

3. CCL rates

bash
curl -X GET "https://www.energycode.ai/api/v1/rates/ccl?charging_year=2025-26" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

Calculating Charges

Beta · schema 2026-07-01

The rate endpoints above return published rates. POST /calculate returns the computed network and policy charges for a supply: send an MPAN, a billing period and consumption, and it prices each charge line by line from published rates. Every line carries its quantity, rate, rate unit and amount in GBP, so the total reconciles to the lines by construction.

Beta

The calculate endpoint is in beta and still under test. It computes every metered demand profile class: Profile Classes 01 to 08 (non-half-hourly and Maximum Demand) and Profile Class 00 (half-hourly settled, with metered half-hourly input). Generation and export MPANs are not supported and return 422 GENERATION_NOT_YET_SUPPORTED. The response contract is versioned (schema_version: "2026-07-01"); additive fields may appear without a version bump, breaking changes never do.

Getting these figures right is the hard part of non-commodity billing, and the endpoint is built around that difficulty. Rates load from the published source statements for the billing period's own charging year. Meter switch windows come from the meter's actual Standard Settlement Configuration in Elexon Market Domain Data, not from a national assumption. Every estimated quantity is disclosed on the line it affects and in a machine-readable block. And where a defensible figure cannot be computed, the endpoint refuses with a specific error code rather than returning a plausible number.

What it computes

Eight non-commodity electricity charges, selected per request:

  • Network: DUoS (unit red/amber/green, fixed, and, on the Maximum Demand classes, capacity, exceeded capacity and reactive), TNUoS (locational and residual), BSUoS.
  • Policy: Capacity Market, CCL, CfD, Nuclear RAB, AAHEDC.

Select the charges with the required charges_to_calculate object. There is no default, so a partial calculation is always deliberate. Capacity Market computes on the classes it applies to (Profile Class 00 and 05 to 08) as a disclaimed estimate; requesting it on Profile Class 01 to 04 returns 422 CHARGE_NOT_YET_SUPPORTED.

What it does not compute

This endpoint never computes the following. The response lists them under charges_not_yet_supported, so a partial total is always explicit:

  • Wholesale energy (supplier-negotiated)
  • Renewables Obligation (RO)
  • Feed-in Tariff (FIT)
  • Smart DCC
  • Gas LDZ transportation

Generation and export are not currently supported. A generation or export MPAN returns 422 GENERATION_NOT_YET_SUPPORTED rather than a number; export tariffs and embedded-generation credits are a later release. EDCM (EHV site-specific) MPANs are also not yet supported and return 422 EDCM_NOT_YET_SUPPORTED.

On the roadmap for calculation through the API:

  • Generation and export pricingComing soon
  • Gas network charge calculation (LDZ transportation)Coming soon
  • Platform-sourced consumption via industry data flowsComing soon
  • EHV site-specific (EDCM) pricingComing soon

IDNO import MPANs are supported when you supply a host-DNO location. An IDNO MPAN's distributor id (24 to 76) names the operator, not a region, and an IDNO's rate mirrors its host DNO, whose region is fixed by the site location. That is why pricing needs one location input the MPAN alone cannot yield: supply either override_distributor_id (the host DNO id, 10 to 23) or postcode (the site postcode or outward code, resolved to the host DNO for you). With a location supplied, an IDNO import MPAN prices at the host DNO's published CDCM rates, identical to a host-DNO MPAN in that region. The resolved host is echoed in summary.resolved_host_distributor_id and summary.host_dno_resolution.

Supply neither location on an IDNO MPAN and the request returns 422 IDNO_HOST_REQUIRED naming how to add it. Supply both and they resolve to different host DNOs, and the request returns 400 HOST_DNO_CONFLICT: a priced result never guesses between two conflicting locations. Coverage is identical to a host-DNO MPAN (currently 2022 to 2028); an unpublished period returns 422 RATES_NOT_PUBLISHED.

The accuracy contract

The endpoint never returns a silently wrong number. Its refusals are the guarantee:

  • Published rates only. A requested charge with no published rate for the billing period returns 422 RATES_NOT_PUBLISHED naming the charge and charging year. A period beyond the published holdings refuses; it is never priced on a forecast or extrapolated rate. The one deliberate exception is the Capacity Market: no published per-supply Capacity Market rate exists, so its line is always a disclaimed estimate (source: "estimated") and says so on the line.
  • Refuses rather than assumes. A declaration that moves real money and has no honest default is required, never defaulted. A configuration, period or supply that cannot be priced defensibly returns a specific error code rather than a plausible number: the error-code table below is this contract in full.
  • Estimates are disclosed, twice. Where a quantity is modelled rather than metered, the affected line carries a note and summary.consumption_basis records the basis machine-readably. Rate provenance and quantity basis are separate axes: a modelled quantity never downgrades the rate's published source, and a published rate never hides that its quantity was modelled.
  • Platform-sourced data is either whole or refused. When your account sources the half-hourly data through the platform (consumption_source: "platform"), a window the retrieved data cannot fully cover returns 422 PLATFORM_CONSUMPTION_PARTIAL_WINDOW rather than pricing a partial window, and every priced result discloses its settlement provenance in summary.consumption_basis.platform.
  • Errors cost nothing. Every refusal bills 0 calls (X-Request-Cost: 0).

Request reference

FieldRequiredTypePurpose
mpanyesstring21-digit legacy or 22-digit MHHS MPAN (quoted string, leading zeros kept)
billing_periodyesobjectstart_date and end_date, ISO YYYY-MM-DD
consumptionunless platform-sourcedobjectExactly one input format: total, day_night, rag or half_hourly (see below). Required unless consumption_source: "platform" sources it for you
consumption_sourcenoenum"platform" is the only value: source the consumption through your registered ElectraLink credentials instead of posting it. Profile Class 00 sources metered half-hourly data; Profile Classes 01 to 08 source the supply point's Estimated Annual Consumption, labelled as an estimate (see Settlement-Sourced Pricing below). Ignored when consumption is supplied
charges_to_calculateyesobjectBoolean flags for the charges to compute; at least one true
capacity_kvaon PC 05 to 08numberThe agreed Maximum Import Capacity (kVA). Required on Profile Class 05 to 08; optional on a non-domestic Profile Class 00; rejected elsewhere
max_demand_kvanonumberMeasured maximum demand (kVA) for the exceeded-capacity charge. Profile Class 05 to 08 only
kvarhnonumberMeasured reactive energy (kVArh) for the reactive-power charge. Profile Class 05 to 08 only
peak_demand_kwnonumberWinter peak demand (kW) for the Capacity Market estimate. Profile Class 05 to 08, and an override on Profile Class 00
triad_demand_kwnonumberCustomer-asserted Triad-average demand (kW) for TNUoS locational. Profile Class 00 only
sscnostring4-character Standard Settlement Configuration id, e.g. "0246"
tnuos_bandnoenumTNUoS TDR residual band: "domestic" (Profile Class 01/02) or 1 to 4 (Profile Class 03 to 08)
tariff_category_codenostringAsserts the DUoS tariff category, e.g. "ND_AGG_B1" or "LV_SS_B1"
cclnoobjectCCL declarations: cca_holder, exempt, domestic_premises (a Profile Class 00 override; domesticity otherwise derives from the MPAN's tariff category)
override_distributor_idnonumberOverrides the distributor decoded from the MPAN. Only DNO ids (10 to 23) compute; other values are refused

Unknown fields return 400 VALIDATION_ERROR. The raw engine objects (capacity, reactive, capacity_market, tnuos) are rejected with a message pointing at the accepted flat field (capacity_kva, kvarh, peak_demand_kw, triad_demand_kw), and website-only or later-tranche fields (billed_values, cross_year_consumption) are rejected with a specific reason in details.field_errors, so a copied payload never fails silently.

Consumption input formats

Supply exactly one of the four formats. Which formats a supply accepts depends on its profile class; a format outside the supply's accepted set returns 422 CONSUMPTION_FORMAT_NOT_YET_SUPPORTED naming the formats it does accept.

FormatYou sendApplies to
half_hourlyper-day settlement-period kWhProfile Class 00: the metered path, and the highest-fidelity input
totaltotal_kwhProfile Class 01 to 08: one scalar, the default recommendation
day_nightday_kwh, night_kwhThe Economy 7 classes (02 and 04): register reads
ragred_kwh, amber_kwh, green_kwhEvery supported class: replay the banded quantities from a DNO invoice for bill validation

Response shape

A successful response returns data with a versioned shape: schema_version, summary (identity, period and the consumption-basis disclosure), line_items (the itemised breakdown: one line per charge component with its quantity, rate, rate unit and amount in GBP), charge_detail (sub-period reconciliation blocks), charges_included and charges_not_yet_supported (coverage), totals, provenance and errors (always empty on a 200; per-charge failures return 502 instead of a partial 200). The worked example below shows every block in a real response.

Platform-sourced consumption

On a Profile Class 00 (half-hourly settled) supply, your account can have the endpoint fetch the metered half-hourly data itself rather than posting it: register your own ElectraLink data-access credentials once, then send consumption_source: "platform" with no consumption object. The retrieved settlement half-hourly data is bucketed and priced through the identical half-hourly pipeline (rag_split: "metered" included), and the response adds a settlement-provenance block: summary.consumption_basis.platform records the provider, the settlement-run label and run date exactly as the data source labelled them, the fraction of periods flagged estimated, and a factual coverage label. Estimated periods are normal settlement output: they are priced identically to actual reads and labelled, never refused.

Credentials are registered on PUT /api/v1/electralink-key (with GET for status and DELETE to remove), a write-only surface: the credential is encrypted at rest, never shown again, and every response carries display metadata only. A newly stored credential is held as pending_verification until the platform verifies it against the data source; until then a platform-sourced calculation returns 422 PLATFORM_KEY_NOT_VERIFIED and you can supply consumption directly. Registering again rotates (replaces) the credential. Key management bills 0.

Supplied data always wins: when a consumption object is present, consumption_source is ignored and the request prices exactly as posted. A platform-sourced success costs 7 calls (the 5 of every successful calculation plus 2 for the sourcing); every platform refusal bills 0. The refusal codes are in the error table below.

Enabled per account

Platform-sourced consumption is enabled per account. Without the capability, any request setting consumption_source: "platform" returns 403 FORBIDDEN, and the /electralink-key endpoint returns the same 403 before any credential read. To have it switched on for your account, write to [email protected].

Full methodology reference

This page documents the calculate endpoint at evaluation depth: what it computes, the request shape, the error contract and a complete worked example. The full methodology reference: the per-field request contracts, the per-class format matrix and the basis each format prices on, the consumption_basis disclosure reference, the line-item and reconciliation conventions, and further verified worked examples across Profile Classes 00 to 08, is enabled per account at /developers/reference. To have it switched on for your account, write to [email protected].

Worked example

The example below is a verified figure. The request reproduces a golden test case hand-derived from the published source statements (DNO Annex 1 schedules, NESO TNUoS tariff tables, LCCC CfD rates, HMRC CCL rates), and the documented response is the endpoint's actual output for that request. The engine must land on the hand-derived value to the penny before a case ships. The MPAN is a synthetic test value; substitute a real supply's MPAN. The full reference documents further verified examples across every supported profile class.

Profile Class 03, total, with a full response

A non-domestic aggregated supply with the first non-zero CCL. The complete verified response:

bash
curl -X POST "https://www.energycode.ai/api/v1/calculate" \
  -H "X-Api-Key: YOUR_API_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "mpan": "038011991087654321009",
    "billing_period": { "start_date": "2025-06-01", "end_date": "2025-06-30" },
    "consumption": { "total_kwh": 3080, "input_format": "total" },
    "charges_to_calculate": { "duos": true, "tnuos": true, "bsuos": true, "cfd": true, "nuclearRab": true, "aahedc": true, "ccl": true }
  }'

Response:

json
{
  "success": true,
  "data": {
    "schema_version": "2026-07-01",
    "summary": {
      "mpan": "038011991087654321009",
      "dno_name": "Eastern Power Networks",
      "dno_region": "Eastern",
      "tariff_code": "ND_AGG_NR",
      "tariff_name": "Non-Domestic Aggregated or CT No Residual",
      "billing_period": { "start_date": "2025-06-01", "end_date": "2025-06-30" },
      "billing_days": 30,
      "total_kwh": 3080,
      "resolved_host_distributor_id": 10,
      "host_dno_resolution": "native",
      "consumption_basis": {
        "input_format": "total",
        "rag_split": "d0018_estimated",
        "estimation_method": "d0018_profile_shape",
        "tnuos_band_source": "auto_derived",
        "tnuos_band_note": "TNUoS residual band auto-estimated from consumption. It may differ from the frozen NESO/DNO band assignment for the price-control period; supply tnuos_band for the billed figure.",
        "tnuos_locational_quantity_basis": "d0018_modelled_peak_4_7pm",
        "duos_band_provenance": "llfc_mapped"
      }
    },
    "line_items": [
      { "key": "duos", "label": "Unit rates", "group": "network", "quantity": 329.09, "quantity_unit": "kWh", "rate": 11.834, "rate_unit": "p/kWh", "amount_gbp": 38.94, "source": "published", "note": "Red/amber/green split estimated from total kWh using Elexon D0018 profile coefficients (PC03) and the DNO's published time-band definitions. Rate is published; the volume in each band is an estimate." },
      { "key": "duos", "label": "Unit rates", "group": "network", "quantity": 1562.42, "quantity_unit": "kWh", "rate": 1.349, "rate_unit": "p/kWh", "amount_gbp": 21.08, "source": "published", "note": "Red/amber/green split estimated from total kWh using Elexon D0018 profile coefficients (PC03) and the DNO's published time-band definitions. Rate is published; the volume in each band is an estimate." },
      { "key": "duos", "label": "Unit rates", "group": "network", "quantity": 1188.49, "quantity_unit": "kWh", "rate": 0.226, "rate_unit": "p/kWh", "amount_gbp": 2.69, "source": "published", "note": "Red/amber/green split estimated from total kWh using Elexon D0018 profile coefficients (PC03) and the DNO's published time-band definitions. Rate is published; the volume in each band is an estimate." },
      { "key": "duos-fixed", "label": "Fixed charge", "group": "network", "quantity": 30, "quantity_unit": "days", "rate": 8.47, "rate_unit": "p/MPAN/day", "amount_gbp": 2.54, "source": "published" },
      { "key": "bsuos", "label": "BSUoS", "group": "network", "quantity": 3080, "quantity_unit": "kWh", "rate": 1.074025974025974, "rate_unit": "p/kWh", "amount_gbp": 33.08, "source": "published" },
      { "key": "tnuos-locational", "label": "Locational", "group": "network", "quantity": 430.909, "quantity_unit": "kWh", "rate": 0.152494, "rate_unit": "p/kWh", "amount_gbp": 0.66, "source": "published", "note": "Quantity is the 4pm to 7pm peak-band consumption, modelled from Elexon D0018 class-average profile coefficients applied to total consumption, not directly metered. The published zonal rate is applied to this modelled quantity." },
      { "key": "tnuos", "label": "Residual", "group": "network", "quantity": 30, "quantity_unit": "days", "rate": 2.068587, "rate_unit": "GBP/day", "amount_gbp": 62.06, "source": "published" },
      { "key": "ccl", "label": "CCL", "group": "policy", "quantity": 3080, "quantity_unit": "kWh", "rate": 0.775, "rate_unit": "p/kWh", "amount_gbp": 23.87, "source": "published", "ccl_basis": "main_rate" },
      { "key": "cfd", "label": "CfD", "group": "policy", "quantity": 3080, "quantity_unit": "kWh", "rate": 1.2068181818181818, "rate_unit": "p/kWh", "amount_gbp": 37.17, "source": "published" },
      { "key": "nuclear-rab", "label": "Nuclear RAB", "group": "policy", "quantity": 3080, "quantity_unit": "kWh", "rate": 0, "rate_unit": "p/kWh", "amount_gbp": 0, "source": "published", "note": "Nuclear RAB levy not yet in effect before December 2025." },
      { "key": "aahedc", "label": "AAHEDC", "group": "policy", "quantity": 3080, "quantity_unit": "kWh", "rate": 0.04090909090909091, "rate_unit": "p/kWh", "amount_gbp": 1.26, "source": "published" }
    ],
    "charge_detail": {
      "cfd_quarters": [
        { "quarter": "Q2", "year": "2025", "period_start": "2025-04-01", "period_end": "2025-06-30", "days_in_quarter": 30, "ilr_gbp_mwh": 11.959, "operational_levy_gbp_mwh": 0.1089, "kwh_portion": 3080, "charge_gbp": 37.17 }
      ],
      "bsuos_periods": [
        { "tariff_number": 5, "period_start": "2025-06-01", "period_end": "2025-06-30", "rate_pkwh": 1.074, "kwh_portion": 3080, "days": 30, "charge_gbp": 33.08 }
      ]
    },
    "charges_included": [
      { "charge": "duos", "computed": true },
      { "charge": "tnuos", "computed": true },
      { "charge": "bsuos", "computed": true },
      { "charge": "cfd", "computed": true },
      { "charge": "ccl", "computed": true },
      { "charge": "nuclearRab", "computed": true },
      { "charge": "aahedc", "computed": true }
    ],
    "charges_not_yet_supported": [
      { "charge": "wholesale-energy", "computed": false, "reason": "out of scope: wholesale energy is supplier-negotiated" },
      { "charge": "ro", "computed": false, "reason": "out of scope: Renewables Obligation not computed by this endpoint" },
      { "charge": "fit", "computed": false, "reason": "out of scope: Feed-in Tariff not computed by this endpoint" },
      { "charge": "smart-dcc", "computed": false, "reason": "out of scope: Smart DCC not computed by this endpoint" },
      { "charge": "gas", "computed": false, "reason": "out of scope: gas LDZ transportation not computed by this endpoint" }
    ],
    "totals": { "total_charges_gbp": 223.35 },
    "provenance": {
      "charging_years": ["2025-26"],
      "charges": [
        { "charge": "duos", "source": "published", "detail": "published", "charging_year": "2025-26" },
        { "charge": "ccl", "source": "published", "charging_year": "2025-26" },
        { "charge": "bsuos", "source": "published", "charging_year": "2025-26" },
        { "charge": "tnuos", "source": "published", "charging_year": "2025-26" },
        { "charge": "cfd", "source": "published", "charging_year": "2025-26" },
        { "charge": "aahedc", "source": "published", "charging_year": "2025-26" },
        { "charge": "nuclear_rab", "source": "published", "detail": "Nuclear RAB levy not yet in effect before December 2025.", "charging_year": "2025-26" }
      ],
      "rates_vintage": "2026-07-13T12:00:00.000Z"
    },
    "errors": []
  }
}

Points worth reading off this response:

  • Each DUoS unit line is one band (red, amber, green); the quantities are a disclosed D0018 estimate and the note says so, while the rate source stays published.
  • TNUoS is two honest lines. The locational quantity (430.909 kWh) is modelled 4pm to 7pm consumption and carries the disclosure; the residual is the published £2.068587/day for the auto-derived band across 30 days.
  • CCL is charged at the published 0.775 p/kWh (ccl_basis: "main_rate"): the supply's annualised average clears the de-minimis threshold.
  • charge_detail.cfd_quarters reconciles to the CfD line: (11.959 + 0.1089) × 3080 ÷ 1000 = £37.17.
  • The total, £223.35, is the sum of the eleven 2dp line amounts.

Settlement-Sourced Pricing

Beta

Send an MPAN and let the API source the supply's consumption for you, instead of posting consumption yourself.

  1. Register an ElectraLink credential once with PUT /electralink-key. The API stores it encrypted, verifies it live on save, and never returns it to you again. Update or delete it at any time.
  2. Call POST /calculate with consumption_source set to "platform" and the MPAN. The API retrieves the supply's own settlement data and prices every network, transmission and policy charge from it.
bash
curl -X POST "https://www.energycode.ai/api/v1/calculate" \
  -H "X-Api-Key: YOUR_API_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "mpan": "038011991087654321009",
    "consumption_source": "platform",
    "billing_period": { "start_date": "2025-06-01", "end_date": "2025-06-30" },
    "charges_to_calculate": { "duos": true, "tnuos": true, "bsuos": true, "cfd": true, "nuclearRab": true, "aahedc": true, "ccl": true }
  }'

Settlement-Sourced Pricing works two ways, chosen from the meter. A half-hourly settled import supply (Profile Class 00) is priced from its own half-hourly settlement data. An ordinary non-half-hourly meter (Profile Classes 01 to 08) is priced from the supply point's Estimated Annual Consumption, apportioned to the billing period and labelled as an estimate throughout (derivation: "eac_derived"). Where a supply is out of coverage, or its data is missing or estimated, the call returns a labelled reason and no priced figure. The error codes below name each case.

Error codes

Every refusal below is the guarantee of never a silently-wrong number. A June 2029 period lies beyond the published DUoS holdings, so the endpoint refuses rather than pricing on extrapolated rates:

json
{
  "success": false,
  "error": {
    "code": "RATES_NOT_PUBLISHED",
    "message": "Published rates are not available for duos for 2029-30. This endpoint only returns computed charges from published rates.",
    "details": {
      "charges": ["duos"],
      "charging_years": ["2029-30"],
      "provenance": [
        { "charge": "duos", "source": "forecast", "detail": "only a published forward-view (forecast) rate is available for the requested period", "charging_year": "2029-30" }
      ]
    }
  }
}

The response bills 0 calls (X-Request-Cost: 0), like every refusal on this endpoint.

HTTP StatusError CodeMeaning
400VALIDATION_ERRORUnknown or rejected field, missing or malformed charges_to_calculate, malformed body, an out-of-range billing period, an ambiguous consumption shape, an ssc that does not map to the profile class or cannot carry a day/night split, an ssc contradicting a 22-digit MPAN's embedded SSC, an invalid tnuos_band for the profile class, a demand field (capacity_kva, max_demand_kva, kvarh, peak_demand_kw, triad_demand_kw) on a profile class it does not apply to, a half-hourly structure fault (a gap, duplicate or wrong per-day period count, named with the offending date and expected count), a CCL contradiction (cca_holder: true on a domestic supply; capacity_kva on a supply that resolves domestic, declared or derived from a DOM_* category; a ccl.domestic_premises that conflicts with the effective tariff category, false against a DOM_* category or true against a non-domestic family, or that conflicts with the caller's own asserted tariff_category_code) or ccl.domestic_premises outside Profile Class 00. details.field_errors names each field.
400INVALID_MPANA full-length MPAN failed format or check-digit validation, carries an embedded distributor id that is neither a DNO (10 to 23) nor an IDNO (24 to 76), or a 22-digit MPAN's embedded SSC does not map to its profile class (an internally inconsistent MPAN).
400HOST_DNO_CONFLICTAn IDNO import MPAN with both override_distributor_id and postcode, resolving to different host DNOs. details.override_distributor_id and details.postcode_host_distributor_id name both. Supply one location input. A billed compute refuses the ambiguity rather than guessing.
400NIGHT_SHARE_REQUIRES_SSCA night_share on a Profile Class 04 total with no sourceable SSC. That total prices via the whole-shape profile, which has no night register for a share to attach to. Supply an ssc, or omit night_share.
403FORBIDDENThe request set consumption_source: "platform" but the account does not hold the platform-sourcing capability. Enabled per account: contact [email protected]. Checked before anything else, so nothing about credential state is disclosed.
405METHOD_NOT_ALLOWEDThe endpoint accepts POST only.
413REQUEST_TOO_LARGERequest body exceeds the 1.5MB cap (sized for well over a year of half-hourly data).
415UNSUPPORTED_CONTENT_ENCODINGThe request carried a Content-Encoding (compressed body). Send raw JSON.
422PROFILE_CLASS_NOT_YET_SUPPORTEDThe supply's profile class is outside the supported Profile Class 00 to 08 set.
422CAPACITY_REQUIREDA Profile Class 05 to 08 request without capacity_kva. The agreed Maximum Import Capacity prices the capacity line and sets the TNUoS band, and has no honest default.
422CCL_DOMESTICITY_REQUIREDA Profile Class 00 request with CCL or TNUoS requested where domesticity could be neither declared nor derived: no ccl.domestic_premises, and the LLFC does not map to a tariff category with no tariff_category_code asserted to derive it from. A half-hourly supply's domesticity moves the CCL line and the TDR band, and has no honest default.
422CHARGE_NOT_YET_SUPPORTEDA requested charge is not enabled for the supply's profile class (capacityMarket on Profile Class 01 to 04). details.charges names it; details.supported lists the enabled set. Returned even when a supported charge is also requested.
422CHARGE_NOT_COMPUTABLEA requested charge cannot be computed for this supply and period. details.charges names them. Returned rather than a partial total.
422CONSUMPTION_FORMAT_NOT_YET_SUPPORTEDThe consumption format is outside the supply's accepted set: day_night outside Profile Class 02/04, total or day_night on Profile Class 00, half_hourly outside Profile Class 00. details.supported_formats lists the formats enabled for the supply.
422METER_CONFIG_REQUIREDA Profile Class 04 day/night request with no resolvable SSC (no ssc field and no MHHS-embedded SSC). Supply total (priced via the published whole-shape profile), supply banded red/amber/green, supply the meter's ssc, or use a 22-digit MHHS MPAN.
422METER_CONFIG_UNSOURCEABLEA Profile Class 04 day/night SSC is a teleswitch meter or otherwise has no sourceable switch window. Profile Class 04 register reads are not priced on the standard fallback window. Supply total, supply banded red/amber/green, or use a meter whose window can be sourced.
422RESIDUAL_BAND_UNRESOLVEDThe MPAN's LLFC does not map to a tariff category, so the DUoS residual band cannot be resolved. Supply tariff_category_code to assert it. Returned rather than assuming the smallest band.
422GENERATION_NOT_YET_SUPPORTEDGeneration or export MPAN. Generation and export are not currently supported by this endpoint.
422IDNO_HOST_REQUIREDIDNO import MPAN (embedded distributor id 24 to 76) with neither override_distributor_id nor postcode. The id names the IDNO operator, not a region, and an IDNO's rate mirrors its host DNO, whose region is fixed by the site location, so pricing needs a host-DNO location input the MPAN alone cannot yield. details.distributor_id names the IDNO id. Supply override_distributor_id (host DNO 10 to 23) or postcode to price at the host DNO's published rates.
422POSTCODE_HOST_NOT_FOUNDA postcode on an IDNO import MPAN parsed to a valid outward code with no host-DNO mapping. details.outward_code names it. Check the postcode, or supply override_distributor_id directly.
422EDCM_NOT_YET_SUPPORTEDEDCM (EHV site-specific) MPAN.
422CROSS_YEAR_NOT_YET_SUPPORTEDThe billing period spans two charging years. details.start_charging_year and details.end_charging_year name both. Split the request at 1 April.
422RATES_NOT_PUBLISHEDNo published rate exists for a requested charge in the billing period. details.provenance names the charge, the best available source and the charging year. The Capacity Market is exempt from this gate by design: its line is always a disclaimed estimate.
422PLATFORM_KEY_NOT_REGISTEREDA platform-sourced request with no ElectraLink credential registered for your account. Register one via PUT /electralink-key, or supply consumption directly.
422PLATFORM_KEY_NOT_VERIFIEDA platform-sourced request where the registered credential has not been verified yet. Platform sourcing requires a verified credential; supply consumption directly until verification completes.
422PLATFORM_CONSUMPTION_UNAVAILABLENo consumption data is available for this supply from the platform source. details.reason names why. On the Profile Class 00 half-hourly route: NO_HH_DATA_NHH_UNMIGRATED (not yet migrated to half-hourly settlement), UNMETERED_SUPPLY, NO_HH_HISTORY (no half-hourly history on record for import settlement), or EXPORT_COVERAGE_SEPARATE (an export supply point). On the Profile Class 01-08 EAC route: NO_EAC_DATA, NO_EAC_HH_SETTLED (the supply is half-hourly settled, so no EAC is published for it), EAC_TPR_INCOMPLETE, or EAC_INTERNAL_INCONSISTENT. Supply consumption directly.
422PLATFORM_CONSUMPTION_PARTIAL_WINDOWThe billing period could not be fully covered by the retrievable half-hourly data. An umbrella code: details.reason is INSUFFICIENT_HISTORY (no usable history overlaps any of the window) or PARTIAL_WINDOW_COVERAGE (history exists but does not cover the whole window). Returned rather than pricing a partial window.
422PLATFORM_CONSUMPTION_NOT_AUTHORISEDThe registered credential is not proven to be entitled to this supply point. details.reason is ENTITLEMENT_UNPROVEN. The response is identical whether or not data exists behind the entitlement.
429RATE_LIMIT_EXCEEDEDThe shared per-minute limit or this endpoint's per-minute compute sub-limit (20 calculations per minute) was exceeded. details.retry_after_seconds says when to retry.
500CALCULATION_FAILEDThe calculation could not be completed. Safe to retry.
502CHARGE_CALCULATION_FAILEDA requested charge failed during computation (for example, a data gap in the published coefficients for the period). No partial total is returned; details.errors names each failed charge. Safe to retry; persistent failures are worth reporting.
502PLATFORM_SOURCE_ERRORThe platform consumption source could not be read (a transport or read failure on the platform side). Nothing from the source is echoed. Try again, or supply consumption directly.

Billing

A successful calculation costs 5 calls against your account's monthly quota, whatever the input format (a half-hourly calculation costs the same 5), reported in the X-Request-Cost response header. A platform-sourced success costs 7 calls (the 5 plus 2 for the sourcing). Every error, including every refusal above, costs nothing, and so does every call to /electralink-key.

Endpoint Reference

All endpoints are served from https://www.energycode.ai/api/v1 and require the X-Api-Key header. Query parameters shown as "optional" can be omitted; reference and rate list endpoints support limit and offset for pagination.

Rates

MethodEndpointDescriptionKey params
GET/rates/duosDUoS rates for a DNO regiondno_id (required), charging_year, tariff_category, llfc_code
GET/rates/tnuosTNUoS zonal and TDR ratescharging_year, zone_id, gsp_group, rate_type, voltage, band
GET/rates/bsuosBSUoS ratescharging_year, period, date
GET/rates/cclClimate Change Levy ratescharging_year
GET/rates/cfdCfD Levy ratescharging_year, quarter, date
GET/rates/capacity-marketCapacity Market published auction clearing prices per delivery yeardelivery_year
GET/rates/aahedcAAHEDC rates (p/kWh and £/MWh)charging_year, date
GET/rates/nuclear-rabNuclear RAB Levy rates (live from Dec 2025)charging_year, date
GET/rates/llfLine Loss Factors from Elexon ISDdno_id (required), charging_year (required), llfc_code
GET/rates/by-mpanFull applicable non-commodity rate card for a single MPAN, demand or generationmpan (required), charging_year, override_distributor_id, postcode (for IDNO MPANs), charges
GET/rates/by-mprnFull Gas LDZ transportation rate card for a single MPRNmprn (required), postcode (required), charging_year, charges

Reference Data

MethodEndpointDescriptionKey params
GET/reference/dnosList all 14 DNO regionsnone
GET/reference/llfcsLLFC-to-tariff-category mappings for a DNOdno_id (required), llfc_code, market_segment, is_import
GET/reference/mtcsMeter Timeswitch Classes (MDD)mtc_id, mtc_type, is_common_code
GET/reference/sscsStandard Settlement Configurations (MDD)ssc_code, ssc_type, is_active, include (tprs, clock_intervals)
GET/reference/tprsTime Pattern Regimes (MDD)tpr_id, tpr_type, include (clock_intervals)
GET/reference/profile-coefficientsD0018 profile coefficients (NHH, PC 01-04)profile_class (required), gsp_group (required), date (required), period_index

Calculations

MethodEndpointDescriptionKey params
POST/calculateCompute itemised network and policy charges for an MPAN (Beta)mpan (required), billing_period (required), consumption (required), charges_to_calculate (required), capacity_kva (required on Profile Class 05 to 08), max_demand_kva, kvarh, peak_demand_kw, triad_demand_kw, ssc, tnuos_band, tariff_category_code, ccl, override_distributor_id (host DNO 10-23), postcode (host DNO for an IDNO import MPAN)

Account

MethodEndpointDescriptionKey params
GET/usageYour own usage and current quota stateperiod (day/week/month/all), start_date, end_date

Looking Up DUoS Rates by LLFC

The 3-character LLFC code from an MPAN can be resolved to its tariff category(ies) in a single call. Pass llfc_code to /rates/duos and the matching rate tables come back directly, no separate mapping lookup needed. llfc_code takes precedence over tariff_category.

bash
curl -X GET "https://www.energycode.ai/api/v1/rates/duos?dno_id=10&charging_year=2025-26&llfc_code=001" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

MPANs are strings. Keep the leading zeros.

An MPAN Core begins with 0 (e.g. 008450831071094911008). Spreadsheets and JSON number types silently strip leading zeros, turning a valid 21-digit MPAN into a 19- or 20-digit value the API rejects with 400 UNEXPECTED_LENGTH, whose message names dropped leading zeros as the likely cause. A 6-10 digit value (a gas MPRN pasted by mistake) is rejected with 400 LIKELY_MPRN. Always send mpan as a quoted string, zero-padded to 21 (legacy) or 22 (MHHS) digits. The API does not auto-pad, substitute or truncate, because it cannot know how the value was altered. It does forgive presentation noise: surrounding or internal spaces, hyphens, zero-width characters and full-width digits are normalized away before validation, so 0084 5083 1071 0949 11008 and 0084-5083-1071-0949-11008 are both accepted.

Rate Card by MPAN

Send a single MPAN to /rates/by-mpan and it returns the full applicable non-commodity rate card for that supply point - raw published rates only.

Charges do not share a calendar, so data.requested_charging_year is an echo of your request used to select which rows to return, never the period of a returned rate. Each block carries its own basis (a plain-words period, e.g. "charging year (Apr–Mar)"), and each rate line carries its own period, a provenance label and a source reference. provenance is official_actual for a settled or final published figure, or official_forecast for an official forward figure (a NESO Draft/5YV tariff, an LCCC set-ahead Interim Levy Rate, a future Capacity Market auction result). Both are official data from the responsible body; a forecast is given and labelled as such. Our own derived estimates are never returned as a rate: where the only figure held for a year is an estimate, the block returns rates: null, provenance: null and an explanatory note, with published_through marking the coverage boundary.

For an ordinary DNO MPAN (distributor id 10-23) you send nothing but the MPAN. The endpoint decodes the distributor id, resolves the tariff category from the LLFC and returns the rate card directly:

bash
curl -X GET "https://www.energycode.ai/api/v1/rates/by-mpan?mpan=008450831071094911008&charging_year=2025-26" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

The response bundles every non-commodity charge that applies to the meter in one payload:

json
{
  "success": true,
  "data": {
    "mpan": {
      "distributor_id": 10,
      "dno_name": "Eastern Power Networks",
      "profile_class": "00",
      "gsp_group": "_A"
    },
    "requested_charging_year": "2026-27",
    "resolved_host_distributor_id": 10,
    "host_dno_resolution": "native",
    "charges_available": ["cdcm", "tnuos", "bsuos", "ccl", "cfd", "capacity_market", "aahedc", "nuclear_rab"],
    "charges_returned": ["cdcm", "tnuos", "bsuos", "ccl", "cfd", "capacity_market", "aahedc", "nuclear_rab"],
    "charges_requested": null,
    "cdcm": {
      "charging_year": "2026-27",
      "tariff_category": "LVS_SS_B3",
      "dno_name": "Eastern Power Networks",
      "basis": "charging year (Apr–Mar)",
      "provenance": "official_actual",
      "source_document": "EPN DUoS Charging Statement 2026-27",
      "tariff_category_source": "llfc",
      "published_through": "2026-27",
      "rates": {
        "red_rate_pkwh": 12.34,
        "amber_rate_pkwh": 5.67,
        "green_rate_pkwh": 1.23,
        "fixed_rate_pmpan_day": 45.67,
        "capacity_rate_pkva_day": 6.89,
        "exceeded_capacity_rate_pkva_day": 13.78,
        "reactive_rate_pkvarh": 0.42
      }
    },
    "tnuos": {
      "charging_year": "2026-27",
      "basis": "TNUoS charging year (Apr–Mar)",
      "locational": {
        "hh_tariff_gbp_kw": 30.12,
        "provenance": "official_actual",
        "source_document": "NESO TNUoS Tariffs 2026-27 (Final)"
      },
      "tdr_bands": [
        { "band_code": "LV2", "rate_gbp_day": 1.23, "provenance": "official_actual" }
      ]
    },
    "bsuos": {
      "requested_charging_year": "2026-27",
      "basis": "BSUoS fixed 6-month period (date-windowed; no charging year)",
      "published_through": "2027-03-31",
      "periods": [
        {
          "period_start": "2026-04-01",
          "period_end": "2026-09-30",
          "rate_pkwh": 0.412,
          "rate_type": "fixed",
          "provenance": "official_forecast"
        }
      ]
    },
    "policy": {
      "ccl": {
        "charging_year": "2026-27",
        "basis": "charging year (Apr–Mar)",
        "rate_pkwh": 0.775,
        "provenance": "official_actual",
        "source": "HMRC Climate Change Levy rates"
      },
      "cfd": {
        "requested_charging_year": "2026-27",
        "basis": "CfD calendar quarter (LCCC Interim Levy Rate, set-ahead)",
        "published_through": "2026-12-31",
        "quarters": [
          {
            "delivery_year": "2026-27",
            "quarter": "Q2",
            "period_start": "2026-04-01",
            "period_end": "2026-06-30",
            "interim_levy_rate_gbp_mwh": 10.26,
            "provenance": "official_forecast",
            "source": "LCCC CfD Interim Levy Rate"
          }
        ]
      },
      "capacity_market": {
        "requested_charging_year": "2026-27",
        "basis": "Capacity Market delivery year (Oct–Sep), published auction clearing prices",
        "published_through": "2028-09-30",
        "delivery_years": [
          {
            "delivery_year": "2026-27",
            "period_start": "2026-10-01",
            "period_end": "2027-09-30",
            "t4_auction_price_gbp_kw": 63.0,
            "t3_auction_price_gbp_kw": null,
            "t1_auction_price_gbp_kw": null,
            "settlement_costs_levy_gbp_mwh": 0.05,
            "provenance": "official_forecast",
            "source": "NESO EMR Capacity Market auction results"
          }
        ]
      },
      "aahedc": { "...": "..." },
      "nuclear_rab": { "...": "..." }
    }
  }
}
  • cdcm.rates carries the DUoS unit rates (red / amber / green), the fixed charge, and the capacity, exceeded-capacity and reactive rates - the per-kVA and per-kVArh figures, not just totals.
  • tnuos is the transmission network charge and bsuos is NESO's balancing charge, returned as separate top-level blocks; policy holds CCL, CfD, Capacity Market, AAHEDC and Nuclear RAB. Together they are the complete non-commodity stack for the supply point.
  • policy.capacity_market returns published auction clearing prices (£/kW/year) per delivery year (Oct–Sep). A charging-year window spans two delivery years, so both are returned. policy.cfd returns one entry per calendar quarter, each carrying its own delivery_year and period.
  • host_dno_resolution is native for an ordinary DNO MPAN - the MPAN's own distributor id was used, so no postcode is needed.
  • An EHV (EDCM) site returns an edcm block of site-specific import/export rates (with a direction indicator) in place of cdcm.

One MPAN in, the whole rate card out

/rates/by-mpan is a pure rates lookup: it returns published rates and never touches consumption. The only case where an MPAN alone is not enough is an IDNO MPAN (distributor id 24+), which needs a postcode - see below.

Selecting individual charges

The full card is the default. Add the charges query parameter with a comma-separated list to return only the blocks you name. The endpoint runs the same decode and resolution either way, and each returned block is identical to its full-card form.

  • distribution (or duos) returns the DUoS block for the meter: cdcm, or edcm for an EHV site.
  • transmission (or tnuos) returns TNUoS. BSUoS is separate: request it with bsuos.
  • policy returns all five policy charges, or name one: ccl, cfd, capacity_market, aahedc, nuclear_rab.

Every response carries three lists:

  • charges_available - every charge that applies to this MPAN.
  • charges_returned - the charges in this response.
  • charges_requested - the charges you asked for, or null for the full card.
bash
curl -X GET "https://www.energycode.ai/api/v1/rates/by-mpan?mpan=008450831071094911008&charging_year=2025-26&charges=duos,cfd" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"
json
{
  "success": true,
  "data": {
    "mpan": { "distributor_id": 10, "dno_name": "Eastern Power Networks", "profile_class": "00", "gsp_group": "_A" },
    "requested_charging_year": "2026-27",
    "resolved_host_distributor_id": 10,
    "host_dno_resolution": "native",
    "charges_available": ["cdcm", "tnuos", "bsuos", "ccl", "cfd", "capacity_market", "aahedc", "nuclear_rab"],
    "charges_returned": ["cdcm", "cfd"],
    "charges_requested": ["cdcm", "cfd"],
    "cdcm": { "...": "..." },
    "policy": {
      "cfd": { "...": "..." }
    }
  }
}

An unknown token, an empty value, or more than 50 tokens returns 400 VALIDATION_ERROR with the accepted list. The MPAN and generation checks run first, so a generation MPAN or a gas MPRN returns its own error whatever you pass in charges.

Generation and export MPANs

Generation and export is auto-detected from the LLFC (an export LLFC on a Profile Class 00 supply point). You send nothing extra: the response carries a connection_type of generation and returns the charges that apply to an exporter.

bash
curl -X GET "https://www.energycode.ai/api/v1/rates/by-mpan?mpan=008457911000107350005&charging_year=2026-27" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

Generation rates are shown signed: a credit is negative (a payment to the generator), a charge positive.

json
{
  "success": true,
  "data": {
    "mpan": {
      "mpan": "008457911000107350005",
      "formatted": "00 845 791 10 00107350 005",
      "format": "legacy",
      "distributor_id": 10,
      "dno_name": "Eastern Power Networks",
      "llfc_code": "791",
      "profile_class": "00",
      "gsp_group": "_A"
    },
    "connection_type": "generation",
    "requested_charging_year": "2026-27",
    "resolved_host_distributor_id": 10,
    "host_dno_resolution": "native",
    "charges_available": ["cdcm", "tnuos"],
    "charges_returned": ["cdcm", "tnuos"],
    "charges_requested": null,
    "cdcm": {
      "charging_year": "2026-27",
      "tariff_category": "HV_GEN_SS",
      "tariff_name": "HV Generation Site Specific",
      "dno_name": "Eastern Power Networks",
      "basis": "charging year (Apr–Mar)",
      "provenance": "official_actual",
      "source_document": "EPN DUoS Charging Statement 2026-27",
      "tariff_category_source": "llfc",
      "published_through": "2026-27",
      "rates": {
        "red_rate_pkwh": -5.466,
        "amber_rate_pkwh": -0.559,
        "green_rate_pkwh": -0.052,
        "fixed_rate_pmpan_day": 12.88,
        "capacity_rate_pkva_day": 0.0,
        "exceeded_capacity_rate_pkva_day": 0.0,
        "reactive_rate_pkvarh": 0.277
      }
    },
    "tnuos": {
      "charging_year": "2026-27",
      "basis": "TNUoS charging year (Apr–Mar)",
      "wider_generation": {
        "basis": "Wider generation TNUoS (£/kW), published per NESO generation zone. All published zones are returned raw and signed (southern zones are typically NEGATIVE = a credit; components may be zero). The applicable zone cannot be resolved from the MPAN — the caller applies its generator classification and NESO generation zone to select the row. Each row carries its own provenance from its NESO publication (Final = official_actual; Draft/Forecast = official_forecast).",
        "zone_count": 27,
        "zones": [
          {
            "zone_id": 1,
            "system_peak_gbp_kw": 3.628060,
            "shared_year_round_gbp_kw": 26.532717,
            "not_shared_year_round_gbp_kw": 17.674559,
            "residual_gbp_kw": -2.476760,
            "small_gen_discount_gbp_kw": 0.0,
            "effective_from": "2026-04-01",
            "effective_to": "2027-03-31",
            "provenance": "official_actual",
            "source_document": "NESO TNUoS Tariffs 2026-27 (Final)"
          }
          // ... all 27 generation zones
        ]
      },
      "eet_credit": {
        "basis": "Eligibility for Embedded Export Tariff (EET) credit, resolved from the site DEMAND zone (14-zone table). Published as a positive £/kW; shown here as credit_gbp_kw (negative to the customer, following the published sign convention). A published zero is a real published zero.",
        "demand_zone_id": 9,
        "demand_zone_name": "Eastern",
        "gsp_group": "_A",
        "published_tariff_gbp_kw": 3.206484,
        "credit_gbp_kw": -3.206484,
        "effective_from": "2026-04-01",
        "effective_to": "2027-03-31",
        "source_document": "NESO TNUoS Tariffs 2026-27 (Final)",
        "provenance": "official_actual"
      }
    },
    "bsuos": {
      "applies": false,
      "rate": null,
      "basis": "Borne by demand only; generators exempt since 1 April 2023 (CMP308)."
    },
    "policy": {
      "ccl": { "applies": false, "rate": null, "basis": "Charged on energy consumed at non-domestic premises (Finance Act 2000 Sch 6); none on export." },
      "cfd": { "applies": false, "rate": null, "basis": "Per-MWh obligation on suppliers' supply volume (CfD ESO Regs 2014); none on generation/export." },
      "capacity_market": { "applies": false, "rate": null, "basis": "Supplier demand charge in peak windows (Electricity Capacity Regs 2014); none on generation/export." },
      "aahedc": { "applies": false, "rate": null, "basis": "No AAHEDC applies to generation/export — NESO levies AAHEDC on supplier demand only, with no published generation rate." },
      "nuclear_rab": { "applies": false, "rate": null, "basis": "Per-MWh levy on electricity suppliers (Nuclear Energy (Financing) Act 2022); none on generation/export." },
      "ro": { "applies": false, "rate": null, "basis": "Supplier per-MWh obligation (RO Order 2015); none on export. (Generator earns ROCs — scheme revenue, not a network rate.)" },
      "fit": { "applies": false, "rate": null, "basis": "Levy recovered from suppliers (Energy Act 2008 s.41); none on generation. (Accredited legacy gen receives FIT payments — scheme revenue, not a network rate.)" }
    },
    "dgnur": {
      "applies": true,
      "rate": null,
      "basis": "DGNUR (Distributed Generation Network Use rebate, DCUSA Sch 16): a credit under a site-specific connection agreement, with no published lookup rate. rate is null; resolve the amount from the site agreement."
    }
  }
}
  • cdcm.rates uses the same field names as the demand card (red_rate_pkwh, amber_rate_pkwh, green_rate_pkwh, fixed_rate_pmpan_day, capacity_rate_pkva_day, exceeded_capacity_rate_pkva_day, reactive_rate_pkvarh), with the unit rates typically negative credits.
  • tnuos.wider_generation is an object of { basis, zone_count, zones }. Each zone in zones carries five signed components: system_peak_gbp_kw, shared_year_round_gbp_kw, not_shared_year_round_gbp_kw, residual_gbp_kw and small_gen_discount_gbp_kw.
  • tnuos.eet_credit carries published_tariff_gbp_kw (the positive published magnitude) and credit_gbp_kw (its negative, the credit to the customer), resolved from the site demand zone.
  • bsuos is a top-level no-rate block (applies: false, rate: null) with a basis recording that generators are exempt since 1 April 2023 (CMP308).
  • policy carries a no-rate block for every demand-only levy: ccl, cfd, capacity_market, aahedc, nuclear_rab, ro and fit, each with applies: false, rate: null and a statutory basis. A top-level dgnur block (Distributed Generation Network Use rebate, DCUSA Sch 16) is present with applies: true and rate: null.
  • A charge that does not apply always carries an accurate basis; it is never silently dropped.

Rate Card by MPRN

Send a single gas MPRN to /rates/by-mprn and it returns the full Gas LDZ transportation rate card for that supply point - raw published charges only. This endpoint requires a key with the gas scope.

A gas MPRN identifies the meter point but does not itself carry a Local Distribution Zone. The LDZ is geographic and is resolved from the site postcode, so postcode is required. Each line carries its own period (the gas charging year runs 1 April to 31 March), a provenance label, a source_document reference and an applicability label. Values may be negative (for example a SOLR credit).

bash
curl -X GET "https://www.energycode.ai/api/v1/rates/by-mprn?mprn=1234567810&postcode=M1%201AA&charging_year=2026-27" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

The response resolves the zone identity and returns the six canonical UNC-governed components:

json
{
  "success": true,
  "data": {
    "mprn": {
      "mprn": "1234567810",
      "ldz": "NW",
      "exit_zone": "NW1",
      "gdn": "CAD"
    },
    "requested_charging_year": "2026-27",
    "basis": "Gas LDZ-transportation rate card. Each component's period is the gas charging year (1 April–31 March). Every rate line is raw published data with its own provenance and source document.",
    "charges_available": ["zca", "zco", "cca", "ecn", "cfi", "solr"],
    "charges_requested": null,
    "charges_returned": ["zca", "zco", "cca", "ecn", "cfi", "solr"],
    "transportation": {
      "zca": {
        "name": "LDZ Capacity (ZCA)",
        "applicability": "Applies to all sites (incl. CSEP). Full band table shown; no band selected (no AQ supplied).",
        "charging_year": "2026-27",
        "basis": "gas charging year (1 Apr–31 Mar)",
        "provenance": "official_actual",
        "source_document": "38509 - Charging Statement April 2026 North West.pdf",
        "rate_unit": "p_pkwh_day",
        "bands": {
          "small_band_rate": 0.3335,
          "medium_band_rate": 0.2784,
          "large_coefficient": 2.1945,
          "large_exponent": -0.2483,
          "large_minimum_rate": 0.0309
        }
      },
      "zco": {
        "name": "LDZ Commodity (ZCO)",
        "applicability": "Applies to all sites (incl. CSEP). Full band table shown; no band selected (no AQ supplied).",
        "charging_year": "2026-27",
        "basis": "gas charging year (1 Apr–31 Mar)",
        "provenance": "official_actual",
        "source_document": "38509 - Charging Statement April 2026 North West.pdf",
        "rate_unit": "p_kwh",
        "bands": {
          "small_band_rate": 0.0567,
          "medium_band_rate": 0.0477,
          "large_coefficient": 0.416,
          "large_exponent": -0.2586,
          "large_minimum_rate": 0.0049
        }
      },
      "cca": {
        "name": "Customer Capacity (CCA)",
        "applicability": "Applies to all direct-connect sites. A CSEP site carries only ZCA + ZCO + ECN. Full band table shown.",
        "charging_year": "2026-27",
        "basis": "gas charging year (1 Apr–31 Mar)",
        "provenance": "official_actual",
        "source_document": "38509 - Charging Statement April 2026 North West.pdf",
        "rate_unit": "p_pkwh_day",
        "bands": {
          "small_band_rate": 0.1574,
          "medium_band_rate": 0.0048,
          "large_coefficient": 0.1087,
          "large_exponent": -0.21,
          "large_minimum_rate": null
        }
      },
      "ecn": {
        "name": "Exit Capacity (ECN)",
        "applicability": "Applies to all sites at the exit zone level. Resolved per exit zone.",
        "charging_year": "2026-27",
        "basis": "gas charging year (1 Apr–31 Mar)",
        "provenance": "official_actual",
        "source_document": "38509 - Charging Statement April 2026 North West.pdf",
        "exit_zone": "NW1",
        "rate_p_pkwh_day": 0.0391,
        "indicative": false
      },
      "cfi": {
        "name": "Customer Fixed (CFI)",
        "applicability": "Applies to medium AQ band sites only (73,200–732,000 kWh/year); no CFI for small or large band. Both read-frequency rows shown.",
        "charging_year": "2026-27",
        "basis": "gas charging year (1 Apr–31 Mar)",
        "provenance": "official_actual",
        "source_document": "38509 - Charging Statement April 2026 North West.pdf",
        "rows": [
          { "read_frequency": "non_monthly", "rate_p_day": 44.8078 },
          { "read_frequency": "monthly",     "rate_p_day": 47.7111 }
        ]
      },
      "solr": {
        "name": "Supplier of Last Resort (SOLR)",
        "applicability": "Applies to domestic customers only. Values may be negative (a credit); the published sign is preserved. Both customer-type rows shown.",
        "charging_year": "2026-27",
        "basis": "gas charging year (1 Apr–31 Mar)",
        "provenance": "official_actual",
        "source_document": "38509 - Charging Statement April 2026 North West.pdf",
        "rows": [
          { "customer_type": "domestic", "rate_p_pkwh_day": -0.0047 },
          { "customer_type": "ic",       "rate_p_pkwh_day": 0 }
        ]
      }
    }
  }
}
  • The mprn identity resolves { mprn, ldz, exit_zone, gdn } for the supply point. gdn is the Gas Distribution Network code (CAD, NGN, SGN or WWU).
  • zca, zco and cca each carry a rate_unit and a bands object of small_band_rate, medium_band_rate, large_coefficient, large_exponent and large_minimum_rate. No band is selected: the request carries no annual quantity (AQ), so the small and medium rates are flat and the large band is the raw power function large_coefficient × SOQ^large_exponent, returned raw, never evaluated.
  • ecn (NTS exit) is a single block with an exit_zone, a rate_p_pkwh_day and an indicative flag. cfi returns both rows (read_frequency non_monthly and monthly, each with a rate_p_day), and solr returns both rows (customer_type domestic and ic, each with a rate_p_pkwh_day).

postcode is required for a gas MPRN

A missing postcode returns 400 GAS_POSTCODE_REQUIRED. If the outward code spans more than one gas network or LDZ, the request returns 400 GAS_POSTCODE_AMBIGUOUS: supply the full postcode to disambiguate. A valid MPRN and postcode that resolve to no zone on record return 422 LDZ_UNRESOLVABLE, and an MPRN that is not a valid 6-10 digit value returns 400 INVALID_MPRN. An MPRN served by an Independent Gas Transporter surfaces an igt_note.

IDNO MPANs

An IDNO (Independent DNO) MPAN carries a distributor id of 24 or above. That id identifies the IDNO, not the host DNO whose DUoS and network rates actually apply. The host DNO is geographic: it is determined by the site's postcode, and the MPAN alone cannot yield it.

So when you call /rates/by-mpan with an IDNO MPAN, supply the site postcode and the endpoint resolves the host DNO for you, then returns the host DNO's rate card:

bash
curl -X GET "https://www.energycode.ai/api/v1/rates/by-mpan?mpan=008450922422524534007&postcode=SW1A%201AA&charging_year=2025-26" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

The response tells you which host DNO was used and how it was resolved:

json
{
  "success": true,
  "data": {
    "mpan": { "distributor_id": 24, "dno_name": "IDNO 24", "...": "..." },
    "requested_charging_year": "2026-27",
    "resolved_host_distributor_id": 12,
    "host_dno_resolution": "postcode",
    "cdcm": { "...": "..." }
  }
}
  • resolved_host_distributor_id is the DNO id (10-23) actually used to resolve the DUoS/DNO rates - the host DNO, not the IDNO id from the MPAN.
  • host_dno_resolution is postcode when the host DNO came from the postcode, override when you passed override_distributor_id, native for an ordinary DNO MPAN, or edcm for an EHV site.

If you already know the host DNO, you can skip the postcode and pass it directly with override_distributor_id (10-23). If you supply both, the override wins and a warnings entry flags any disagreement.

A postcode on an ordinary DNO MPAN (distributor id 10-23) is ignored. EDCM/EHV IDNO sites resolve their host DNO automatically from the site record - you do not need a postcode for them.

IDNO_HOST_REQUIRED

An IDNO MPAN (distributor 24+) sent to /rates/by-mpan with no postcode or override_distributor_id is rejected with 400 IDNO_HOST_REQUIRED. Send the site postcode, or override_distributor_id (host DNO 10-23).

Self-Service Usage and Quota Checks

Any key can inspect its own consumption and remaining quota via GET /usage, no admin grant required.

bash
curl -X GET "https://www.energycode.ai/api/v1/usage?period=month" \
  -H "X-Api-Key: YOUR_API_KEY_HERE"

Response (abridged):

json
{
  "success": true,
  "data": {
    "customer_id": "123e4567-e89b-12d3-a456-426614174000",
    "period": "month",
    "start_date": "2026-07-01",
    "end_date": "2026-07-31",
    "summary": {
      "total_requests": 1240,
      "successful_requests": 1198,
      "failed_requests": 42,
      "unique_endpoints": 7,
      "avg_latency_ms": 58
    },
    "quota": {
      "calls_this_month": 1240,
      "calls_per_month": 15000,
      "overage_calls": 0,
      "billing_mode": "capped",
      "requests_this_month": 812,
      "reference_rows_today": 3200,
      "reference_rows_per_day": 25000,
      "requests_per_minute": 120
    }
  }
}

Interactive Explorer and Machine-Readable Spec

Both are enabled per account: once documentation access is switched on for your account, sign in and they open directly.

Error Handling

All API responses follow a consistent format for errors.

Error Response Format

json
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description"
  }
}

Common Error Codes

HTTP StatusError CodeDescriptionAction
400VALIDATION_ERRORInvalid request parametersCheck request format and parameter values
400LIKELY_MPRNmpan is a 6-10 digit value, the shape of a gas MPRN, not an electricity MPANSend a full 21/22-digit electricity MPAN or a 13-digit MPAN Core; gas MPRN data is not served here
400UNEXPECTED_LENGTHmpan is a 19, 20 or 23 digit near-miss around the 21/22 full length (19 or 20 is usually dropped leading zeros; 23 is usually duplicated or appended characters)Resend the MPAN as a quoted, zero-padded string (the API never auto-pads)
400INVALID_MPANA full-length MPAN fails format or check-digit validationCheck the MPAN characters and check digit
400IDNO_HOST_REQUIREDIDNO MPAN (distributor 24+) with no postcode or override_distributor_idSend the site postcode, or override_distributor_id (host DNO 10-23)
401UNAUTHORIZEDMissing or invalid API keyVerify X-Api-Key header is set correctly
401INVALID_API_KEYAPI key format is incorrectCheck key format: ec_(live|test)_[64 hex chars]
403SCOPE_DENIEDEndpoint dataset not in your plan scopesUpgrade your plan or use a key with the right scope
404NOT_FOUNDResource not foundVerify DNO ID, charging year, tariff code exist
429RATE_LIMIT_EXCEEDEDPer-minute rate limit exceededWait for Retry-After seconds before retrying
429QUOTA_EXCEEDEDMonthly call quota or daily reference-row quota reached (capped accounts only)Wait for the quota window to reset, or contact us to raise your allowance
429TOO_MANY_AUTH_ATTEMPTSToo many failed auth attemptsWait 5 minutes before retrying
500INTERNAL_ERRORServer errorContact support if issue persists

Example: Handling Validation Errors

json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid dno_id. Must be one of: 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23"
  }
}

Retry Strategy

For transient errors (rate limits, server errors), implement exponential backoff:

Python Example:

python
import time
import requests

def make_request_with_retry(url, headers, max_retries=3):
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)

        if response.status_code == 200:
            return response.json()

        if response.status_code == 429:
            retry_after = int(response.headers.get('Retry-After', 60))
            print(f"Rate limited. Waiting {retry_after} seconds...")
            time.sleep(retry_after)
            continue

        if response.status_code >= 500:
            wait_time = 2 ** attempt  # Exponential backoff: 1s, 2s, 4s
            print(f"Server error. Retrying in {wait_time}s...")
            time.sleep(wait_time)
            continue

        # Non-retryable error
        return response.json()

    raise Exception("Max retries exceeded")

Code Examples

Python

Install dependencies:

bash
pip install requests

Example: Fetch DNO regions and DUoS rates

python
import requests
import os

API_KEY = os.getenv("ENERGYCODE_API_KEY")
BASE_URL = "https://www.energycode.ai/api/v1"

headers = {
    "X-Api-Key": API_KEY,
    "Content-Type": "application/json"
}

# 1. List all DNO regions
def get_dno_regions():
    response = requests.get(f"{BASE_URL}/reference/dnos", headers=headers)
    response.raise_for_status()
    return response.json()

# 2. Get DUoS rates for a specific DNO
def get_duos_rates(dno_id, charging_year="2025-26"):
    params = {
        "dno_id": dno_id,
        "charging_year": charging_year
    }
    response = requests.get(f"{BASE_URL}/rates/duos", headers=headers, params=params)
    response.raise_for_status()
    return response.json()

# Usage
if __name__ == "__main__":
    # Fetch DNO regions
    dnos = get_dno_regions()
    print(f"Found {len(dnos['data'])} DNO regions")

    # Get rates for Eastern Power Networks (DNO 10)
    rates = get_duos_rates(dno_id=10)
    print(f"Retrieved {len(rates['data']['rates'])} tariff categories")

JavaScript / TypeScript (Node.js)

Install dependencies:

bash
npm install axios

Example:

typescript
import axios, { AxiosInstance } from 'axios';

const API_KEY = process.env.ENERGYCODE_API_KEY;
const BASE_URL = 'https://www.energycode.ai/api/v1';

const client: AxiosInstance = axios.create({
  baseURL: BASE_URL,
  headers: {
    'X-Api-Key': API_KEY,
    'Content-Type': 'application/json',
  },
});

// 1. List all DNO regions
async function getDnoRegions() {
  const response = await client.get('/reference/dnos');
  return response.data;
}

// 2. Get DUoS rates for a specific DNO
async function getDuosRates(dnoId: number, chargingYear: string = '2025-26') {
  const response = await client.get('/rates/duos', {
    params: { dno_id: dnoId, charging_year: chargingYear },
  });
  return response.data;
}

// Usage
async function main() {
  try {
    // Fetch DNO regions
    const dnos = await getDnoRegions();
    console.log(`Found ${dnos.data.length} DNO regions`);

    // Get rates for Eastern Power Networks (DNO 10)
    const rates = await getDuosRates(10);
    console.log(`Retrieved ${rates.data.rates.length} tariff categories`);
  } catch (error) {
    if (axios.isAxiosError(error)) {
      console.error('API Error:', error.response?.data);
    } else {
      console.error('Unexpected error:', error);
    }
  }
}

main();

cURL (Shell Script)

bash
#!/bin/bash

API_KEY="YOUR_API_KEY_HERE"
BASE_URL="https://www.energycode.ai/api/v1"

# Get all DNO regions
curl -X GET "$BASE_URL/reference/dnos" \
  -H "X-Api-Key: $API_KEY" \
  | jq '.'

# Get DUoS rates for DNO 10
curl -X GET "$BASE_URL/rates/duos?dno_id=10&charging_year=2025-26" \
  -H "X-Api-Key: $API_KEY" \
  | jq '.'

Best Practices

1. Caching Strategies

Rate data changes annually (per charging year). Cache aggressively:

python
from functools import lru_cache
import requests

@lru_cache(maxsize=100)
def get_duos_rates_cached(dno_id: int, charging_year: str):
    """Cache rates by DNO and charging year (rates don't change within a year)"""
    response = requests.get(
        f"{BASE_URL}/rates/duos",
        headers=headers,
        params={"dno_id": dno_id, "charging_year": charging_year}
    )
    return response.json()

Cache invalidation:

  • Rates change on 1 April each year (start of new charging year)
  • Cache can be invalidated annually or when a new charging year is published

2. Batch Requests Efficiently

Instead of making 14 separate requests for all DNOs, fetch reference data once and iterate:

python
# GOOD: One request for all DNOs
dnos = get_dno_regions()
for dno in dnos['data']:
    rates = get_duos_rates(dno['id'])
    # Process rates...

3. Monitor Rate Limits Proactively

Check X-RateLimit-Remaining and slow down before hitting the limit:

python
def make_request(url, headers):
    response = requests.get(url, headers=headers)

    remaining = int(response.headers.get('X-RateLimit-Remaining', 100))
    if remaining < 10:
        print("Approaching rate limit, slowing down...")
        time.sleep(2)

    return response.json()

4. Validate Input Before Sending

Reduce failed requests by validating inputs locally:

python
VALID_DNO_IDS = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]

def validate_dno_id(dno_id: int):
    if dno_id not in VALID_DNO_IDS:
        raise ValueError(f"Invalid DNO ID: {dno_id}")

5. Handle Errors Gracefully

Always check the success field before accessing data:

python
response = requests.get(url, headers=headers)
data = response.json()

if not data.get('success'):
    error = data.get('error', {})
    raise Exception(f"API Error [{error.get('code')}]: {error.get('message')}")

# Safe to access data
result = data['data']

6. Use Test Keys for Development

Keep test and live keys separate:

python
# Use environment-specific keys
API_KEY = os.getenv("ENERGYCODE_API_KEY_TEST")  # Development
# API_KEY = os.getenv("ENERGYCODE_API_KEY_LIVE")  # Production

Getting Help

API Reference Documentation

Full API reference with all endpoints, parameters, and response schemas, enabled per account:

Support Channels

ChannelPurposeResponse Time
Email[email protected]1-2 business days
Technical Issues[email protected]24 hours
Account/Billing[email protected]1 business day

Before Contacting Support

Please include:

  1. API key prefix (first 12 characters, e.g., ec_live_abc1...)
  2. Request ID from X-Request-ID response header
  3. Timestamp of the issue
  4. Complete request (cURL format preferred)
  5. Complete response (including status code and headers)
  6. Expected behavior vs. actual behavior

Appendix: Reference Data

Valid DNO IDs

IDNameOperator Group
10Eastern Power NetworksUKPN
11East MidlandsNGED
12London Power NetworksUKPN
13SP ManwebSPEN
14West MidlandsNGED
15Northern Powergrid (Northeast)NPg
16Electricity North WestENWL
17Scottish Hydro (SHEPD)SSEN
18SP DistributionSPEN
19South Eastern Power NetworksUKPN
20Southern Electric (SEPD)SSEN
21South WalesNGED
22South WestNGED
23Northern Powergrid (Yorkshire)NPg

Charging Year Format

Charging years run from 1 April to 31 March and are formatted as YYYY-YY:

  • 2025-26 = 1 April 2025 to 31 March 2026
  • 2026-27 = 1 April 2026 to 31 March 2027

Common Tariff Categories (CDCM)

CodeDescription
DOM_AGGDomestic Aggregated
ND_AGG_B1Non-Domestic Aggregated, Band 1
ND_AGG_B2Non-Domestic Aggregated, Band 2
ND_AGG_B3Non-Domestic Aggregated, Band 3
ND_AGG_B4Non-Domestic Aggregated, Band 4
LV_SS_B1LV Site Specific, Band 1
HV_SS_B1HV Site Specific, Band 1
LV_GEN_AGGLV Generation Aggregated

This is a representative subset. The tariff_category code is returned on every DUoS rate row, and the full CDCM category reference is available on request - email [email protected].

Last Updated: 2026-07-09 · API Version: v1 · Status: Stable