- Free, no key, no sign-up
- JSON, with CORS open for browsers
- Every half hour, from NESO's forecast
- Great Britain, 14 regions or a postcode
GET https://energycount.co.uk/grid/best.json?region=12&hours=2 The request
| Parameter | Values | What it does |
|---|---|---|
region | 0 to 14 (default 0) | 0 is Great Britain; 1 to 14 are NESO's grid regions, listed below. |
postcode | The first half of a postcode, such as BH16 | Uses the forecast for that postcode area instead of a region. Takes priority over region. Northern Ireland isn't covered. |
hours | 1, 2, 3 or 4 (default 2) | How long a window to find: the dishwasher might need 2 hours, a car 4. |
forecast | 1 | Also return the 48 half hours of the forecast. |
Regions
0 Great Britain1 North Scotland2 South Scotland3 North West England4 North East England5 Yorkshire6 North Wales & Merseyside7 South Wales8 West Midlands9 East Midlands10 East England11 South West England12 South England13 London14 South East England The response
An example, for South England at 09:40 on 17 October 2026. Times are UTC (the Z), so in summer they are an
hour behind UK clocks; every language's date library turns them into local time.
{
"v": 1,
"region": {
"id": 12,
"name": "South England"
},
"postcode": null,
"hours": 2,
"today": {
"start": "2026-10-17T12:00:00Z",
"end": "2026-10-17T14:00:00Z",
"avg": 96,
"index": "low"
},
"next24": {
"start": "2026-10-18T02:00:00Z",
"end": "2026-10-18T04:00:00Z",
"avg": 71,
"index": "low"
},
"dirtiest": {
"start": "2026-10-17T16:30:00Z",
"end": "2026-10-17T18:30:00Z",
"avg": 182,
"index": "moderate"
},
"now": {
"from": "2026-10-17T08:30:00Z",
"to": "2026-10-17T09:00:00Z",
"ci": 128,
"index": "low"
},
"cleanestNow": false,
"updated": "2026-10-17T08:41:12Z",
"stale": false,
"source": "NESO Carbon Intensity API",
"licence": "CC BY 4.0",
"attribution": "Carbon intensity forecast from NESO (CC BY 4.0) via GridMonkey, https://energycount.co.uk/tools/grid-mix/"
} | Field | Meaning |
|---|---|
next24 | The cleanest run of hours in the next 24 hours: start, end, its average in gCO₂/kWh (avg) and band (index). It may be tomorrow. |
today | The cleanest run that ends by midnight UK time, or null when less of today is left than the window. |
dirtiest | The dirtiest run of the same length in the next 24 hours: the time to avoid. |
now | The half hour in progress: NESO's forecast (ci, gCO₂/kWh) and its band. |
cleanestNow | true when the cleanest window starts with the half hour in progress: a good moment to switch on. |
forecast | Only with forecast=1: 48 half hours, each from, ci and index. |
region, postcode, hours | What the answer is for. |
updated, stale | When GridMonkey last fetched NESO's forecast. stale is true when NESO couldn't be reached and this is the last good answer, at most 3 hours old. |
source, licence, attribution | Where the figures come from, and the credit to show with them. |
The bands are very low, low, moderate, high and very high.
They are GridMonkey's bands, fixed in grams and the same in every part of the answer: very low under 70 g,
low 70–139 g, moderate 140–219 g, high 220–299 g and very high 300 g or more. The app's live reading and the
badges show NESO's own band for each half hour where NESO gives one, and NESO's cuts are lower than these, so
the two can be a band apart: at 105 g, say, NESO may say moderate where this API says low.
Try it
From code
The command line
curl "https://energycount.co.uk/grid/best.json?region=12&hours=2" JavaScript
const r = await fetch('https://energycount.co.uk/grid/best.json?region=12&hours=2');
const best = await r.json();
const start = new Date(best.next24.start);
const end = new Date(best.next24.end);
const hm = (d) => d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/London' });
console.log(`Cleanest 2 hours: ${hm(start)}–${hm(end)}, ${best.next24.avg} gCO2/kWh`); Python
import requests
from datetime import datetime
from zoneinfo import ZoneInfo
best = requests.get("https://energycount.co.uk/grid/best.json",
params={"region": 12, "hours": 2}, timeout=10).json()
uk = ZoneInfo("Europe/London")
start = datetime.fromisoformat(best["next24"]["start"]).astimezone(uk)
end = datetime.fromisoformat(best["next24"]["end"]).astimezone(uk)
print(f"Cleanest 2 hours: {start:%H:%M}-{end:%H:%M}, {best['next24']['avg']} gCO2/kWh") Home Assistant
Add this to configuration.yaml and Home Assistant gets four sensors (the start, end and carbon of the
cleanest 2 hours, and the grid's carbon now) plus a Cleanest window now sensor that is on while the window
runs. Change region=12 to your region from the list above, or use postcode=BH16 with your own
postcode area. It asks every 30 minutes, which is all it needs.
# GridMonkey: the cleanest 2 hours in the next 24, for South England (region 12).
# Change region=12 to your region's number, then restart Home Assistant.
rest:
- resource: https://energycount.co.uk/grid/best.json?region=12&hours=2
scan_interval: 1800
sensor:
- name: "Cleanest window start"
unique_id: gridmonkey_cleanest_window_start
value_template: "{{ value_json.next24.start }}"
device_class: timestamp
- name: "Cleanest window end"
unique_id: gridmonkey_cleanest_window_end
value_template: "{{ value_json.next24.end }}"
device_class: timestamp
- name: "Cleanest window carbon"
unique_id: gridmonkey_cleanest_window_carbon
value_template: "{{ value_json.next24.avg }}"
unit_of_measurement: "gCO2/kWh"
- name: "Grid carbon now"
unique_id: gridmonkey_grid_carbon_now
value_template: "{{ value_json.now.ci }}"
unit_of_measurement: "gCO2/kWh"
template:
- binary_sensor:
- name: "Cleanest window now"
unique_id: gridmonkey_cleanest_window_now
state: >
{% set s = states('sensor.cleanest_window_start') %}
{% set e = states('sensor.cleanest_window_end') %}
{% if s in ['unknown', 'unavailable'] or e in ['unknown', 'unavailable'] %}
false
{% else %}
{{ as_datetime(s) <= now() < as_datetime(e) }}
{% endif %}
Then an automation can start something at the right time. This one turns on a dishwasher's smart plug when the cleanest 2 hours begin, if a Dishwasher ready toggle is on: create that toggle as a helper, and switch it on when you load the machine. A smart plug only suits a machine that starts by itself when the power comes on, so check yours, or use the appliance's own smart control in the action instead. It uses the trigger and action format of Home Assistant 2024.10 and later.
- alias: "Dishwasher in the cleanest window"
description: "Turn on the dishwasher's smart plug when the cleanest 2 hours start, if it's loaded."
triggers:
- trigger: time
at: sensor.cleanest_window_start
conditions:
- condition: state
entity_id: input_boolean.dishwasher_ready
state: "on"
actions:
- action: switch.turn_on
target:
entity_id: switch.dishwasher_plug
- action: input_boolean.turn_off
target:
entity_id: input_boolean.dishwasher_ready
The same pattern works for an EV charger, an immersion heater or a heat pump's hot-water boost: use hours=4
for a longer job. On a time-of-use tariff, check the cheap hours too before you rely on it for cost.
Fair use, credit and terms
- Polling. Every 30 minutes is plenty; answers are kept for up to 10 minutes, never past the end of the half hour, so faster polling gets the same answer. Each address is limited to 30 requests an hour, then gets
429with aRetry-Afterheader. - Errors.
400for a parameter it can't read (the body says which),404for a postcode area NESO has no forecast for (Northern Ireland isn't covered),502if NESO can't be reached and there is no recent answer to fall back on. - Credit. The forecast is NESO's, under the CC BY 4.0 licence, so show the
attributiontext, or "Carbon intensity forecast: NESO, via GridMonkey" with a link to this page, wherever you show the figures. - Terms. Free for any use, including commercial. It is run on a best-effort basis with no guarantee of availability, and comes with no warranty: don't use it where a wrong or missing answer could cause harm. If the format ever changes, the version number
vwill change and this page will say so first.
Prefer a picture? The best-time badge shows the same answer as a small image for any web page, and GridMonkey draws the whole forecast.
Questions, answered
Do I need an API key or an account?
No. Call the address from anything that can make a web request, including a web page in someone's browser (it sends the CORS header that allows that). There's nothing to sign up for.
How often should I call it?
Every 30 minutes is plenty. NESO updates its forecast each half hour, and GridMonkey keeps each answer for up to 10 minutes, never past the end of the half hour, so calling more often gets the same answer. Each address allows 30 requests an hour; past that it answers 429 until the hour is up.
Is the cleanest time also the cheapest?
Often, but not always. On a time-of-use tariff such as Octopus Agile, windy nights and sunny afternoons tend to be both cheap and clean, but prices also follow demand and gas prices. This API gives carbon only. GridMonkey's app can show Agile prices for your region beside the carbon forecast.
Data: NESO Carbon Intensity API (carbonintensity.org.uk), licensed under CC BY 4.0. GridMonkey is made by Energycount.