A single REST interface for European power, gas, carbon and weather fundamentals — day-ahead prices, cross-border flows, generation mix, gas storage, LNG send-out and pipeline flows — delivered with a transparent live-vs-fallback data contract built for REMIT II-grade auditability.
The Voltstack Data API exposes the same European energy fundamentals that power the Voltstack Analytics terminal. Every endpoint returns JSON over HTTPS, designed for two patterns: low-latency polling for live desks, and scheduled pulls for analytics pipelines. The API is read-only and spans four domains:
All endpoints sit under one versioned base path. The major version is pinned in the path; additive changes (new fields, new zones) ship without a version bump.
Base URL https://api.voltstack.energy/v1 Protocol HTTPS only (TLS 1.2+) Format application/json; charset=utf-8 Methods GET (read-only)
The /v1 surface is read-only and has two access tiers, set for the surface as a whole rather than per path.
As measured on 22 September 2026: a request carrying a valid data API key is answered at full depth on every
documented path, and a request carrying no key is answered as a labelled sample. Since 24 September 2026 five of
those paths are paused and answer neither way (Paused endpoints, below). A sample response is the same
envelope with a sample block added (§04). Current-data endpoints serve their standard recent window.
History endpoints serve at most the most recent 30 days: a window that starts earlier is narrowed to that floor and
still labelled, a window that ends at or before the floor is refused with 401 api-key-required, and the
CSV export refuses an anonymous request whose from and to are not both ISO days inside the window, because a narrowed
file could not carry the label. Anonymous requests also carry a per-address rate limit (§07). An endpoint that
additionally requires a signed-in tenant (§06) still refuses a caller who is not one. The tiers are deployment
configuration and can change without a new revision of this document, which is why the statement is dated. A keyed
request is never narrowed, never labelled and does not depend on that configuration, so build every integration
keyed. Ask data@voltstack.energy before you rely on the sample tier for anything.
A data API key is a token of the form vsk_live_<key_id>_<secret> — a ten-character public
lookup handle and a forty-character secret, both base62. It is presented in the X-API-Key header; an
Authorization: Bearer header carrying the same token is also accepted. Keys are issued on request at
research@voltstack.energy. Server-side each key is a row carrying its own identity, so keys are issued, listed,
rotated and deactivated individually and usage is attributed per key. For those keys only the SHA-256 digest of the
secret is held at rest, and comparison is constant-time, so a key cannot be recovered from the key store and cannot be
guessed by timing. A configured DATA_API_KEYS list on the web service is also honoured, as a break-glass
path that verifies without the key store. An entry on that list is either the SHA-256 digest of a token or, in the
older form, the token itself. The older form is a plaintext credential held in the hosting provider's service
configuration, not a digest, and as at this revision at least one issued key is still held that way. Keys on the list
carry no per-key usage record.
Every issued key also carries one or more scopes, fixed when the key is issued: history covers
the settled-history endpoints under /v1/history, current covers every other documented path (live power,
gas, weather and benchmark feeds), and all covers both. A valid key presented on an endpoint outside its
scopes is refused with 403 and error: "insufficient-scope". This is a different refusal from
the 401 above: the credential itself verified, the grant is what falls short, so re-presenting the same key cannot
succeed. The refusal names the scope the endpoint requires and the scopes the key was granted; it never echoes the key.
Keys honoured through the break-glass DATA_API_KEYS list carry no scope restriction. To extend an issued
key's scopes, write to research@voltstack.energy.
Paused endpoints. As at 24 September 2026, five documented paths are paused while Voltstack confirms
redistribution rights with their publishers: /v1/power/imbalance-de, /v1/power/grid-stress
and /v1/power/balancing-stress (netztransparenz.de), /v1/power/constraints (JAO) and
/v1/history/n2ex-dayahead (Nord Pool N2EX). Each answers every request with 451 and the
body shown below, whatever it carries and before any credential is checked, so neither a retry nor another key
changes the answer. /v1/power/imbalance keeps answering, but its four German control-area rows come from
netztransparenz.de and are withheld: the ENTSO-E areas are served as before, and each German area reads
paused in meta.areaStatus. Bulk extracts of the same datasets are paused as well. A later
revision of this document records each publisher's answer and the date its endpoints return.
# Keyed request curl "https://api.voltstack.energy/v1/history/day-ahead?zone=DE_LU&from=2026-01-01&to=2026-07-01" \ -H "X-API-Key: vsk_live_<key_id>_<secret>" # No key: 200, the same envelope with a sample block added: curl "https://api.voltstack.energy/v1/power/day-ahead" "sample": { "sample": true, "window": "recent", "detail": "Anonymous sample tier. Full depth requires a data API key ..." } # No key, a history window ending at or before the 30-day floor: 401 { "error": "api-key-required", "detail": "Anonymous access is limited to a 30-day sample window. Full-depth history requires a data API key in the X-API-Key header. Request one at research@voltstack.energy." } # An invalid, revoked or expired key returns 401 on every path, before any parameter is read: { "error": "api-key-required", "reason": "invalid-key", "detail": "This endpoint requires a data API key in the X-API-Key header. Request one at research@voltstack.energy." } # A valid key presented outside its scopes returns 403: { "error": "insufficient-scope", "detail": "This API key does not carry the 'history' scope required by this endpoint (granted: current). To extend the key's scopes, contact research@voltstack.energy." } # Any request to a paused endpoint returns 451: { "error": "unavailable", "reason": "This dataset is paused while we confirm redistribution rights with its publisher." }
Every response is wrapped in a uniform envelope — the core of Voltstack's data-integrity model. Each payload declares whether the value is live upstream data or a transparent fallback, when it was sourced, and whether it was cached. Consumers never receive a silently stale or fabricated value.
{
"source": "entsoe", // canonical upstream identifier
"live": true, // true = real upstream; false = treat as fallback
"data": { /* domain payload */ },
"fetchedAt": 1781622120092, // epoch ms the value was obtained
"cached": false, // served from the server-side TTL cache
"reason": null, // when live=false: "no-token" | "upstream-error" | "rate-limited" | "disabled"
"meta": { // per-payload provenance
"upstream": "ENTSO-E Transparency (A44)",
"unit": "EUR/MWh (zone currency)", "timezone": "UTC",
"completeness": 0.998, // [0,1] fraction of expected points arrived (history)
"gaps": [], // timestamp ranges with missing data (history)
"dataAgeSeconds": 642, // seconds since the rows were last written
"dataAsOf": 1781650800000 // epoch ms of the newest data point
}
}
| Field | Type | Description |
|---|---|---|
| source | string | Canonical upstream (e.g. entsoe, gie-agsi, entsog, elexon, open-meteo). |
| live | boolean | true = current upstream data; false signals fallback — inspect reason. |
| data | object | Domain payload (§6). null when live is false and no cached value exists. |
| fetchedAt | integer | Unix epoch milliseconds (UTC) the upstream value was obtained. |
| cached | boolean | Served from cache vs a fresh upstream fetch. |
| lastGood | boolean | Optional. true when the payload is real data served from a retained last-good copy (a prior successful fetch) rather than a fresh upstream read; live stays true and fetchedAt/dataAgeSeconds carry the copy's real age. |
| reason | string | Present only when live is false: no-token · upstream-error · rate-limited · disabled (Data Dictionary §27). |
| sample | object | Present only on a response to a request carrying no key (§03): sample: true, window ("recent" on current-data endpoints, "30d" on history) and a detail string naming the key-request address. Never present on a keyed response. |
| meta | object | Provenance block: upstream source name, unit, timezone (always UTC); on archive-backed payloads, completeness [0,1], gaps[] and dataAgeSeconds (seconds since the worker last wrote the rows behind the payload, recomputed on every response including cache hits; a re-fetch that rewrites unchanged values resets it, so it measures ingestion, not how current the data is; for cross-border flows and the negative-price radar it is instead the age of the oldest delivery slot served); dataAsOf (epoch ms of the newest data point behind the headline values: its delivery slot, settlement period, gas day or observation, or for a forecast the moment it was captured; where a payload headlines several series, the stalest series' newest point; later than now when a published curve runs ahead), set on every payload that carries dataAgeSeconds and on every feed the public /status page watches; on multi-area payloads, a per-area areaStatus diagnostic (e.g. ok · stale · estimator · no-data · paused, the last for a row withheld while its publisher confirms redistribution rights, §03). |
| Concern | Convention |
|---|---|
| Timestamps | Unix epoch ms, UTC. Delivery slots / gas days also returned as ISO-8601 dates. |
| Power price units | Currency per MWh — EUR (Continental/Nordic), GBP (GB). |
| Gas / LNG units | GWh/d for flows & send-out; % full and TWh for storage. |
| Flow / generation | MW. Cross-border flows signed (+ normal direction, − reversed). |
| Resolution | Day-ahead is resolution-aware (PT60M & PT15M); value returned is the slot covering "now". |
| Change fields | Day-ahead price change = day-on-day move vs the same delivery hour yesterday. |
| History windows | from / to as ISO dates (UTC), window capped at 3 years for a keyed request; a request carrying no key is limited to the most recent 30 days (§03). When real coverage starts later than from, the payload declares it via oldestAvailable rather than silently returning a shorter series. |
Current day-ahead power price per bidding zone with day-on-day change, as two zone-keyed maps. 11 zones: DE-LU, FR, NL, BE, AT, NO1, NO2, SE3, DK1, ES, IT-North — all priced in EUR/MWh. GB is not in this payload: ENTSO-E's A44 series carries nothing for GB, and the GB auction curve, the n2ex-dayahead history endpoint below, is paused (§03).
{ "prices": { "DE_LU": 72.87, "FR": 58.11, "NL": 69.40 },
"changes": { "DE_LU": -3.63, "FR": 2.40, "NL": -1.05 } }
changes is the day-on-day move vs the same delivery slot the previous day. Source: ENTSO-E (A44). Refresh: ≤5 min.
Physical cross-border electricity flows for 12 interconnectors (IFA1/2, NSL, BritNed, Nemo, Viking, FR–DE, DE–NL, DE–AT, NO–SE, ES–FR, FR–IT), as a map of interconnector id to signed net flow in MW.
{ "flows": { "IFA_1": 2000, "NSL": -1310, "FR_DE": 852, "ES_FR": -405 } }
Source: ENTSO-E (A11). Flow is signed, net of both directions at the border: positive = the link's normal direction, negative = reversed. Ids and normal directions are tabulated in the Data Dictionary §2a; capacities and utilisation are not part of this payload.
Actual generation by fuel, hourly. Countries: DE, FR, GB, ES, IT, NL, NO, SE, PL. range = 1D (default) · 1W · 1M, served from the settled archive; GB (Elexon) is 1D. Multi-day ranges label hours as MM/DD HH:00.
{ "country":"DE", "hours":[
{ "hour":"09:00", "nuclear":0, "coal":2626, "gas":1159,
"biomass":2152, "hydro":1639, "solar":41620, "wind":2009 } ]}
Sources: ENTSO-E (A75); Elexon FUELHH (GB). Generation in MW.
French generation forecast in MW for one production type, as RTE publishes it. filiere = WIND_ONSHORE (default) · WIND_OFFSHORE · SOLAR · AGGREGATED_CPC (RTE's aggregate of generation under purchase obligation); an unknown value is answered as WIND_ONSHORE. horizon = CURRENT · ID · D-1 · D-2 · D-3, RTE's own horizons; without it the most current horizon held is served. RTE publishes AGGREGATED_CPC run by run, so its horizon also names the run, RTE's sub_type, after a slash: D-3/DA01 · D-3/DA02 · D-2/DA01 · D-2/DA02 · D-1/DA01 · D-1/DA02 · D-1/ID00 to D-1/ID02 · ID/ID03 to ID/ID24. DA01 and DA02 are RTE's two day-ahead runs, ID00 to ID02 its evening and midnight updates of D-1, and ID03 to ID24 its hourly intraday runs, each covering the rest of the day. Every horizon served for the aggregate is one RTE run; without horizon, the first run of the most current horizon held is served. The window runs from 24 h before the request to 4 days after it.
{ "filiere":"WIND_ONSHORE", "horizon":"D-1", "unit":"MW", "availableHorizons":["CURRENT", "ID", "D-1", "D-2"],
"points":[ { "startMs":1792792800000, "value":4200, "resolutionMin":15 } ] }
Source: RTE Generation Forecast API (data.rte-france.com); attribution Source: RTE (data.rte-france.com). Each value is RTE's, unchanged, over RTE's own interval (resolutionMin); the load factor RTE also publishes is not stored. A requested horizon with no rows in the window, while other horizons have some, is echoed with an empty points array, never replaced by another; when the production type has no rows in the window at all, the answer is live:false with data:null and a reason. A horizon without a run asked of AGGREGATED_CPC is answered the same way, since no single RTE series answers it. The aggregate rows stored before v1.21 kept, for each interval, whichever run RTE listed last under its horizon, without the run's label, and under D-1 and ID that mixed several runs. No endpoint serves them, and each run's history starts with the first read that stored it. Refresh: read into the store about every 4 hours. Every field is in the Data Dictionary §20a.
Aggregate and country gas storage (EU + DE, IT, FR, NL, AT). % full, TWh inventory and working capacity.
{ "aggregate":{ "currentLevel":44.7, "currentVolume":506, "workingCapacity":1131, "gasDayStart":"2026-06-15" },
"countries":[ { "id":"GIE_DE", "currentPct":36.8 } ] }
Source: GIE AGSI+. One value per gas day.
LNG terminal send-out (regasification to grid) and tank fullness. EU + ES, FR, NL, IT, BE.
{ "aggregate":{ "sendOut":3924, "sendOutUtil":49.5, "fullness":56.8, "dtrs":7936 },
"countries":[ { "id":"NL", "sendOut":773, "fullness":50.3 } ] }
Source: GIE ALSI+. Send-out in GWh/d — the LNG-to-grid demand signal.
Physical pipeline flows at major EU import arteries (Norway, Algeria, Azerbaijan/TAP, TurkStream), latest complete gas day.
{ "points":[ { "id":"mazara", "label":"Mazara del Vallo", "source":"Algeria", "flow":651 } ],
"total":3088, "gasDayStart":"2026-06-16" }
Source: ENTSOG Transparency Platform. Flows in GWh/d.
EU Allowance primary auction results by auctioning entity (EU, DE, PL, NIR), as EEX publishes them: one row per auction over the last 400 days, oldest first, with the clearing price, the volume auctioned, the cover ratio and the number of bidders. A cancelled auction is a row with cleared:false and a null price, never a gap.
{ "auctions":[ { "day":"2026-10-22", "zone":"EU", "cleared":true, "price":68.41,
"volume":3019500, "coverRatio":1.62, "bidders":24 } ],
"latestByZone":{ "EU":{ "day":"2026-10-22", "price":68.41 } }, "oldestAvailable":"2020-01-07",
"zoneCadence":{ "EU":{ "status":"live", "lastAuction":"2026-10-22", "windowDays":11 } } }
Source: EEX primary auction reports (public.eex-group.com), under EEX's written permission of 29 July 2026, which covers primary auction data only; attribution Source: EEX. No EUA spot or futures price is served, and an auction clearing price is not one. latestByZone holds each entity's newest cleared auction; zoneCadence and meta.areaStatus say whether each entity is publishing on its own observed cadence. Refresh: read once a day after the 09:00 to 11:00 CET auction window. Every field is in the Data Dictionary §13b.
Temperature, wind and precipitation forecast with HDD/CDD for demand modelling. 16 regions across Europe & the US.
{ "EU_DE":[ { "date":"2026-06-16", "forecastHigh":22, "forecastLow":10,
"deviation":-2.6, "hdd":1.8, "cdd":0, "windSpeed":13, "precipProb":8 } ]}
Source: ECMWF / Open-Meteo.
Real supply stack: installed capacity per fuel (ENTSO-E A68, yearly), currently active derates from outage messages (A77/A80), and the latest actual demand (A65). Live only when all three inputs are real — covered for DE, FR, ES, IT, NL, NO, SE, PL. Marginal costs are modeled assumptions, never a market quote.
{ "country":"DE", "capacityYear":2026, "demandMw":58400, "demandAt":"2026-07-17T09:00:00Z",
"clearingPrice":68, "totalAvailableMw":151200, "stack":[
{ "fuel":"gas", "installedMw":31800, "unavailableMw":2400, "availableMw":29400, "marginalCost":68 } ]}
Settled day-ahead price history per bidding zone from the archive (ENTSO-E A44) — the same series behind the live day-ahead endpoint, ~2 years deep and extended daily. Resolution follows the market (PT60M / PT15M, inferred per window); meta carries completeness and gaps.
{ "zone":"DE_LU", "oldestAvailable":"2024-07-17",
"points":[ { "t":1767225600000, "p":74.31 } ]}
Realized volatility derived from the settled day-ahead series: rolling 7/30/90-day sample standard deviation of day-over-day changes in the zone's daily baseload price, in ccy/MWh, never annualized. A derived measure, not a vendor series — EU power has no free options data, and log returns are unusable on prices that go negative.
{ "zone":"DE_LU", "oldestAvailable":"2024-07-17", "points":[
{ "date":"2026-07-16", "dateMs":1784160000000, "vol7d":9.8, "vol30d":12.4, "vol90d":15.1 } ]}
EU aggregate storage history (GIE AGSI+, held from 2015) with a real five-year band: for each display day, min/max/avg of the same calendar day across the five prior years — never the display window compared against itself. area defaults to EU; GIE_DE, GIE_IT, GIE_FR, GIE_NL and GIE_AT return that country's own level and band, each built from its own prior years. GIE_UK is refused with 400 and the reason: the UK series changed basis on 2020-12-30, so its prior years do not measure what a current UK reading measures.
{ "oldestAvailable":"2015-01-01", "points":[ { "date":"2026-07-16", "level":62.4,
"fiveYearAvg":68.1, "fiveYearMin":54.2, "fiveYearMax":77.9 } ]}
EU aggregate LNG send-out (GWh/d) and tank fullness (%) day by day (GIE ALSI+, held from 2015). Deliberately no five-year band: send-out is arbitrage-driven, so a seasonal "normal range" would overclaim.
{ "oldestAvailable":"2015-01-01", "points":[ { "date":"2026-07-16", "sendOut":3910, "fullness":55.9 } ]}
Benchmark commodity price history (FRED): Brent & WTI (USD/bbl, daily), Henry Hub (USD/MMBtu, daily), EU gas import price (USD/MMBtu, monthly), Australian coal (USD/mt, monthly). Held from 2015; oldestAvailable is declared per series, since cadences clamp at different depths.
{ "series":[ { "id":"brent", "freq":"daily", "points":[ { "t":"2026-07-16", "v":78.4 } ] } ],
"oldestAvailable":{ "brent":"2015-01-02", "eu_gas":"2015-01-01" } }
/v1/history/day-ahead,
/v1/history/n2ex-dayahead (paused, §03), /v1/history/gas-storage (EU or the requested area)
and /v1/history/lng-terminals accept as_of, an ISO 8601 instant with a timezone, for example
as_of=2026-10-01T12:00:00Z. A positive offset must be sent URL-encoded (%2B02:00), because a
bare + in a query string arrives as a space. The instant must exist on the calendar (2026-02-30 is refused)
and must not be in the future. The window then comes back as Voltstack had captured it at that instant: for each
delivery interval, the latest value captured at or before as_of, never the value held now.
data.pointInTime carries asOf, captureStartedAt (when capture of the series
began) and heldNotKnownAsOf, the count of intervals held today that had not been captured by
as_of. Those intervals are left out of points, not filled in. Capture runs forward from each
series' start and nothing before it is reconstructed, so an as_of earlier than the start is refused (§08)
with the start in the body. A window in which nothing had been captured by as_of is answered
200 with live:false, reason:"as-of-nothing-captured", an empty
points and pointInTime, so a client polling ahead of a first capture keeps polling; on
gas-storage and lng-terminals, reason:"as-of-only-implausible" says values had been captured but every one
failed the plausibility filter the latest-value read also applies. Under as_of, meta describes
what was held at that instant: dataAsOf is the newest data point captured by then (never the newest held
now) and dataAgeSeconds counts from the newest capture at or before as_of to as_of.
The gas-storage five-year band is null, because its years predate capture. An answer is final once as_of is
15 minutes old: a capture being written as the request arrives can still change an answer for a more recent instant,
so those are computed afresh on every request and never cached. realized-vol and
commodities refuse as_of rather than ignore it.The dislocation surface: physical constraint and event data behind the price series. Field-level detail for every payload is in the Data Dictionary §14–25. None of these endpoints carries a simulated fallback — when an upstream has nothing, the envelope says so.
Latest settled imbalance price per control area (ENTSO-E A85; deficit price on dual-category areas). The four German
control areas come from the netztransparenz AEP estimator and are withheld while that publisher confirms
redistribution rights: each reads paused in meta.areaStatus (§03). When ENTSO-E returns no
price for any of its five areas, nothing is left to serve: the answer is live: false with reason
upstream-error and data: null, and any German rows it withheld still read
paused.
German uniform imbalance price (AEP estimator, the near-real-time reBAP proxy): latest 15-minute value plus today's series. Source: netztransparenz.de. Paused since 24 September 2026: answers 451 (§03).
German redispatch: today's energy and measure count vs the trailing 30-day median, most-instructed plants, latest measures, and the live NRV system balance. Source: netztransparenz.de. Event archive from 2024-07. Paused since 24 September 2026: answers 451 (§03).
German balancing market: aFRR/mFRR capacity clearing prices per 4-h block, the PICASSO/MARI cross-border marginal price vs DE_LU day-ahead, and firings of the TSOs' own scarcity flag. Sources: regelleistung.net · netztransparenz.de. Paused since 24 September 2026: answers 451 (§03).
Core FBMC constraint radar: binding CNECs ranked by shadow price for the freshest delivery day, plus per-border MaxBex tightness vs the 30-day median of daily minima. Source: JAO Publication Tool. MaxBex archive 365d+, extended daily; the Nordic flow-based domain is ingested to the same store. Paused since 24 September 2026, bulk extracts of both domains included: answers 451 (§03).
REMIT urgent market messages, latest version per event, newest first, with active-now MW aggregates by area. Versions are retained as republished. Source: Nord Pool REMIT UMM.
French nuclear availability: forward D+90 curve derived from unit outage filings (fleet nominal minus deepest active outage per unit) plus the underlying outages. Source: ENTSO-E A77/A80, derived.
Norwegian reservoir fill vs the historical min/median/max band for the same ISO week, week-over-week delta, and the national deviation expressed in TWh. Source: NVE Magasinstatistikk, weekly, history from 1995.
GB balancing mechanism: the top-of-stack accepted offer and bid prices and the spread of that top-of-stack offer over the Market Index Price (MID), all read at one settlement period, NESO wind forecast-vs-outturn delta, next demand forecast, and today's most-dispatched units. Sources: Elexon Insights (BOD/BOALF/MID) · NESO Data Portal. The top-of-stack prices and the spread are not averages, so the acceptance weighting does not apply to them, and counting a segment twice never moved them. What changed for them from the date /coverage shows for GB balancing acceptances is in each day's last periods: those periods no longer take in the day before's segments that ran past midnight, and they now include the day's own segments that ran past midnight and were accepted after its last pass, so topOfStackOffer, topOfStackBid and bmMidSpread can differ there. A day's last periods are numbered 45 and 46 on the 46-period day in March and 49 and 50 on the 50-period day in October. Before that date the day before's segments went by period number, so on the day after the March change they were in periods 45 and 46, and on the October change day in periods 47 and 48, which can differ there too. A segment accepted after the day's last pass that ends by midnight is still not read. Stored values from before that date may be restated by a repair. The energy-weighted accepted offer and bid prices are not in this payload: the CSV export serves them per settlement period, and they change from that date as Methodology Guide §04 describes.
GB gas physicals: instantaneous entry flows by terminal, actual linepack with same-time-yesterday delta, demand by category, and the D-1 published NTS demand forecast. Source: National Gas Transmission Data Portal. GB only: Norwegian flows are not part of this or any payload; Norwegian system events are the next endpoint.
Norwegian gas system events: field and terminal unavailability published by Gassco as REMIT urgent market
messages, read from the UMM Atom feed at request time. Events only. Norwegian pipeline flows are not ingested,
and the aggregated exit nominations Gassco also publishes, stored for the Voltstack workspace since 24 September
2026, appear in no payload on this surface. Each message states its own capacity unit, so unavailable
capacity is reported per unit in activeNowUnavailable and never as one feed-level total; asOfMs
is the instant the active-now figures were evaluated at, and it travels with a cached or retained payload rather than
being restated. Every field is in the Data Dictionary §22a.
{ "eventCount":1, "activeNowCount":1, "asOfMs":1789387200000,
"activeNowUnavailable":[ { "unit":"mcm/d", "unavailable":18.2, "events":1 } ],
"events":[ { "messageId":"2026-304__________________003", "eventStatus":"Active", "unavailabilityType":"Planned",
"eventType":"Other unavailability", "unavailabilityReason":"Yearly maintenance", "assets":["Troll"],
"capacity":{ "unit":"mcm/d", "unavailable":18.2, "available":114.0, "technical":132.2 },
"eventStartMs":1789185600000, "eventStopMs":1789617600000, "publishedAtMs":1789184009020,
"balancingZones":["21Y000000000024I", "21Y0000000001278", "21YGB-UKGASGRIDW", "21YNL----TTF---1", "37Y701125MH0000I", "37Y701133MH0000P"],
"participant":"Gassco AS", "participantEic":"21X-NO-A-A0A0A-2" } ] }
Source: Gassco (umm.gassco.no). The example is the message the feed carried on 12 September 2026. An empty
events array with live:true is a real reading: Gassco publishes only when something happens.
data.events[] can carry a superseded message beside the revision that replaced it (same asset, same
window, the earlier one Dismissed) and nothing in the payload marks that relationship, so aggregate from
activeNowUnavailable, which counts Active messages only, never from the array. Cached 15 min;
when the upstream does not answer, a retained copy up to 24 h old is served with lastGood:true and its
original asOfMs. While such a copy exists a rate-limited and a failed upstream produce the same envelope:
reason is present only when no copy exists.
Forecast instability per zone and variable: D+1 run-to-run deltas and ensemble spread, percentile-ranked against each series' own trailing distribution, with honest history-day counts. Vintage-native: forecast runs cannot be backfilled.
GB N2EX settled day-ahead auction history (GBP/MWh) — the GB auction curve ENTSO-E never carries. oldestAvailable reports the true archive start; depth accrues daily. Source: Nord Pool N2EX data portal. Paused since 24 September 2026: answers 451 (§03).
DE battery revenue index for the reference 1 MW / 2 MWh configuration: monthly EUR/MW by stream (perfect-foresight arbitrage, aFRR/mFRR/FCR capacity) with per-stream archive starts. Methodology published.
Same engine, caller-supplied battery configuration (power, energy, round-trip efficiency, cycles/day). Beyond the surface access in §03, this endpoint additionally requires the calling tenant to be on a Professional or Enterprise plan.
Nightly reconciliation verdicts for the settled day-ahead archive against SMARD (Bundesnetzagentur) and Energy-Charts (Fraunhofer ISE): mean absolute difference and pass/fail per zone. Plain payload, not the standard envelope; also rendered on the public /status page.
Server-side caching is matched to each upstream's true publication cadence — polling faster than the TTL simply returns
the cached value with cached:true.
| Domain | Cache TTL | Underlying cadence |
|---|---|---|
| power/day-ahead | 5 min | Daily auction; 15-min delivery slots |
| power/flows | 5 min | Intraday physical flows |
| power/generation | 15 min | Realised actuals (≈1h lag) |
| gas/storage · gas/lng | 6 h | One value per gas day |
| gas/pipeline-flows | 6 h | Settled daily flows (D+1) |
| gas/norway-events | 15 min | Event-driven: Gassco publishes when an event occurs or is revised; a retained copy is served up to 24 h when the upstream does not answer |
| power/fr-generation-forecast | 30 min | RTE's horizons from D-3 to the current one, the aggregate run by run; read into the store about every 4 hours |
| carbon/eua-auctions | 30 min | One result per auction day and entity, published after the 09:00 to 11:00 CET window; read once a day |
| weather | 30 min | Several model runs per day |
| power/merit-order | 15 min | Capacity yearly · derates event-driven · load hourly |
| history/day-ahead | 15 min | Settled archive, extended daily |
| history/gas-storage · lng-terminals · realized-vol | 3 h | One value per gas / trading day |
| history/commodities | 6 h | FRED updates at most daily |
Voltstack applies two rate limits of its own, each counted per endpoint over a one-minute window. A keyed request
counts against its key: 120 requests a minute by default, with per-key overrides on request. A request carrying no key
counts against its client address, an IPv6 address by its /64: 60 requests a minute. Past either limit the response is 429 with a
Retry-After header in seconds, X-RateLimit-Limit and X-RateLimit-Remaining,
and a body of error: "rate-limited" with retryAfterSeconds. The counters live in the memory
of a single web instance and restart with it. Separately from both, the server-side TTL cache above means polling
faster than the TTL only returns cached values, and upstream back-off is a different signal: it surfaces as
200 with live:false and reason:"rate-limited", not as a 429. Fair use applies
during early access.
A transport error is a non-2xx status; a data fallback is 200 with live:false. A
request for a feed whose upstream is briefly unavailable still returns 200 with a reason, so
pipelines degrade gracefully rather than break.
| Status | Meaning |
|---|---|
| 200 | Success. Inspect live / reason for provenance. |
| 400 | Invalid zone/region/country parameter, or a malformed/out-of-order date range. JSON body, e.g. { "error": "unknown zone" }. An as_of the endpoint cannot answer (§06) is also a 400, with a detail string: invalid-as-of when the value is not an instant with a timezone, names a date that does not exist, or lies in the future; as-of-before-capture when it is earlier than the series' capture start, which the body names in captureStartedAt (null when the series was never captured); as-of-not-available on an endpoint that does not serve as_of. A window with nothing captured by as_of is not an error: it is a 200 with a reason (§06). |
| 401 | An invalid, revoked or expired data API key, on any endpoint that is not paused (451, below), refused before any parameter is validated, so it can arrive where a valid key would have received a 400. Or a request carrying no key that asks for history the sample tier cannot serve: a window ending at or before the 30-day floor, or a CSV export without explicit ISO from and to inside it (§03). Body: error: "api-key-required" with a detail string naming the key-request address. A valid key that is short on scope is a 403, below, never a 401. |
| 403 | Valid key, insufficient scope. The presented key verified but does not carry the scope this endpoint requires — keys are scoped history, current or all (§3), and the settled-history endpoints under /v1/history need history while every other documented path needs current. Body: error: "insufficient-scope" with a detail naming the required scope and the key's granted scopes, never the key itself. Retrying cannot succeed; scope extensions at research@voltstack.energy. |
| 404 | Unknown endpoint path. |
| 429 | Voltstack's own rate limit (§07): per key for a keyed request, per client address for a request carrying no key. Retry-After header in seconds; body error: "rate-limited" with retryAfterSeconds. |
| 451 | Unavailable for legal reasons: the endpoint's dataset is paused while Voltstack confirms redistribution rights with its publisher (§03 names the five paths). Answered to every request on a paused path, keyed or not, before any credential or parameter is checked. Body: { "error": "unavailable", "reason": "This dataset is paused while we confirm redistribution rights with its publisher." }. Retrying cannot succeed while the pause stands; a revision of this document records when it is lifted. |
| 5xx | Voltstack-side error. Safe to retry with backoff. |
| Domain | Coverage | Source | Status |
|---|---|---|---|
| Day-ahead price | 11 bidding zones | ENTSO-E | LIVE |
| Cross-border flows | 12 interconnectors | ENTSO-E | LIVE |
| Generation mix | 9 countries · 7 fuels | ENTSO-E · Elexon (GB) | LIVE |
| Gas storage | EU + 6 countries | GIE AGSI+ | LIVE |
| LNG send-out | EU + 5 countries | GIE ALSI+ | LIVE |
| Pipeline flows | 8 import arteries | ENTSOG | LIVE |
| Weather / HDD-CDD | 16 regions | ECMWF / Open-Meteo | LIVE |
| Day-ahead history | 11 zones · ~2 yr settled | ENTSO-E A44 (archive) | LIVE |
| Realized volatility | 11 zones · 7/30/90-day windows | Derived from settled A44 | LIVE |
| Merit-order stack | 8 countries · capacity, derates, demand | ENTSO-E A68 / A77+A80 / A65 | LIVE |
| Gas storage history | EU agg + 5-yr band · since 2015 | GIE AGSI+ (archive) | LIVE |
| LNG send-out history | EU aggregate · since 2015 | GIE ALSI+ (archive) | LIVE |
| Commodity benchmarks | 5 series · since 2015 | FRED | LIVE |
| Imbalance prices | 5 EU areas · the DE rows (uniform reBAP proxy) withheld | ENTSO-E A85 · netztransparenz (paused) | LIVE |
| DE imbalance, grid & balancing stress | AEP estimator, redispatch, NRV, capacity prices, CBMP, scarcity flag | netztransparenz · regelleistung.net | PAUSED |
| Flow-based constraints | Core and Nordic domains stored, not served | JAO Publication Tool | PAUSED |
| REMIT urgent messages | Versioned event stream | Nord Pool UMM | LIVE |
| FR nuclear availability | Forward D+90 derived curve | ENTSO-E A77/A80 | LIVE |
| Nordic hydro balance | Weekly · history from 1995 | NVE | LIVE |
| GB balancing mechanism | Acceptances, MID spread, wind delta | Elexon · NESO | LIVE |
| GB gas physicals | Flows, linepack, forecast | National Gas | LIVE |
| GB day-ahead auction history | Settled N2EX auction, GBP/MWh | Nord Pool N2EX | PAUSED |
| Norwegian gas system events | Field & terminal unavailability (REMIT UMM) · events, not flows | Gassco (umm.gassco.no) | LIVE |
| Forecast risk + DE BESS index + cross-validation | Vintage-native · public benchmark · nightly verdicts | Open-Meteo · derived · SMARD/Energy-Charts | LIVE |
| EUA primary auctions | EU ETS auction clearing prices · history from 2020 | EEX (licensed) | LIVE |
| FR generation forecast | Wind, solar & purchase-obligation aggregate · per horizon, D-3 to current; the aggregate per RTE run | RTE (France) | LIVE |
| EUA / UKA spot & forwards | EU ETS · UK ETS | Exchange-licensed | ENTERPRISE |
| TTF / NBP gas curves | Forward term structure | Exchange-licensed | ENTERPRISE |
| Brent / WTI oil | Forward term structure | Exchange-licensed | ENTERPRISE |
/coverage shows for each of
those two series, shown there even before either one holds an actual revision: every change our re-reads have seen
is retained as a later version, dated to when Voltstack captured it, since neither source gives its own publication
time for a correction. For a GB balancing acceptance the versions follow Elexon's acceptance (BOALF) itself: a
change to its level from or level to, segment end, national grid unit name, closing settlement period or SO, STOR
or RR flag is a version, while its side and matched offer or bid price, Voltstack's own match against Elexon's
bid-offer data (BOD), never make one and are corrected in place on the latest version when better BOD data arrives.
Version 1 of a redispatch measure carries the measure's own window start as published_at; version 1 of a GB
balancing acceptance carries Elexon's own acceptance time. Only a later version of either is dated to when we
captured it. An append-only vintage sidecar records the four forecast feeds (weather, forecast risk, GB NESO forecast, FR RTE forecast) at every
capture pass since 2026-07-31, and the JAO flow-based domains whenever a value changes. RTE's purchase-obligation
aggregate is recorded run by run from v1.21; what was recorded of it before then carried no run label, and mixed
runs under D-1 and ID, so it is not served. It also records day-ahead
prices (ENTSO-E and GB N2EX), imbalance prices, the German AEP estimator and NRV balance, actual generation and load,
AGSI+ storage, ALSI+ LNG and cross-border physical flows: once when a value is first captured and again whenever it
changes, never on an identical re-fetch. The instant recorded is when Voltstack held the value, not the publisher's
own timestamp. Capture runs forward from each series' own start, which the as_of read reports; nothing earlier
exists or will be backfilled. The settled-history endpoints read these vintages through as_of (§06).
Capture continues for the datasets paused in §03; their values are held, not served.v1.21 · 2026-10-25:
/v1/power/fr-generation-forecast serves RTE's purchase-obligation aggregate one RTE run at a time. Its horizon names
the run after a slash (D-1/DA02, ID/ID07), as RTE's sub_type does; before, several runs were stored under one
horizon and the last one RTE listed kept for each interval. Aggregate rows stored before this revision are no longer
served, here or as recorded revisions; §07, §09 and the point-in-time note say so. Wind and solar are unchanged,
and no other endpoint, field or status code changed.
v1.20 · 2026-10-25:
/v1/carbon/eua-auctions now names its attribution as Source: EEX, the line the platform shows beside the same data;
Source: EEX Group belongs to the EEX DataSource data a desk brings itself. §01 and §09 name RTE's purchase-obligation
aggregate rather than an aggregate, as the /v1/power/fr-generation-forecast entry already did. No endpoint, field or
status code changed.
v1.19 · 2026-10-23: §06 gains
/v1/power/fr-generation-forecast, RTE's French generation forecast, and /v1/carbon/eua-auctions, EEX's EUA primary
auction results, each served from the store the terminal already reads, with a row each in §07. §01 and §09 no
longer say their /v1 paths follow. No other endpoint, field or status code changed.
v1.18 · 2026-10-01: Publisher
details: Voltstack Limited, Dublin, Ireland. No endpoint, field or status code changed.
v1.17 · 2026-09-28: the
point-in-time capture note no longer says German redispatch measures and GB balancing acceptances are
version-captured with a publication timestamp since 2026-07-14, since neither source gives one. It points each at
the date /coverage shows for it, says what version 1 of each carries as published_at (the measure's own
window start, or Elexon's own acceptance time), and says which fields of a GB acceptance make a version and that its
matched price and side are corrected in place. REMIT UMMs and unit outages are unchanged. §06's
/v1/power/gb-balancing entry now describes its figures as they are computed: the top-of-stack accepted offer and bid
prices and the top-of-stack offer's spread over MID (it read "accepted offer/bid spread vs MID"), none of them an
average, so the acceptance weighting does not apply to them and a segment counted twice never moved them. It says
what changed for them from the date /coverage shows for GB balancing acceptances, in each day's last
periods: those no longer take in the day before's segments that ran past midnight and now include the day's
own segments that ran past midnight and were accepted after its last pass, so topOfStackOffer, topOfStackBid and
bmMidSpread can differ there, and a repair may restate the values stored before that date. It gives the numbers of
a day's last periods on the 46- and 50-period days, and says that the day before's segments had gone into periods 45
and 46 on the day after the March change and 47 and 48 on the October change day, which can differ there too. It
says that a segment accepted after the day's last pass that ends by midnight is still not read. It points to the CSV export for the energy-weighted accepted prices, which change
from that date as Methodology Guide §04 describes. No endpoint, field or status code changed.
v1.16 · 2026-09-24: §06's
/v1/power/imbalance entry says what the endpoint answers when ENTSO-E returns no price for any of its five areas.
Nothing is then left to serve, so the answer is live false with reason upstream-error and data null, and any German
rows it withheld still read paused in meta.areaStatus. It could previously come back live with an empty areas map.
No endpoint, field or status code changed.
v1.15 · 2026-09-24: /v1/gas/norway-events no longer says the Gassco nominations are not ingested.
Gassco confirmed on 15 September that they are covered and Voltstack now stores them for the workspace, but they
appear in no payload on this surface, and Norwegian pipeline flows are still not ingested. No endpoint, field or
error changed.
v1.14 · 2026-09-24: five endpoints
are paused while Voltstack confirms redistribution rights with their publishers, on a founder ruling of this date:
/v1/power/imbalance-de, /v1/power/grid-stress and /v1/power/balancing-stress (netztransparenz.de),
/v1/power/constraints (JAO) and /v1/history/n2ex-dayahead (Nord Pool N2EX). Each answers every request, keyed or
not, with 451 and error "unavailable". §03 states the pause and its date, §06 marks the five PAUSED, and §08 gains
the 451 row while its 401 row now excludes paused endpoints. /v1/power/imbalance keeps its ENTSO-E areas and
withholds the four German rows, which read paused in meta.areaStatus, a value §04 now lists. §09 splits GB gas
physicals from the N2EX auction history and marks the netztransparenz and JAO rows paused. The cover ticker no
longer prints a GB N2EX price.
v1.13 · 2026-09-23: §06 documents
as_of on the four captured settled-history endpoints (day-ahead, n2ex-dayahead, gas-storage with its area,
lng-terminals): the window as captured at an instant, read from the vintage sidecar and never from the latest values,
with data.pointInTime; the refusal for an instant before capture; the 200 live:false answer, with reason
as-of-nothing-captured or as-of-only-implausible, for a window with nothing to serve; what meta.dataAsOf and
meta.dataAgeSeconds describe on that read; that an answer is final once as_of is 15 minutes old; the %2B encoding a
positive offset needs; and that a calendar date which does not exist is refused. realized-vol and commodities refuse
as_of instead of ignoring it. §08's 400 row names the three as_of error codes. §09's point-in-time note lists the
price and fundamentals series now captured on change and no longer says the as-of API is outside this surface.
v1.12 · 2026-09-23: §04 gains meta.dataAsOf, the
instant of the newest data point behind a payload's headline values. The public /status page now ages each feed from it
against that series' own publication cadence, where it used to print the age of the cache fill, so a series older than
its cadence allows reads stale rather than live. meta.dataAgeSeconds keeps its values; its description now says what it
measures, the last write of the rows, which a re-fetch of unchanged values resets. On /v1/gas/pipeline-flows each
point also carries the gas day its flow belongs to (Data Dictionary §9).
v1.11 · 2026-09-23: §06's
/v1/history/gas-storage gains the optional area parameter, so a country's fill is compared with that country's own
five-year band. The product's storage overlay needed it: it had compared a country's current fill with a fixed
February norm. The EU default is unchanged; GIE_UK is refused with the reason stated above.
v1.10 · 2026-09-22: §03 described
a surface key-gated in full, and production stopped matching it when the anonymous sample tier was switched on:
measured 22 September 2026, 28 of the 29 documented paths answer a request carrying no key with 200 and a sample
block, and /custom refuses it for want of a signed-in tenant. §03 now states the two tiers as measured and dated, and
§05's history-window row follows. §03's at-rest sentence overreached (F-252): only keys issued as rows are held as a
digest, while an entry on the break-glass list may be the token itself; the list now also accepts a digest form and
the section says which is which. §07 said there was no per-key rate limit and no 429 from Voltstack; a per-key limit
had shipped (F-334), and a per-address limit on requests carrying no key ships with this revision, so §07 documents
both and §08 gains a 429 row. §08's 401 row is rewritten to the two refusals that remain. The v1.6 to v1.9 statements
that every request carrying no key is refused are superseded.
v1.9 · 2026-09-13: §06 gains
/v1/gas/norway-events, the Gassco REMIT UMM events feed live at /api/feeds/gassco since 13 September 2026, with the
payload documented field by field in Data Dictionary §22a; §01, §07 and §09 name it. Events only: the endpoint carries no
Norwegian flow or nomination data and the coverage matrix says so. Two payload facts are stated because a reader would
otherwise assume the opposite: capacity units are per message so there is no feed-level total, and data.events[] can
carry a superseded message beside its revision so aggregation belongs on activeNowUnavailable. Documentation catch-up:
no behaviour changed in this revision.
v1.8 · 2026-08-27: §03 documents key
scopes (history, current, all) and the 403 refusal a valid key receives on an endpoint its scopes do not cover, with the
served insufficient-scope body printed; §08 gains the matching 403 row and the 401 row now points the short-scope case at
it. Documentation catch-up: the shipped gate already refused short-scoped keys this way, and no behaviour changed in this
revision.
v1.7 · 2026-08-09: the §06 de-bess entry
said "Public, no auth", which contradicted §03 inside this document and was false against the shipped gate, measured 401
unkeyed on every documented /v1 path. Removed: no §06 entry now states its own access rule, because access is set for the
surface and §03 is where it is stated. The de-bess/custom entry now says its Professional-plan requirement is additional to
§03 rather than an alternative to it.
v1.6 · 2026-08-08: the §08 401 row said
"history endpoints only", which v1.5 left standing when §03 withdrew the same claim. Every documented /v1 path refuses a
keyless request, current feeds included, so §08 now says so and §03 states the observed gating rather than staying silent
on it. The "keyless /v1" wording in the v1.3 note below is superseded on both counts.
v1.5 · 2026-08-08: §03 withdraws the
keyless-surface scope claim, which production replaced, and documents the shipped key mechanics instead (vsk_live_ token format,
per-key identity, hashed at rest); the 401 example now prints the body the service actually returns. §09 corrects gas storage to
six countries and weather to sixteen regions, and the §06 weather line follows; in each case one served id had never been documented,
and the data dictionary §7 and §27 now name it.
v1.4 · 2026-07-31: document version and date
published as machine-readable meta tags (voltstack-doc-version, voltstack-doc-updated) and printed in the page header; the PDF edition is
served alongside the HTML at /docs/.
v1.3 — 2026-07-31: authentication rewritten to the shipped
key model (keyless /v1; X-API-Key on history endpoints); day-ahead and flows payload examples corrected to the served shapes; error and
rate-limit semantics corrected; envelope lastGood, areaStatus and the real reason enum documented; EUA primary auctions and the RTE FR
generation forecast added to coverage. Specification subject to change during early access. Field names and coverage reflect the
platform as at 2026-07-31. © 2026 Voltstack Limited.