A practical guide to the free hourly US grid carbon intensity API, with working code for three concrete use cases. Backtest demand response. Time EV charging. Compute time-weighted Scope 2.
GET /api/intensity?zip=94105&hours=24 to retrieve up to 168 hours (7 days) of intensity values for the balancing authority serving that ZIP. Data lags approximately 24 hours - it is not real-time. For live (sub-hour) or forecast intensity, WattTime and Electricity Maps offer commercial APIs. For historical backtesting, post-hoc Scope 2 refinement, or training carbon forecast models, this free API is sufficient.
The API does three things behind one HTTP call:
Fuel factors used (direct combustion, operational emissions only):
| Fuel | EIA code | kg CO₂e per MWh |
|---|---|---|
| Coal | COL | 1,050 |
| Natural Gas | NG | 450 |
| Petroleum | OIL | 800 |
| Other fossil | OTH | 700 |
| Geothermal | GEO | 50 |
| Nuclear / Hydro / Wind / Solar / Biomass | - | 0 |
Source: generation-weighted EPA eGRID2023 averages + IPCC AR6 for non-combustion sources. No lifecycle emissions included.
The interesting finding: variation is massively different between grids. Below is a 7-day average (April 9-16, 2026) showing hour-of-day intensity for three major US grids.
| Grid | Cleanest hour (local) | Dirtiest hour | Swing | Week avg |
|---|---|---|---|---|
| CAISO (California) | 3pm PT @ 0.027 62% solar, 16% wind | 9pm PT @ 0.301 gas ramp, no solar | 11.3× | 0.121 |
| ERCOT (Texas) | 1pm CT @ 0.202 34% solar, 27% wind | 9pm CT @ 0.327 42% gas, 37% wind, 13% coal | 1.6× | 0.271 |
| NYISO (New York) | 6pm ET @ 0.358 48% gas, 24% hydro | 1am ET @ 0.386 50% gas, 23% other fossil | 1.1× | 0.373 |
Three findings worth noting:
| Hour (PT) | Avg intensity (kg CO₂e/kWh) | Pattern |
|---|---|---|
| midnight–3am | 0.12–0.21 | No solar, gas baseload + some wind |
| 6am–7am | 0.20–0.25 | Morning demand ramp, solar not yet |
| 8am | 0.09 | Solar ramping up fast - 4× drop in one hour |
| 11am–5pm | 0.027–0.033 | Solar peak - 60-66% of generation |
| 6pm | 0.05 | Solar fading, wind steady |
| 7pm | 0.16 | Sharp ramp as solar drops out |
| 8pm–10pm | 0.29–0.30 | "Duck curve" peak - gas ramps to meet demand |
This is a textbook duck curve in carbon terms. The afternoon intensity (0.027 kg/kWh) is 86% lower than California's annual eGRID average (CAMX, 0.195 kg/kWh). Annual averages hide enormous scheduling opportunities.
| Hour (UTC) | Intensity (kg CO₂e/kWh) | Fuel mix |
|---|---|---|
| 23:00 (~4pm PT, cleanest) | 0.022 | 66% solar · 12% wind · 4% gas |
| Window avg | 0.121 | - |
| 03:00 (~8pm PT, dirtiest) | 0.318 | solar gone · 19% wind · gas-heavy ramp |
Peak-day swing: ~15×. The 7-day average swing is still 11.3×. On its dirtiest-to-cleanest day, California's grid hits an order-of-magnitude difference between afternoon solar peak and evening gas ramp. This is why annual-average Scope 2 understates the decarbonization benefit of scheduling flexibility.
Say your ops team moved a nightly 1 MWh (1,000 kWh) batch job in ERCOT from 9pm local (02:00 UTC, dirtiest hour last Wednesday) to 12pm local (17:00 UTC, cleanest hour). How much CO₂e did that save, really?
import requests
API = "https://emission-factors.com/api/intensity"
def intensity_at(ba, hour_utc):
"""Get intensity for a specific past hour."""
r = requests.get(API, params={"ba": ba, "hours": 168})
hours = {h["hour_utc"]: h["intensity_kg_co2e_per_kwh"] for h in r.json()["hourly"]}
return hours.get(hour_utc)
# Original schedule (dirty hour)
dirty = intensity_at("ERCO", "2026-04-16T02:00Z") # 0.371 kg/kWh
# Shifted schedule (clean hour)
clean = intensity_at("ERCO", "2026-04-15T17:00Z") # 0.179 kg/kWh
kwh = 1000
emissions_if_original = kwh * dirty # = 371 kg CO2e
emissions_actual = kwh * clean # = 179 kg CO2e
savings = emissions_if_original - emissions_actual
print(f"Carbon saved by shifting: {savings:.0f} kg CO2e "
f"({(1 - clean/dirty)*100:.0f}% reduction)")
# Output: Carbon saved by shifting: 192 kg CO2e (52% reduction)
Result on a favorable day: 192 kg CO₂e avoided per run, 52% reduction. Over the full 7-day window the typical ERCOT swing is smaller (~1.6× or 39% reduction from dirtiest to cleanest hour), so averaging across the year a realistic savings is 30-40% per run, not 52%. Still substantial: a 250-run-per-year job saves roughly 30-45 tCO₂e/year from scheduling alone.
If you run a 20-vehicle EV fleet in San Francisco that charges 100 kWh per vehicle per night (2 MWh total fleet charging), when is the cleanest hour to charge?
Common intuition: overnight off-peak is cleanest. In California, that's wrong.
import requests
r = requests.get("https://emission-factors.com/api/intensity",
params={"zip": "94105", "hours": 168})
# Group hours of day across the week
from collections import defaultdict
from datetime import datetime
by_hour = defaultdict(list)
for h in r.json()["hourly"]:
# Convert UTC to PT roughly (UTC-8)
hour_utc = int(h["hour_utc"][11:13])
hour_pt = (hour_utc - 8) % 24
by_hour[hour_pt].append(h["intensity_kg_co2e_per_kwh"])
# Average intensity by local hour
for hour_pt in sorted(by_hour.keys()):
avg = sum(by_hour[hour_pt]) / len(by_hour[hour_pt])
bar = "█" * int(avg * 100)
print(f" {hour_pt:02d}:00 PT {avg:.3f} {bar}")
Verified CAISO 7-day averages (April 9-16, 2026):
| Local hour (PT) | Avg intensity | Why |
|---|---|---|
| midnight–3 am | 0.12–0.21 | No solar, gas baseload + some wind |
| 6–7 am | 0.20–0.25 | Morning ramp, solar not yet producing |
| 8 am | 0.09 | Solar ramping (4× drop in one hour) |
| 12pm–3 pm | 0.028 | Solar peak - 60-66% of generation |
| 6 pm | 0.05 | Solar fading, wind steady |
| 8–10 pm | 0.30 | Duck curve - solar gone, gas ramps hard |
Actionable conclusion for California: Charging a 2 MWh fleet at noon (0.028 kg/kWh) instead of midnight (0.21 kg/kWh) reduces emissions from ~420 kg/day to ~56 kg/day. That's 364 kg CO₂e/day saved, or roughly 133 tCO₂e/year just from scheduling - without buying any RECs or upgrading any equipment.
And if you charge at the worst hour (9pm, 0.30 kg/kWh)? ~600 kg/day - 10× worse than noon. Utility time-of-use rates often penalize midday ("peak" pricing) but that's exactly the cleanest grid hour in California.
In Texas (ERCOT), midday is also cleanest but the swing is much smaller - noon ~0.20 kg/kWh vs 9pm ~0.33. In New York (NYISO), there's essentially no carbon-optimal hour - the grid is gas-heavy all day and variation is under 10%. Point: use your own grid's data, don't assume.
Your data center in PJM (e.g. ZIP 60601 Chicago) draws a fairly flat 500 kW around the clock = 4.38 GWh/year. Annual-average Scope 2 calculation uses the eGRID RFCW factor (0.4155 kg/kWh):
annual_kwh = 500 * 24 * 365 # = 4,380,000 kWh
egrid_factor = 0.4155 # RFCW eGRID2023
annual_scope2_tCO2e = annual_kwh * egrid_factor / 1000
# = 1,820 tCO2e/year
But a time-weighted calculation using hourly intensity reveals a more nuanced picture. Here's the pattern:
r = requests.get("https://emission-factors.com/api/intensity",
params={"zip": "60601", "hours": 168})
hourly_data = r.json()["hourly"]
load_kw = 500 # constant 500 kW
hourly_emissions_kg = sum(
load_kw * h["intensity_kg_co2e_per_kwh"]
for h in hourly_data
)
# Scale to annual
weekly_kwh = load_kw * len(hourly_data)
annual_scaling = (annual_kwh / weekly_kwh)
time_weighted_annual_tCO2e = hourly_emissions_kg * annual_scaling / 1000
For a flat 24/7 load like a data center, the time-weighted Scope 2 roughly equals the annual-average (within ±3%) because the load doesn't coincide with any specific intensity peak. The hourly approach doesn't help much.
For a daytime-only workload (8am-6pm weekdays) in the same ZIP, the time-weighted Scope 2 is typically 8-15% lower than annual-average because daytime hours on PJM lean on nuclear + solar more heavily than nighttime hours.
For a CAISO workload that runs only 10am-4pm, the time-weighted Scope 2 can be ~86% lower than the annual CAMX eGRID factor would suggest. Verified: CAISO noon-3pm 7-day average is 0.028 kg/kWh vs CAMX annual 0.195 kg/kWh. Those exact hours are 60-66% solar.
Be honest with yourself about fit:
The annual eGRID factor (e.g. CAMX 0.195 kg/kWh) is a single generation-weighted number covering the whole year for an entire subregion. It's the standard for GHG Protocol Scope 2 location-based reporting. Our hourly intensity is computed from EIA-930 fuel mix for each hour, showing actual grid conditions. Annual is the regulatory standard; hourly shows the real-world variation hidden inside that average.
The GHG Protocol accepts both methods; hourly-weighted is recognized as more accurate where load patterns and generation patterns diverge. CDP currently accepts either. For CSRD, the trend is toward time-weighted where feasible, but annual-average remains the baseline. Always document your methodology explicitly.
California's grid hits 60-70% solar during spring/summer afternoons. Since solar has a zero combustion emission factor, the intensity drops nearly to zero. Gas picks up when solar ramps down (the "duck curve"), so evening hours are 10-15× dirtier.
EIA-930 covers the 66 US balancing authorities, but we map ZIP to one primary BA per state for simplicity. A state like Colorado has multiple BAs (PSCO, WACM, WAUW) and our V1 picks one. V2 will use EIA shapefiles for proper spatial mapping.
No. The API is free and keyless - no signup, no header, no auth token. Just call the endpoint. Full API docs.
curl "https://emission-factors.com/api/intensity?zip=94105&hours=24"