{"openapi":"3.1.0","info":{"title":"Gridomics API","version":"v1","description":"Live carbon intensity of European electricity grids, per bidding zone.\n\n**Honesty model:** every zone carries a `freshness` (`fresh`/`delayed`/`modeled`/`unknown`) and a `coverage` (share of generation backed by a committed IPCC emission factor). A zone with no trustworthy data reports `freshness: \"unknown\"` with all data fields nulled — we never invent a number. `consumptionIntensityGCO2eqPerKwh` is the flow-traced consumption figure (null below the coverage floor).\n\n**Access:** the read API is open and keyless. Anonymous callers are rate-limited per IP across three windows — 60/min, 1,000/hour, 10,000/day; an API key lifts these to 600/min, 10,000/hour, 100,000/day. The hour/day caps sit well below the minute rate so short bursts are fine but sustained scraping is throttled. Per-window state is returned in `X-RateLimit-*` (and `X-RateLimit-*-minute/hour/day`) headers; a 429 names the binding `window` + `Retry-After`.\n\n**Attribution:** please credit \"data via Gridomics\" when you use it.","license":{"name":"Data: see methodology","url":"https://gridomics.com/methodology"}},"servers":[{"url":"https://api.gridomics.com","description":"Production"}],"paths":{"/api/v1/zones":{"get":{"summary":"All zones — current snapshot","description":"The full SnapshotPayload for every zone. A malformed zone is dropped (reported in `X-Gridomics-Dropped-Zones`), not 503'd (ADR-0015).","responses":{"200":{"description":"Current snapshot","content":{"application/json":{}}},"429":{"description":"Rate limited — see Retry-After + X-RateLimit-*"},"503":{"description":"No snapshot yet (cold start) or envelope invalid"}}}},"/api/v1/zones/{zoneId}":{"get":{"summary":"One zone — current snapshot","parameters":[{"name":"zoneId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Z]{2}(-[A-Z0-9]+)?$"},"example":"CZ"}],"responses":{"200":{"description":"The zone's ZoneSnapshot"},"400":{"description":"Malformed zone id"},"404":{"description":"Well-formed id, absent from the snapshot"},"503":{"description":"No snapshot available"}}}},"/api/v1/zones/{zoneId}/history":{"get":{"summary":"One zone — trailing time series","parameters":[{"name":"zoneId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Z]{2}(-[A-Z0-9]+)?$"},"example":"CZ"},{"name":"window","in":"query","required":false,"schema":{"type":"string","enum":["24h","7d"],"default":"24h"}}],"responses":{"200":{"description":"Ascending points (intensity, consumption, renewable, demand, price)"},"400":{"description":"Malformed zone id or unsupported window"}}}},"/api/v1/zones/{zoneId}/daily":{"get":{"summary":"One zone — permanent daily aggregates (long-horizon archive)","description":"Daily roll-ups (avg/min/max) persisted permanently (ADR-0022) — long-horizon grid context (CO₂ intensity, price, renewable share) over months/years, for downstream consumers like householdsim. Honesty-first: days with no data are omitted, never synthesized.","parameters":[{"name":"zoneId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Z]{2}(-[A-Z0-9]+)?$"},"example":"CZ"},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"Inclusive start day YYYY-MM-DD. Default: 30 days before `to`."},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"Inclusive end day YYYY-MM-DD. Default: today (UTC)."}],"responses":{"200":{"description":"{ zoneId, from, to, days:[{ day, intensity, consumptionIntensity, priceEurPerMWh, renewableShare, windSolarShareOfDemand, minIntensity, maxIntensity, sampleCount, computedBy }] } ascending. computedBy: null = live daily rollup; 'reprocess-a75-v1' = rebuilt from the raw archive (ADR-0030)."},"400":{"description":"Malformed zone id, bad date, inverted range, or range > 366 days"}}}},"/api/v1/history/at":{"get":{"summary":"All-zones snapshot as of a past instant (time scrubber)","description":"The latest history row per zone at/before `t`, trimmed to the map's layer metrics (intensity / price / renewable). Cross-border flows are not archived, so they are omitted. Empty `zones` (no history yet) is a 200, not an error.","parameters":[{"name":"t","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"ISO-8601 instant; defaults to now, capped at now."}],"responses":{"200":{"description":"{ at, generatedAt, zones } — per-zone layer metrics"},"400":{"description":"Unparseable t"}}}},"/api/v1/forecast":{"get":{"summary":"Day-ahead forecast — all zones (time scrubber, future)","description":"Forward hourly curves for the two metrics with an honest day-ahead source (ADR-0023/0028): price (ENTSO-E A44 cleared day-ahead auction; NO/SE/DK use their ADR-0027 representative bidding zone) and windSolarShareOfDemand (A69 wind+solar forecast ÷ A65 load forecast — may exceed 1 when renewables are forecast to outstrip demand; NOT the live renewable-share-of-generation metric). Carbon intensity + trade balance have no honest forecast source and are deliberately absent — the map shows them as no-data ahead of now, never a guess. Empty zones (cold start) is a 200.","responses":{"200":{"description":"{ schemaVersion, generatedAt, horizonEndIso, zones: { [zoneId]: [{ atIso, priceEurPerMWh, windSolarShareOfDemand }] } }"}}}},"/api/v1/zones/{zoneId}/forecast-accuracy":{"get":{"summary":"One zone — day-ahead wind+solar forecast accuracy","description":"How accurate our day-ahead WIND+SOLAR-SHARE-OF-DEMAND forecast (ADR-0028) was vs the realised daily mean of the same metric (ADR-0024/0029): per UTC day, the logged forecast joined with realised wind+solar÷demand, plus MAE + signed bias (percentage points) over the window. Both sides may exceed 100% (renewables outstripping demand). Price is excluded — the A44 day-ahead auction is also our realised price, so it has no error to score. A day appears only when both a forecast and a realised value exist (never scored against a gap).","parameters":[{"name":"zoneId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Z]{2}(-[A-Z0-9]+)?$"},"example":"DE"},{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":90,"default":14},"description":"Look-back window in days. Default 14, cap 90."}],"responses":{"200":{"description":"{ zoneId, from, to, maePp, biasPp, sampleDays, days:[{ day, forecastWindSolarShareOfDemand, actualWindSolarShareOfDemand, errorPp }] }"},"400":{"description":"Malformed zone id"}}}},"/api/v1/zones/{zoneId}/reprocess":{"get":{"summary":"One zone-day — re-derive from the raw archive (reproducibility)","description":"Reprocessing engine (ADR-0025): recomputes a zone-day's daily intensity + renewable share from the IMMUTABLE raw A75 archive (ADR-0018) under the current methodology, and compares to the stored zone_daily. A match proves the permanent archive is reproducible from first principles; a delta surfaces a methodology change or regression. Read-only (never writes). Bounded to one zone-day.","parameters":[{"name":"zoneId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Z]{2}(-[A-Z0-9]+)?$"},"example":"CZ"},{"name":"day","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"UTC day YYYY-MM-DD to re-derive. Default: yesterday."}],"responses":{"200":{"description":"{ zoneId, day, fromRaw:{avgIntensity,min,max,avgRenewableShare,sampleCount}|null, stored:{…}|null, comparison:{intensityDeltaPct,renewableDeltaPp,reproducible} }"},"404":{"description":"Unknown zone or a zone with no raw A75 archive (e.g. NESO/GB)"},"503":{"description":"Raw archive (R2) not bound"}}}},"/api/v1/admin/reprocess-backfill":{"post":{"summary":"Admin — rebuild zone-days from the raw archive (secret-gated)","description":"Operator-only (ADR-0030): rebuilds ≤8 zone-days of zone_daily per call from the immutable raw archive under the current methodology — merge-write (A75-derived columns replaced; price/consumption/ws-share preserved), provenance-tagged (computed_by), idempotent. Requires `Authorization: Bearer <ADMIN_TOKEN>`; 503 until the secret is provisioned. Returns per-day outcomes + a `nextFrom` cursor. Documented for transparency — not a public surface.","parameters":[{"name":"zone","in":"query","required":true,"schema":{"type":"string","pattern":"^[A-Z]{2}(-[A-Z0-9]+)?$"}},{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":true,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"{ zoneId, from, to, days:[{day,written,…}], nextFrom }"},"401":{"description":"Missing/invalid bearer"},"503":{"description":"ADMIN_TOKEN not provisioned or raw archive unbound"}}}},"/api/v1/status":{"get":{"summary":"Data health — per-source + per-zone status","description":"Public data-health summary: per-source health (operational/degraded/down), the honest count of zones with live data right now, and each zone's freshness + last successful observation. The continuously-visible 'honesty rule working' view.","responses":{"200":{"description":"StatusSummary (sources, zones, zonesWithData)"}}}}},"components":{"securitySchemes":{"bearerKey":{"type":"http","scheme":"bearer","description":"Optional `grd_live_…` API key to lift the anonymous rate limit."}}}}