Hourly Grid Carbon Intensity by ZIP Code: Free API Guide

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.

Updated April 17, 2026 · By the emission-factors.com team

Community discussion: The 3-state comparison chart from this analysis reached the front page of r/dataisbeautiful (225K+ views, 220+ upvotes, 74 comments). Worth reading the thread for methodology critiques around sample window and annual-vs-hourly averaging.

Is there a free API for hourly grid carbon intensity in the US?

Yes. emission-factors.com returns hourly grid carbon intensity for any US ZIP code, derived from EIA Form 930 hourly fuel-mix data. Call 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.

How it works

The API does three things behind one HTTP call:

  1. Resolve ZIP to balancing authority. ZIP code → state → primary balancing authority (e.g. 94105 → CA → CISO). 22 US balancing authorities supported.
  2. Fetch hourly fuel-mix from EIA-930. The EIA Hourly Electric Grid Monitor publishes MWh generated by each fuel type (coal, gas, nuclear, solar, wind, hydro, etc.) for every hour for each balancing authority.
  3. Compute intensity. Multiply each fuel's MWh by its published CO₂ emission factor (kg per MWh), sum, divide by total generation. The result is kg CO₂e per kWh for that hour on that grid.

Fuel factors used (direct combustion, operational emissions only):

FuelEIA codekg CO₂e per MWh
CoalCOL1,050
Natural GasNG450
PetroleumOIL800
Other fossilOTH700
GeothermalGEO50
Nuclear / Hydro / Wind / Solar / Biomass-0

Source: generation-weighted EPA eGRID2023 averages + IPCC AR6 for non-combustion sources. No lifecycle emissions included.

How much does intensity actually vary by state?

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.

Three-state comparison: CA vs TX vs NY

GridCleanest hour (local)Dirtiest hourSwingWeek 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:

CAISO full 24-hour profile (the interesting one)

Hour (PT)Avg intensity (kg CO₂e/kWh)Pattern
midnight–3am0.12–0.21No solar, gas baseload + some wind
6am–7am0.20–0.25Morning demand ramp, solar not yet
8am0.09Solar ramping up fast - 4× drop in one hour
11am–5pm0.027–0.033Solar peak - 60-66% of generation
6pm0.05Solar fading, wind steady
7pm0.16Sharp ramp as solar drops out
8pm–10pm0.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.

Raw hourly sample (CAISO, April 15-16)

Hour (UTC)Intensity (kg CO₂e/kWh)Fuel mix
23:00 (~4pm PT, cleanest)0.02266% solar · 12% wind · 4% gas
Window avg0.121-
03:00 (~8pm PT, dirtiest)0.318solar 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.

Use case 1: Did shifting our batch job actually reduce carbon?

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.

Use case 2: When should I charge the EV fleet in California?

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 intensityWhy
midnight–3 am0.12–0.21No solar, gas baseload + some wind
6–7 am0.20–0.25Morning ramp, solar not yet producing
8 am0.09Solar ramping (4× drop in one hour)
12pm–3 pm0.028Solar peak - 60-66% of generation
6 pm0.05Solar fading, wind steady
8–10 pm0.30Duck 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.

Use case 3: Time-weighted Scope 2 for a data center

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.

When NOT to use this API

Be honest with yourself about fit:

FAQ

How is this different from the annual eGRID factor you also provide?

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.

Will my CDP / CSRD auditor accept hourly-weighted Scope 2?

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.

Why does CAISO intensity go to 0.022 kg/kWh in the middle of the day?

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.

Why only 22 US balancing authorities?

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.

Do I need an API key?

No. The API is free and keyless - no signup, no header, no auth token. Just call the endpoint. Full API docs.

Try it now:
curl "https://emission-factors.com/api/intensity?zip=94105&hours=24"
Returns the last 24 hours of grid carbon intensity for the California ISO. Full API docs · Use as an MCP tool for Claude.