CARBON INTENSITY API
Last-hour grid carbon intensity, by country.
Free & Open Source.
Operational, lifecycle and consumption-based carbon intensity (gCO2eq/kWh) for every country with published electricity data — 213 in total. True clock hours where a live grid feed exists, a year of hourly history behind them, and the annual average on its own route everywhere else.
Quick example
Get the last completed hour for one country (ISO-3166 alpha-2):
curl https://ci-api.fabiocicerchia.it/v2/DE/past-hour
{
"country": "Germany",
"country_code": "DE",
"zone": "DE",
"unit": "gCO2eq/kWh",
"period_start": "2026-08-27T17:00:00Z",
"period_end": "2026-08-27T18:00:00Z",
"resolution_sec": 900,
"points": 4,
"points_expected": 4,
"complete": true,
"direct": 280,
"lifecycle": 330,
"consumption_direct": 320,
"consumption_lifecycle": 370,
"basis": "measured",
"data_source": { "name": "ENTSO-E", "realtime": true, "status": "operational" },
"generated_at": "2026-08-27T19:13:36Z"
}
period_start and period_end are a true clock hour.
resolution_sec is how wide the underlying provider points are —
900 for ENTSO-E's quarter-hours — and points against
points_expected is how much of the hour the mean covers.
complete is always true here; use
/current-hour for the hour still filling in.
Reading a response
The three numbers, and how to tell whether a reading is current.
Every reading carries four intensities, all in gCO2eq/kWh. They are the corners of two independent axes: how much of the supply chain is counted, and whether the figure describes electricity made in the country or electricity used there.
Production-based counts what the country generates, exports included. Consumption-based counts what comes out of a socket there, adjusted for trade. They part company wherever a country imports heavily: Switzerland generates at 39 on hydro and nuclear but consumes at 179, because much of what it uses is bought in. Running a server in Zürich, 39 flatters you.
| Production-based | Consumption-based | |
|---|---|---|
| Combustion only | direct |
consumption_direct |
| Plus upstream | lifecycle |
consumption_lifecycle |
They are not a ladder. consumption_direct is larger than
lifecycle for an importing country while counting less of the
supply chain — the suffixes say which corner each figure sits in.
| Field | What it counts |
|---|---|
direct |
Combustion only — what the stack emits while generating, computed from the live fuel mix. Nuclear, hydro, wind and solar count as zero. The only figure that genuinely varies hour to hour. |
lifecycle |
Adds extraction, transport, construction and manufacturing — why wind and solar are not truly zero. IPCC AR6 factors, applied as a fixed per-country uplift. Use this one by default. |
consumption_direct |
Adds imported electricity, so it describes what a country consumes rather than what it generates — still combustion-only, hence the suffix. A fixed annual adjustment, and absent from zone readings. |
consumption_lifecycle |
Both adjustments at once: what is consumed here, counted across the
whole supply chain. The figure to report a footprint
with.
Also the most modelled of the four — the upstream uplift is derived from the domestic generation mix and applied to the consumed one, which assumes imports carry a similar upstream intensity per kWh. |
All four are direct plus a per-country constant, so
within one country they rank the hours of a day
identically. Deciding when to run something, any of them gives
the same answer.
Across countries they do not. The constants differ, so
the ordering changes: by direct, Switzerland (39) looks
cleaner than France (41); by consumption_direct it is
France (85) against Switzerland's (179). Deciding where to run
something, the figure you pick decides the answer — use
consumption_lifecycle.
Is the reading current?
There is no stale flag: responses are static files, so nothing
can evaluate freshness at the moment you ask. generated_at tells
you when the snapshot was built, and two checks cover it:
-
Is it a real measurement? Only
basis: "measured"describes the hour you asked for. The yearly figure is no longer a stand-in on an hourly route: it has its own,/v2/<CODE>/yearly, which every country answers. The hourly routes 404 where no live feed exists, rather than quietly serving a constant that describes no particular hour. -
If measured, is it late? Those refresh hourly, so more
than about 65 minutes between
generated_atand now means a run was missed. The age test applies only to measured readings — annual ones are rewritten just weekly, since a yearly figure cannot go out of date in an hour, so an oldgenerated_aton one is expected rather than a fault.
stale = (Date.now() - Date.parse(r.generated_at)) > 3900e3
|| r.basis !== "measured";
Endpoints
Lookups are path-based — the bucket is served directly, so there is nothing
in the request path to read a query string. See the
API reference, rendered from
openapi.json.
| Path | Returns |
|---|---|
/v2/<CODE>/past-hour | The last completed clock hour: the mean of every point in it. Immutable once published. |
/v2/<CODE>/current-hour | The hour in progress: the mean of the points so far. |
/v2/<CODE>/latest | The newest hour the provider has published, however old. Some feeds run a day or more behind by design; for those this is the only reading there is. |
/v2/<CODE>/history/<YYYY-MM-DD> | One UTC day of hourly means, 365 days retained. A past day never changes again, so it is safe to cache forever. |
/v2/<CODE>/yearly | The annual average. Every country has one. |
/v2/<CODE>/<ZONE>/… | The same hourly routes for a bidding zone or balancing region — /v2/IT/SICI/past-hour. Measured only; 404 when the provider has nothing, so treat it as ask the country instead. |
/v2/countries.json | Every country: metadata, zones, realtime availability and annual figures |
/v2/past-hour.json | The measured countries' last completed hour, in one document |
An UPPERCASE segment is a code and a lowercase-hyphenated one is a resource,
so /v2/IT/SICI/past-hour reads as country, zone, resource. Only
countries with a live grid feed answer on the hourly routes; the rest have
/yearly. ISO-3166 alpha-2 only.
When a grid feed goes down, /past-hour and
/current-hour 404 on the very next run — no grace
period, nothing held back. Both are named for particular clock hours and mean
them, so a missing hour is a missing hour.
/latest is what carries a reading across the outage, for as long
as the outage lasts: it has no age bound and is not rewritten while a feed is
quiet, so its generated_at stands still and its age shows.
period_start/period_end always say which hour a
document describes.
Which countries answer on which route depends on how far
behind their grid feed publishes, not on the country, so it moves.
/v2/countries.json carries the live answer per country:
routes (which of the three answered),
data_lag_seconds (how far behind the newest hour reaches) and
stale — the ⚠️ warning flag, set when the
lag is past the bound the hour-named routes are held to, so only
/latest answers and the figures are a recent reading
rather than a current one. Two feeds sit there as a rule: EIA (US) publishes
about a day behind and Eskom (ZA) several days. Check those fields rather
than assuming.
Rate limit: 1 request per 10 seconds per IP,
answered with 429 beyond that — and the body of a 429
is not JSON, so check the status code before parsing. From another origin a
browser cannot see that status at all: the 429 carries no CORS
header, so fetch rejects with a network error — back off on that
too. If you want several countries at once, /v2/countries.json and
/v2/past-hour.json each return the lot in a single request,
which is both faster and kinder than looping over the per-country routes.
And in the spirit of the thing: every request carries
a little carbon of its own. Cache what you fetch, and poll no faster than your
decisions actually change — the readings only move once an hour.
Supported countries and zones
Data & methodology
Built from raw grid-operator data with lifecycle factors following IPCC AR6, WG III, Annex III and consumption-based accounting in the spirit of the ECON-PowerCI method (Scientific Data 12, 2025). Live readings compute operational intensity from each grid's generation mix; lifecycle and consumption are layered on per country.
Sources & attribution
- Annual figures: Our World in Data energy dataset (Ember; Energy Institute Statistical Review) — CC-BY 4.0.
- Real-time source catalogue: operator names and reference URLs, partially
compiled from electricitymaps-contrib
(
DATA_SOURCES.md), which was also used to cross-check coverage. - Lifecycle emission factors: IPCC AR6, WG III (2022), Annex III.
- Consumption-based method: ECON-PowerCI, Scientific Data 12 (2025) — the approach, not the dataset.
- Live generation data: the operators named in each response's
data_source(ENTSO-E, EIA, NESO, ONS, OpenNEM, EMC, Eskom, …), subject to their own terms. The intensity figures are computed here from that generation data and are not published or endorsed by those operators. - United States: U.S. Energy Information Administration, via the EIA API under its Terms of Service — source of the generation mix only.
- Europe: ENTSO-E Transparency Platform, re-usable data under CC BY 4.0. Adapted: generation per production type is weighted by emission factors to derive an intensity.