EnergyCode Enterprise API
Query GB Elec 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 Elec 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 |
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
/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: every successful request costs one call, whatever the endpoint and however much data it returns. Failed requests (4xx/5xx) and the free /usage meta call cost nothing. Each response reports the cost of that call in the X-Request-Cost header.
| Response | Cost |
|---|---|
| Any successful (2xx) data request | 1 call |
/usage | 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: 8
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 a successful call, 0 otherwise) |
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"Endpoint Reference
All endpoints are served from https://www.energycode.ai/api/v1 and require the X-Api-Keyheader. 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 |
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 postcodeand 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
- Swagger UI: 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:
- 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