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.
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:
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:
curl -X GET "https://www.energycode.ai/api/v1/reference/dnos" \
-H "X-Api-Key: YOUR_API_KEY_HERE"Expected Response:
{
"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:
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:
{
"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:
-H "X-Api-Key: ec_live_abc123..."API Key Types
| Key Type | Format | Purpose |
|---|---|---|
| Live | ec_live_* | Production use with real data |
| Test | ec_test_* | Testing and development (same data, separate quota) |
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:
| Scope | Datasets |
|---|---|
duos | Red, amber and green unit rates, fixed, capacity, exceeded-capacity and reactive charges, Line Loss Factors, DNO regions, LLFC mappings |
tnuos | Locational (zonal) demand tariffs, TDR fixed-band charges, embedded export credits |
balancing | BSUoS fixed six-monthly tariffs |
policy | CCL, CfD, Capacity Market, AAHEDC, Nuclear RAB rates |
gas | Gas LDZ transportation rate card by MPRN (ZCA, ZCO, CCA, ECN, CFI, SOLR) |
metering | SSC, TPR and MTC meter-configuration reference, D0018 profile coefficients |
calculate | The POST /calculate compute endpoint (itemised computed charges) |
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)
/usageis always accessible, regardless of scopes.- A request to a dataset outside your key's scopes returns
403with error codeSCOPE_DENIED. Email [email protected] to upgrade your plan.
Security Best Practices
- Never expose API keys in client-side code (JavaScript, mobile apps)
- Use environment variables to store keys securely
- Rotate keys periodically (contact support to generate new keys)
- Use test keys for development - keep live keys for production only
- 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:
- Per-minute rate limit - short-burst throttle (requests per minute)
- Monthly call quota - billable calls consumed per calendar month
- 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.
| Response | Cost |
|---|---|
| Any successful (2xx) rates or reference request | 1 call |
A successful (2xx) POST /calculate computation | 5 calls |
A successful (2xx) platform-sourced POST /calculate computation | 7 calls |
/usage and /electralink-key key management | 0 |
| 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:
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| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per minute |
X-RateLimit-Remaining | Remaining requests in the current per-minute window |
X-RateLimit-Reset | Unix timestamp when the per-minute window resets |
X-Request-Cost | Billable calls this request consumed (1 for most endpoints, 5 for a successful POST /calculate, 0 on errors) |
X-Quota-Limit | Monthly call allowance for your account |
X-Quota-Remaining | Calls remaining in the current calendar month |
X-Quota-Mode | Billing mode for your account: capped or uncapped |
X-Overage-Calls | Calls 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:
{
"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:
{
"success": false,
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Monthly call allowance reached for your account."
}
}Response Headers on 429:
Retry-After: 42The 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
- Implement exponential backoff - wait longer between retries
- Cache responses - store frequently accessed data locally
- Batch requests - fetch data in bulk rather than making many small requests
- Monitor
X-RateLimit-Remaining- proactively slow down before hitting the limit - 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:
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:
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
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
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-01The 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_PUBLISHEDnaming 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
noteandsummary.consumption_basisrecords the basis machine-readably. Rate provenance and quantity basis are separate axes: a modelled quantity never downgrades the rate'spublishedsource, 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 returns422 PLATFORM_CONSUMPTION_PARTIAL_WINDOWrather than pricing a partial window, and every priced result discloses its settlement provenance insummary.consumption_basis.platform. - Errors cost nothing. Every refusal bills 0 calls (
X-Request-Cost: 0).
Request reference
| Field | Required | Type | Purpose |
|---|---|---|---|
mpan | yes | string | 21-digit legacy or 22-digit MHHS MPAN (quoted string, leading zeros kept) |
billing_period | yes | object | start_date and end_date, ISO YYYY-MM-DD |
consumption | unless platform-sourced | object | Exactly one input format: total, day_night, rag or half_hourly (see below). Required unless consumption_source: "platform" sources it for you |
consumption_source | no | enum | "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_calculate | yes | object | Boolean flags for the charges to compute; at least one true |
capacity_kva | on PC 05 to 08 | number | The agreed Maximum Import Capacity (kVA). Required on Profile Class 05 to 08; optional on a non-domestic Profile Class 00; rejected elsewhere |
max_demand_kva | no | number | Measured maximum demand (kVA) for the exceeded-capacity charge. Profile Class 05 to 08 only |
kvarh | no | number | Measured reactive energy (kVArh) for the reactive-power charge. Profile Class 05 to 08 only |
peak_demand_kw | no | number | Winter peak demand (kW) for the Capacity Market estimate. Profile Class 05 to 08, and an override on Profile Class 00 |
triad_demand_kw | no | number | Customer-asserted Triad-average demand (kW) for TNUoS locational. Profile Class 00 only |
ssc | no | string | 4-character Standard Settlement Configuration id, e.g. "0246" |
tnuos_band | no | enum | TNUoS TDR residual band: "domestic" (Profile Class 01/02) or 1 to 4 (Profile Class 03 to 08) |
tariff_category_code | no | string | Asserts the DUoS tariff category, e.g. "ND_AGG_B1" or "LV_SS_B1" |
ccl | no | object | CCL declarations: cca_holder, exempt, domestic_premises (a Profile Class 00 override; domesticity otherwise derives from the MPAN's tariff category) |
override_distributor_id | no | number | Overrides 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.
| Format | You send | Applies to |
|---|---|---|
half_hourly | per-day settlement-period kWh | Profile Class 00: the metered path, and the highest-fidelity input |
total | total_kwh | Profile Class 01 to 08: one scalar, the default recommendation |
day_night | day_kwh, night_kwh | The Economy 7 classes (02 and 04): register reads |
rag | red_kwh, amber_kwh, green_kwh | Every 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:
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:
{
"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
sourcestayspublished. - 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_quartersreconciles 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
BetaSend an MPAN and let the API source the supply's consumption for you, instead of posting consumption yourself.
- 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. - Call
POST /calculatewithconsumption_sourceset to "platform" and the MPAN. The API retrieves the supply's own settlement data and prices every network, transmission and policy charge from it.
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:
{
"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 Status | Error Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Unknown 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. |
| 400 | INVALID_MPAN | A 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). |
| 400 | HOST_DNO_CONFLICT | An 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. |
| 400 | NIGHT_SHARE_REQUIRES_SSC | A 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. |
| 403 | FORBIDDEN | The 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. |
| 405 | METHOD_NOT_ALLOWED | The endpoint accepts POST only. |
| 413 | REQUEST_TOO_LARGE | Request body exceeds the 1.5MB cap (sized for well over a year of half-hourly data). |
| 415 | UNSUPPORTED_CONTENT_ENCODING | The request carried a Content-Encoding (compressed body). Send raw JSON. |
| 422 | PROFILE_CLASS_NOT_YET_SUPPORTED | The supply's profile class is outside the supported Profile Class 00 to 08 set. |
| 422 | CAPACITY_REQUIRED | A 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. |
| 422 | CCL_DOMESTICITY_REQUIRED | A 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. |
| 422 | CHARGE_NOT_YET_SUPPORTED | A 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. |
| 422 | CHARGE_NOT_COMPUTABLE | A requested charge cannot be computed for this supply and period. details.charges names them. Returned rather than a partial total. |
| 422 | CONSUMPTION_FORMAT_NOT_YET_SUPPORTED | The 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. |
| 422 | METER_CONFIG_REQUIRED | A 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. |
| 422 | METER_CONFIG_UNSOURCEABLE | A 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. |
| 422 | RESIDUAL_BAND_UNRESOLVED | The 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. |
| 422 | GENERATION_NOT_YET_SUPPORTED | Generation or export MPAN. Generation and export are not currently supported by this endpoint. |
| 422 | IDNO_HOST_REQUIRED | IDNO 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. |
| 422 | POSTCODE_HOST_NOT_FOUND | A 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. |
| 422 | EDCM_NOT_YET_SUPPORTED | EDCM (EHV site-specific) MPAN. |
| 422 | CROSS_YEAR_NOT_YET_SUPPORTED | The billing period spans two charging years. details.start_charging_year and details.end_charging_year name both. Split the request at 1 April. |
| 422 | RATES_NOT_PUBLISHED | No 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. |
| 422 | PLATFORM_KEY_NOT_REGISTERED | A platform-sourced request with no ElectraLink credential registered for your account. Register one via PUT /electralink-key, or supply consumption directly. |
| 422 | PLATFORM_KEY_NOT_VERIFIED | A platform-sourced request where the registered credential has not been verified yet. Platform sourcing requires a verified credential; supply consumption directly until verification completes. |
| 422 | PLATFORM_CONSUMPTION_UNAVAILABLE | No 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. |
| 422 | PLATFORM_CONSUMPTION_PARTIAL_WINDOW | The 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. |
| 422 | PLATFORM_CONSUMPTION_NOT_AUTHORISED | The 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. |
| 429 | RATE_LIMIT_EXCEEDED | The 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. |
| 500 | CALCULATION_FAILED | The calculation could not be completed. Safe to retry. |
| 502 | CHARGE_CALCULATION_FAILED | A 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. |
| 502 | PLATFORM_SOURCE_ERROR | The 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
| Method | Endpoint | Description | Key params |
|---|---|---|---|
| GET | /rates/duos | DUoS rates for a DNO region | dno_id (required), charging_year, tariff_category, llfc_code |
| GET | /rates/tnuos | TNUoS zonal and TDR rates | charging_year, zone_id, gsp_group, rate_type, voltage, band |
| GET | /rates/bsuos | BSUoS rates | charging_year, period, date |
| GET | /rates/ccl | Climate Change Levy rates | charging_year |
| GET | /rates/cfd | CfD Levy rates | charging_year, quarter, date |
| GET | /rates/capacity-market | Capacity Market published auction clearing prices per delivery year | delivery_year |
| GET | /rates/aahedc | AAHEDC rates (p/kWh and £/MWh) | charging_year, date |
| GET | /rates/nuclear-rab | Nuclear RAB Levy rates (live from Dec 2025) | charging_year, date |
| GET | /rates/llf | Line Loss Factors from Elexon ISD | dno_id (required), charging_year (required), llfc_code |
| GET | /rates/by-mpan | Full applicable non-commodity rate card for a single MPAN, demand or generation | mpan (required), charging_year, override_distributor_id, postcode (for IDNO MPANs), charges |
| GET | /rates/by-mprn | Full Gas LDZ transportation rate card for a single MPRN | mprn (required), postcode (required), charging_year, charges |
Reference Data
| Method | Endpoint | Description | Key params |
|---|---|---|---|
| GET | /reference/dnos | List all 14 DNO regions | none |
| GET | /reference/llfcs | LLFC-to-tariff-category mappings for a DNO | dno_id (required), llfc_code, market_segment, is_import |
| GET | /reference/mtcs | Meter Timeswitch Classes (MDD) | mtc_id, mtc_type, is_common_code |
| GET | /reference/sscs | Standard Settlement Configurations (MDD) | ssc_code, ssc_type, is_active, include (tprs, clock_intervals) |
| GET | /reference/tprs | Time Pattern Regimes (MDD) | tpr_id, tpr_type, include (clock_intervals) |
| GET | /reference/profile-coefficients | D0018 profile coefficients (NHH, PC 01-04) | profile_class (required), gsp_group (required), date (required), period_index |
Calculations
| Method | Endpoint | Description | Key params |
|---|---|---|---|
| POST | /calculate | Compute 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
| Method | Endpoint | Description | Key params |
|---|---|---|---|
| GET | /usage | Your own usage and current quota state | period (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.
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:
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:
{
"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.ratescarries 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.tnuosis the transmission network charge andbsuosis NESO's balancing charge, returned as separate top-level blocks;policyholds CCL, CfD, Capacity Market, AAHEDC and Nuclear RAB. Together they are the complete non-commodity stack for the supply point.policy.capacity_marketreturns published auction clearing prices (£/kW/year) per delivery year (Oct–Sep). A charging-year window spans two delivery years, so both are returned.policy.cfdreturns one entry per calendar quarter, each carrying its owndelivery_yearand period.host_dno_resolutionisnativefor an ordinary DNO MPAN - the MPAN's own distributor id was used, so no postcode is needed.- An EHV (EDCM) site returns an
edcmblock of site-specific import/export rates (with adirectionindicator) in place ofcdcm.
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(orduos) returns the DUoS block for the meter:cdcm, oredcmfor an EHV site.transmission(ortnuos) returns TNUoS. BSUoS is separate: request it withbsuos.policyreturns 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, ornullfor the full card.
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"{
"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.
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.
{
"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.ratesuses 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_generationis an object of{ basis, zone_count, zones }. Each zone inzonescarries five signed components:system_peak_gbp_kw,shared_year_round_gbp_kw,not_shared_year_round_gbp_kw,residual_gbp_kwandsmall_gen_discount_gbp_kw.tnuos.eet_creditcarriespublished_tariff_gbp_kw(the positive published magnitude) andcredit_gbp_kw(its negative, the credit to the customer), resolved from the site demand zone.bsuosis a top-level no-rate block (applies: false,rate: null) with abasisrecording that generators are exempt since 1 April 2023 (CMP308).policycarries a no-rate block for every demand-only levy:ccl,cfd,capacity_market,aahedc,nuclear_rab,roandfit, each withapplies: false,rate: nulland a statutorybasis. A top-leveldgnurblock (Distributed Generation Network Use rebate, DCUSA Sch 16) is present withapplies: trueandrate: 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).
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:
{
"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
mprnidentity resolves{ mprn, ldz, exit_zone, gdn }for the supply point.gdnis the Gas Distribution Network code (CAD,NGN,SGNorWWU). zca,zcoandccaeach carry arate_unitand abandsobject ofsmall_band_rate,medium_band_rate,large_coefficient,large_exponentandlarge_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 functionlarge_coefficient × SOQ^large_exponent, returned raw, never evaluated.ecn(NTS exit) is a single block with anexit_zone, arate_p_pkwh_dayand anindicativeflag.cfireturns bothrows(read_frequencynon_monthlyandmonthly, each with arate_p_day), andsolrreturns bothrows(customer_typedomesticandic, each with arate_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:
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:
{
"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_idis 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_resolutionispostcodewhen the host DNO came from the postcode,overridewhen you passedoverride_distributor_id,nativefor an ordinary DNO MPAN, oredcmfor 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.
curl -X GET "https://www.energycode.ai/api/v1/usage?period=month" \
-H "X-Api-Key: YOUR_API_KEY_HERE"Response (abridged):
{
"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.
- Swagger UI (interactive reference): https://www.energycode.ai/api/docs
- OpenAPI spec (JSON): https://www.energycode.ai/api/v1/openapi.json - import into Postman or a code generator
Error Handling
All API responses follow a consistent format for errors.
Error Response Format
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description"
}
}Common Error Codes
| HTTP Status | Error Code | Description | Action |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Invalid request parameters | Check request format and parameter values |
| 400 | LIKELY_MPRN | mpan is a 6-10 digit value, the shape of a gas MPRN, not an electricity MPAN | Send a full 21/22-digit electricity MPAN or a 13-digit MPAN Core; gas MPRN data is not served here |
| 400 | UNEXPECTED_LENGTH | mpan 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) |
| 400 | INVALID_MPAN | A full-length MPAN fails format or check-digit validation | Check the MPAN characters and check digit |
| 400 | IDNO_HOST_REQUIRED | IDNO MPAN (distributor 24+) with no postcode or override_distributor_id | Send the site postcode, or override_distributor_id (host DNO 10-23) |
| 401 | UNAUTHORIZED | Missing or invalid API key | Verify X-Api-Key header is set correctly |
| 401 | INVALID_API_KEY | API key format is incorrect | Check key format: ec_(live|test)_[64 hex chars] |
| 403 | SCOPE_DENIED | Endpoint dataset not in your plan scopes | Upgrade your plan or use a key with the right scope |
| 404 | NOT_FOUND | Resource not found | Verify DNO ID, charging year, tariff code exist |
| 429 | RATE_LIMIT_EXCEEDED | Per-minute rate limit exceeded | Wait for Retry-After seconds before retrying |
| 429 | QUOTA_EXCEEDED | Monthly 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 |
| 429 | TOO_MANY_AUTH_ATTEMPTS | Too many failed auth attempts | Wait 5 minutes before retrying |
| 500 | INTERNAL_ERROR | Server error | Contact support if issue persists |
Example: Handling Validation Errors
{
"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:
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:
pip install requestsExample: Fetch DNO regions and DUoS rates
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:
npm install axiosExample:
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)
#!/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:
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:
# 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:
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:
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:
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:
# Use environment-specific keys
API_KEY = os.getenv("ENERGYCODE_API_KEY_TEST") # Development
# API_KEY = os.getenv("ENERGYCODE_API_KEY_LIVE") # ProductionGetting Help
API Reference Documentation
Full API reference with all endpoints, parameters, and response schemas, enabled per account:
- OpenAPI Specification (JSON): https://www.energycode.ai/api/v1/openapi.json
- Interactive API Explorer (Swagger UI): https://www.energycode.ai/api/docs
Support Channels
| Channel | Purpose | Response Time |
|---|---|---|
| [email protected] | 1-2 business days | |
| Technical Issues | [email protected] | 24 hours |
| Account/Billing | [email protected] | 1 business day |
Before Contacting Support
Please include:
- API key prefix (first 12 characters, e.g.,
ec_live_abc1...) - Request ID from
X-Request-IDresponse header - Timestamp of the issue
- Complete request (cURL format preferred)
- Complete response (including status code and headers)
- Expected behavior vs. actual behavior
Appendix: Reference Data
Valid DNO IDs
| ID | Name | Operator Group |
|---|---|---|
| 10 | Eastern Power Networks | UKPN |
| 11 | East Midlands | NGED |
| 12 | London Power Networks | UKPN |
| 13 | SP Manweb | SPEN |
| 14 | West Midlands | NGED |
| 15 | Northern Powergrid (Northeast) | NPg |
| 16 | Electricity North West | ENWL |
| 17 | Scottish Hydro (SHEPD) | SSEN |
| 18 | SP Distribution | SPEN |
| 19 | South Eastern Power Networks | UKPN |
| 20 | Southern Electric (SEPD) | SSEN |
| 21 | South Wales | NGED |
| 22 | South West | NGED |
| 23 | Northern 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 20262026-27= 1 April 2026 to 31 March 2027
Common Tariff Categories (CDCM)
| Code | Description |
|---|---|
DOM_AGG | Domestic Aggregated |
ND_AGG_B1 | Non-Domestic Aggregated, Band 1 |
ND_AGG_B2 | Non-Domestic Aggregated, Band 2 |
ND_AGG_B3 | Non-Domestic Aggregated, Band 3 |
ND_AGG_B4 | Non-Domestic Aggregated, Band 4 |
LV_SS_B1 | LV Site Specific, Band 1 |
HV_SS_B1 | HV Site Specific, Band 1 |
LV_GEN_AGG | LV 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