Building a Synthetic Smart-Meter Dataset Generator with Energy API for Safe Developer Testing and Machine-Learning Model Training
Smart-meter data is the backbone of many energy products: tariff optimization tools, carbon-aware scheduling, DER orchestration, billing simulators, and machine-learning models that forecast demand or predict cost. But working directly with real smart-meter streams is often off-limits. Privacy risks, contracts, and compliance friction can delay projects for months, and even when you do secure a feed, it’s rarely consistent across countries, retailers, and time granularities. That leaves developers in a bind: how do you build and test energy features today, safely, without waiting on data governance or scraping half a dozen national portals?
This post shows how to build a synthetic smart-meter dataset generator that pairs realistic load profiles with wholesale electricity prices and carbon-intensity series—so you can safely test developer workflows and train initial ML models without exposing any personal data. We’ll use the unified JSON interface from Energy API to fetch day-ahead curves, hourly PVPC retail references for Spain, carbon intensity, and historical price series across electricity, gas, oil, coal, and carbon. Then we’ll combine those signals with a stochastic usage model to mint a full, lifelike synthetic dataset suitable for local testing, staging environments, and baseline ML training.
The result is a repeatable, region-flexible pipeline that produces smart-meter-like rows with timestamps, 15-min or hourly consumption, cost estimations, and carbon footprint. You’ll learn how to pick the right endpoints, stitch data sources, and implement reliability patterns (retries, health checks, circuit breakers) that move cleanly from prototyping to production.
Why Energy API
When you’re building an energy feature, the hardest problem is rarely the model or the UI. It’s data plumbing: Where do I get the day-ahead curve? How do I align carbon intensity with usage? Will my symbol naming be consistent across markets? How do I consolidate different time granularities without writing a tangle of one-off scrapers? Energy API solves that by normalizing electricity, natural gas, crude oil, coal, carbon allowances, and grid carbon intensity from official sources into one stable JSON schema.
Developer advantages you’ll notice immediately:
- One normalized REST surface replaces OMIE, ENTSO-E, EIA/FRED, ESIOS, and more. No more juggling formats, schedules, or symbol conventions. You query electricity intraday curves and carbon intensity using the same field shapes you use for oil and gas prices.
- Ship features in hours, not weeks of ETL work. The schema is unified across 39+ symbols and 16 endpoints, covering spot prices, historical series, intraday curves, OHLC candles, day-ahead forecasts, cost estimates, and health status. You can query multiple commodities in a single call (for example, BRENT_CRUDE, TTF_GAS, and EUA_CO2 together) to drive dashboards and risk models with minimal joins.
- Intraday electricity curves where sources publish them (15-min or hourly). For synthetic meter generation, that means you can anchor your stochastic load profiles to the actual day-ahead price shape or PVPC hourly tariffs for a given date.
- Operational ergonomics baked in. Consistent error shapes, explicit provider status, and deterministic day-ahead forecast lookups let you implement robust retries, backoff, and circuit breakers easily—so your synthetic data pipeline keeps flowing even when a national portal has an intermittent outage.
Quick Start
Base URL: https://energy-api.com/api/v1
Authentication: pass an api_key as a query parameter. In examples below, replace YOUR_API_KEY with your token.
Let’s pull the most recent prices for multiple commodities in a single call. This is useful for simulating cross-commodity risk or enriching smart-meter rows with contextual energy market signals.
curl -G https://energy-api.com/api/v1/latest \
--data-urlencode "symbols=BRENT_CRUDE,TTF_GAS,EUA_CO2,OMIE_ES_DA" \
--data-urlencode "api_key=YOUR_API_KEY"
Example JSON response:
{
"success": true,
"date": "2026-06-11",
"base": "MIXED",
"rates": {
"BRENT_CRUDE": 74.82,
"TTF_GAS": 38.15,
"EUA_CO2": 67.40,
"OMIE_ES_DA": 86.42
},
"dates": {
"BRENT_CRUDE": "2026-06-11",
"TTF_GAS": "2026-06-11",
"EUA_CO2": "2026-06-11",
"OMIE_ES_DA": "2026-06-11"
},
"currencies": {
"BRENT_CRUDE": "USD",
"TTF_GAS": "EUR",
"EUA_CO2": "EUR",
"OMIE_ES_DA": "EUR"
}
}
Key fields:
- success: Boolean indicating success.
- date: The reference date for the returned latest values.
- rates: A symbol-to-number map of latest price values.
- dates: Per-symbol publishing date (useful if one market lags others).
- currencies: Per-symbol currency code to ensure display and conversion accuracy.
With a single call you get coherent context across oil, gas, carbon, and electricity. For synthetic smart-meter generation, you can store the electricity price baseline (e.g., OMIE_ES_DA) and the carbon allowance level (EUA_CO2) alongside each day’s synthetic load for downstream analysis.
Core Endpoints for a Synthetic Smart‑Meter Generator
A realistic synthetic dataset needs three ingredients: (1) an intraday electricity price curve to shape incentives and time-of-use costs; (2) a carbon-intensity series to estimate emissions; and (3) historical context to randomize realistic volatility. Below are the primary endpoints you’ll use, with example requests, responses, and field explanations. We’ll also include a fluctuation analysis endpoint for scenario generation and a symbol discovery endpoint to make your generator portable across regions.
1) Discover Symbols: GET /symbols
Use this to enumerate symbols by category or provider and adapt your generator to new geographies. For example, discover European gas (TTF_GAS) or Spanish day-ahead power (OMIE_ES_DA) without hardcoding.
curl -G https://energy-api.com/api/v1/symbols \
--data-urlencode "category=electricity" \
--data-urlencode "api_key=YOUR_API_KEY"
Example JSON response:
{
"success": true,
"count": 4,
"symbols": [
{
"symbol": "OMIE_ES_DA",
"name": "OMIE Spain Day-Ahead Electricity",
"category": "electricity",
"country_code": "ES",
"currency_code": "EUR",
"frequency": "daily",
"description": "OMIE day-ahead price for Spain."
},
{
"symbol": "EPEX_DE_DA",
"name": "EPEX Germany Day-Ahead Electricity",
"category": "electricity",
"country_code": "DE",
"currency_code": "EUR",
"frequency": "daily",
"description": "EPEX day-ahead price for Germany."
},
{
"symbol": "PVPC_ES_2TD",
"name": "Spain PVPC Retail Reference (2 Periods)",
"category": "electricity",
"country_code": "ES",
"currency_code": "EUR",
"frequency": "hourly",
"description": "ESIOS PVPC hourly retail reference."
},
{
"symbol": "AEMO_NSW1",
"name": "AEMO NSW1 Electricity",
"category": "electricity",
"country_code": "AU",
"currency_code": "AUD",
"frequency": "5min",
"description": "AEMO NSW1 operational price."
}
]
}
Field meanings:
- symbol, name: Stable identifier and human-readable label you’ll store in config.
- country_code, currency_code: Helpful for regionalizing your generator defaults.
- frequency: Typical publish cadence; informs your resampling logic.
- description: Quick context for dashboards and admin tools.
2) Intraday Electricity Curve: GET /electricity/hourly
Fetch the full curve for a specific symbol and date. This is the cornerstone for shaping time-of-use costs in your synthetic meter rows. Where sources publish 15-min granularity, the endpoint returns that; otherwise hourly.
curl -G https://energy-api.com/api/v1/electricity/hourly \
--data-urlencode "symbol=OMIE_ES_DA" \
--data-urlencode "date=2026-06-11" \
--data-urlencode "api_key=YOUR_API_KEY"
Example JSON response:
{
"success": true,
"symbol": "OMIE_ES_DA",
"date": "2026-06-11",
"currency": "EUR",
"granularity": "hourly",
"curve": [
{"time": "2026-06-11T00:00:00Z", "price": 72.10},
{"time": "2026-06-11T01:00:00Z", "price": 69.55},
{"time": "2026-06-11T02:00:00Z", "price": 66.40},
{"time": "2026-06-11T03:00:00Z", "price": 64.90},
{"time": "2026-06-11T04:00:00Z", "price": 65.25},
{"time": "2026-06-11T05:00:00Z", "price": 68.20},
{"time": "2026-06-11T06:00:00Z", "price": 76.40},
{"time": "2026-06-11T07:00:00Z", "price": 85.10},
{"time": "2026-06-11T08:00:00Z", "price": 92.85},
{"time": "2026-06-11T09:00:00Z", "price": 88.70},
{"time": "2026-06-11T10:00:00Z", "price": 83.50},
{"time": "2026-06-11T11:00:00Z", "price": 82.15},
{"time": "2026-06-11T12:00:00Z", "price": 81.20},
{"time": "2026-06-11T13:00:00Z", "price": 80.10},
{"time": "2026-06-11T14:00:00Z", "price": 78.75},
{"time": "2026-06-11T15:00:00Z", "price": 79.90},
{"time": "2026-06-11T16:00:00Z", "price": 84.30},
{"time": "2026-06-11T17:00:00Z", "price": 94.60},
{"time": "2026-06-11T18:00:00Z", "price": 102.40},
{"time": "2026-06-11T19:00:00Z", "price": 98.50},
{"time": "2026-06-11T20:00:00Z", "price": 90.30},
{"time": "2026-06-11T21:00:00Z", "price": 84.10},
{"time": "2026-06-11T22:00:00Z", "price": 78.25},
{"time": "2026-06-11T23:00:00Z", "price": 74.80}
]
}
Use curve to shape synthetic consumption: draw a base load, add day-of-week and temperature effects, then apply price-elastic adjustments so flexible loads (EV charging, water heating) shift into cheaper hours. Because the response includes precise timestamps, it’s straightforward to left-join cost into your generated rows and compute per-interval charges.
3) Historical Series: GET /timeseries
For machine-learning pretraining and robust testing, you’ll want several months of historical context. Pull a date-bounded series for one or more symbols and use it to parameterize your randomization (e.g., volatility regimes, seasonality, weekend/holiday profiles).
curl -G https://energy-api.com/api/v1/timeseries \
--data-urlencode "start=2025-12-01" \
--data-urlencode "end=2026-02-28" \
--data-urlencode "symbols=OMIE_ES_DA,EUA_CO2,TTF_GAS" \
--data-urlencode "api_key=YOUR_API_KEY"
Example JSON response:
{
"success": true,
"base": "MIXED",
"start_date": "2025-12-01",
"end_date": "2026-02-28",
"rates": {
"OMIE_ES_DA": {
"2025-12-01": 84.70,
"2025-12-02": 86.10,
"2025-12-03": 81.95
},
"EUA_CO2": {
"2025-12-01": 65.35,
"2025-12-02": 65.90,
"2025-12-03": 66.10
},
"TTF_GAS": {
"2025-12-01": 41.20,
"2025-12-02": 42.50,
"2025-12-03": 43.10
}
},
"frequencies": {
"OMIE_ES_DA": "daily",
"EUA_CO2": "daily",
"TTF_GAS": "daily"
},
"currencies": {
"OMIE_ES_DA": "EUR",
"EUA_CO2": "EUR",
"TTF_GAS": "EUR"
}
}
Practical notes:
- rates: A per-symbol map keyed by ISO date. It’s ideal for rolling-window stats and train/validation splits.
- frequencies: Use this to resample or align with your synthetic interval (15-min/hourly) when feature engineering.
- currencies: Ensures consistent display and unit handling in multi-symbol charts or reports.
4) Carbon Intensity: GET /carbon-intensity
Augment each synthetic interval with emissions in gCO2eq/kWh. This lets you build “carbon-aware” features and train models that internalize emissions signals directly.
curl -G https://energy-api.com/api/v1/carbon-intensity \
--data-urlencode "country=DE" \
--data-urlencode "api_key=YOUR_API_KEY"
Example JSON response:
{
"success": true,
"country": "DE",
"unit": "gCO2eq/kWh",
"series": [
{"time": "2026-06-11T00:00:00Z", "value": 348},
{"time": "2026-06-11T01:00:00Z", "value": 340},
{"time": "2026-06-11T02:00:00Z", "value": 332}
],
"source": "ENTSO-E / Ember"
}
Map series.value to your synthetic kWh to compute per-interval emissions and cumulative daily totals. If you’re generating data for multiple countries, call this endpoint per country and attach the relevant carbon intensity at generation time.
5) Fluctuation Analysis: GET /fluctuation
Generate scenario envelopes quickly. For example, you might boost or dampen your synthetic price sensitivities based on recent volatility bands over a time window.
curl -G https://energy-api.com/api/v1/fluctuation \
--data-urlencode "start=2026-05-01" \
--data-urlencode "end=2026-06-01" \
--data-urlencode "symbols=OMIE_ES_DA,EUA_CO2" \
--data-urlencode "api_key=YOUR_API_KEY"
Example JSON response (shape excerpt):
{
"success": true,
"base": "MIXED",
"period": {
"start": "2026-05-01",
"end": "2026-06-01"
},
"symbols": {
"OMIE_ES_DA": {
"start_value": 81.40,
"end_value": 86.42,
"change": 5.02,
"change_pct": 6.17
},
"EUA_CO2": {
"start_value": 64.10,
"end_value": 67.40,
"change": 3.30,
"change_pct": 5.15
}
}
}
Use change_pct as a multiplier for “stress testing” your synthetic generation—e.g., shift flexible load more aggressively if recent volatility is high.
Optional but Useful Category Endpoints
Two more endpoints are especially helpful when focusing on Spain or performing quick unit-cost experiments.
- GET /electricity/pvpc — Hourly PVPC retail references. Handy for retail-oriented synthetic bills, not just wholesale day-ahead. Pair this with your generator to simulate regulated tariffs on synthetic profiles.
- POST /cost-estimate — A quick monthly estimate: latest price × kWh/month. This is a coarse tool for setting bounds in your scenario generator before you run full hourly costing.
Also consider GET /status to track provider health. Use it for circuit breakers and to pick fallback strategies (e.g., reuse the last known curve if today’s fetch is delayed).
Putting It Together: A Synthetic Smart‑Meter Dataset Generator
Below is a practical approach for combining Energy API signals with a stochastic load model to generate a privacy-safe dataset that looks and behaves like real smart-meter data. We will:
- Select region and symbols via GET /symbols so our generator works across countries.
- Fetch intraday electricity curve per day via GET /electricity/hourly to drive time-of-use costing and price-elastic shifts.
- Pull recent /timeseries for electricity, gas, and EUA to shape volatility and seasonality.
- Retrieve carbon intensity for the region via GET /carbon-intensity and compute per-interval emissions.
- Optionally compare PVPC retail references for Spain via GET /electricity/pvpc.
- Use /status to gate the pipeline and apply retries/backoff (429) or previous-day fallbacks.
A compact Python example illustrates the main ideas. The code fetches one day’s hourly curve, generates a synthetic household profile with daily base load, peak behavior, and price-responsive shifting, then computes interval cost and emissions.
import os
import math
import json
import time
import random
import datetime as dt
from urllib.parse import urlencode
import urllib.request
BASE_URL = "https://energy-api.com/api/v1"
API_KEY = "YOUR_API_KEY" # store securely in env/secret manager in real projects
def get_json(path, params):
q = urlencode({**params, "api_key": API_KEY})
url = f"{BASE_URL}{path}?{q}"
for attempt in range(5):
try:
with urllib.request.urlopen(url, timeout=15) as resp:
data = json.loads(resp.read().decode("utf-8"))
if not data.get("success", False):
raise RuntimeError(data.get("error", "Unknown error"))
return data
except urllib.error.HTTPError as e:
# Basic backoff on transient errors like 429/5xx; inspect body for details
if e.code in (429, 500, 502, 503, 504):
time.sleep(2 ** attempt * 0.5)
continue
raise
raise RuntimeError("Max retries exceeded")
def fetch_hourly_curve(symbol, date_str):
return get_json("/electricity/hourly", {"symbol": symbol, "date": date_str})
def fetch_carbon_intensity(country):
return get_json("/carbon-intensity", {"country": country})
def fetch_timeseries(start, end, symbols):
return get_json("/timeseries", {"start": start, "end": end, "symbols": ",".join(symbols)})
def price_elastic_shift(load_vector, price_vector, elasticity=0.12):
# Simple heuristic: reduce load in above-median hours, shift to below-median hours
assert len(load_vector) == len(price_vector)
n = len(load_vector)
median_price = sorted(price_vector)[n // 2]
shifted = load_vector[:]
budget = 0.0
for i in range(n):
if price_vector[i] > median_price:
cut = shifted[i] * (elasticity * (price_vector[i] / (median_price + 1e-9) - 1.0))
cut = min(cut, shifted[i] * 0.35) # cap
shifted[i] -= cut
budget += cut
# redistribute budget into cheapest hours
cheap_hours = sorted(range(n), key=lambda i: price_vector[i])[:max(1, n // 3)]
for i in cheap_hours:
add = budget / len(cheap_hours)
shifted[i] += add
return shifted
def generate_synthetic_profile(date_str, country="ES", symbol="OMIE_ES_DA", seed=42):
random.seed(seed)
curve = fetch_hourly_curve(symbol, date_str)
prices = [pt["price"] for pt in curve["curve"]]
hours = [pt["time"] for pt in curve["curve"]]
# Base daily kWh for a small home; add weekend uplift/downgrade
date = dt.datetime.fromisoformat(date_str)
is_weekend = date.weekday() >= 5
daily_kwh = 8.0 * (1.10 if is_weekend else 1.0)
# Hourly base shape: night low, morning ramp, midday moderate, evening peak
base_shape = [0.25,0.22,0.20,0.20,0.22,0.28,0.40,0.55,0.50,0.40,0.38,0.35,
0.34,0.34,0.36,0.45,0.65,0.80,0.90,0.75,0.60,0.50,0.40,0.32]
# Normalize to sum=1
s = sum(base_shape)
base_shape = [x / s for x in base_shape]
# Add random noise and mild temperature proxy via timeseries context
ctx = fetch_timeseries(
start=(date - dt.timedelta(days=14)).strftime("%Y-%m-%d"),
end=date_str,
symbols=["OMIE_ES_DA","TTF_GAS","EUA_CO2"]
)
vol_hint = abs(ctx["rates"]["OMIE_ES_DA"][sorted(ctx["rates"]["OMIE_ES_DA"].keys())[-1]] -
ctx["rates"]["OMIE_ES_DA"][sorted(ctx["rates"]["OMIE_ES_DA"].keys())[0]])
vol_factor = 1.0 + min(0.20, vol_hint / 100.0)
raw_load = []
for h in range(24):
noise = random.gauss(0, 0.04)
hour_factor = 1.0 + (0.15 if h in (7,8,18,19,20) else 0.0)
raw_load.append(base_shape[h] * hour_factor * max(0.5, 1.0 + noise) * vol_factor)
# Scale to daily kWh
scale = daily_kwh / sum(raw_load)
raw_load = [x * scale for x in raw_load]
# Apply price-elastic shifting
shifted = price_elastic_shift(raw_load, prices, elasticity=0.15)
# Carbon intensity
ci = fetch_carbon_intensity(country)
ci_map = {pt["time"]: pt["value"] for pt in ci["series"]}
rows = []
for i, ts in enumerate(hours):
kwh = shifted[i]
eur_mwh = prices[i]
eur_kwh = eur_mwh / 1000.0
cost_eur = kwh * eur_kwh
intensity = ci_map.get(ts)
emissions_g = (kwh * intensity) if intensity is not None else None
rows.append({
"time": ts,
"kwh": round(kwh, 3),
"price_EUR_MWh": round(eur_mwh, 2),
"cost_EUR": round(cost_eur, 4),
"carbon_intensity_gCO2eq_per_kWh": intensity,
"emissions_gCO2eq": round(emissions_g, 1) if emissions_g is not None else None
})
return rows
if __name__ == "__main__":
synthetic = generate_synthetic_profile("2026-06-11", country="ES", symbol="OMIE_ES_DA", seed=123)
print(json.dumps(synthetic, indent=2))
What this gives you:
- Realistic, privacy-safe hourly intervals with kWh, cost, and emissions—grounded to an actual day’s wholesale price shape and a country’s carbon intensity.
- A controllable lever for price responsiveness (elasticity), enabling A/B tests for demand flexibility features and carbon-aware scheduling simulations.
- Hooks to extend to 15-min data where available (use the same /electricity/hourly endpoint; granularity will reflect the source).
Production notes:
- Add a /status pre-check to short-circuit the daily run if any provider shows a red status; use cached curves or the previous day’s curve in such cases.
- Implement exponential backoff on 429, and respect 404 (no data) for non-publishing days—your generator should shift to the most recent available curve or skip a day as needed.
- Store the symbol metadata from /symbols with your outputs so downstream consumers always know the currency and country context.
JavaScript Example: Bulk Synthetic Generation for a Date Range
If you need multiple days for model training, you can iterate over a date range, fetching each day’s curve and carbon intensity once, then generating synthetic rows for that day. Here’s a Node.js sketch focusing on pipeline structure and reliability.
import fetch from "node-fetch";
const BASE = "https://energy-api.com/api/v1";
const KEY = process.env.ENERGY_API_KEY;
async function call(path, params) {
const q = new URLSearchParams({ ...params, api_key: KEY }).toString();
const url = `${BASE}${path}?${q}`;
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch(url, { timeout: 15000 });
if (res.status === 429 || res.status >= 500) {
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 400));
continue;
}
const data = await res.json();
if (!data.success) throw new Error(data.error || "Unknown API error");
return data;
}
throw new Error("Max retries exceeded");
}
function daterange(start, end) {
const out = [];
let d = new Date(start);
const e = new Date(end);
while (d <= e) {
out.push(d.toISOString().slice(0, 10));
d.setUTCDate(d.getUTCDate() + 1);
}
return out;
}
function elasticShift(kwhs, prices, elasticity = 0.1) {
const n = kwhs.length;
const sorted = [...prices].sort((a,b)=>a-b);
const median = sorted[Math.floor(n/2)];
const out = [...kwhs];
let budget = 0;
for (let i=0;i<n;i++){
if (prices[i] > median) {
const cut = Math.min(out[i] * 0.3, out[i] * elasticity * (prices[i] / (median+1e-9) - 1));
out[i] -= cut;
budget += cut;
}
}
const cheapIdx = [...Array(n).keys()].sort((a,b)=>prices[a]-prices[b]).slice(0, Math.max(1, Math.floor(n/3)));
cheapIdx.forEach(i => { out[i] += budget / cheapIdx.length; });
return out;
}
async function generateDay(date, symbol, country) {
const curve = await call("/electricity/hourly", { symbol, date });
const prices = curve.curve.map(pt => pt.price);
const times = curve.curve.map(pt => pt.time);
const ci = await call("/carbon-intensity", { country });
const ciMap = new Map(ci.series.map(pt => [pt.time, pt.value]));
// naive base
const base = [0.25,0.22,0.20,0.20,0.22,0.28,0.40,0.55,0.50,0.40,0.38,0.35,0.34,0.34,0.36,0.45,0.65,0.80,0.90,0.75,0.60,0.50,0.40,0.32];
const sum = base.reduce((a,b)=>a+b,0);
let kwhs = base.map(x => (x/sum) * 8.5); // 8.5 kWh/day
kwhs = elasticShift(kwhs, prices, 0.12);
const rows = times.map((t, i) => {
const kwh = kwhs[i];
const eur_kwh = prices[i] / 1000;
const ciVal = ciMap.get(t);
return {
time: t,
kwh: +kwh.toFixed(3),
price_EUR_MWh: +prices[i].toFixed(2),
cost_EUR: +(kwh * eur_kwh).toFixed(4),
carbon_intensity_gCO2eq_per_kWh: ciVal ?? null,
emissions_gCO2eq: ciVal ? +(kwh * ciVal).toFixed(1) : null
};
});
return rows;
}
async function run() {
const days = daterange("2026-06-01", "2026-06-07");
const all = [];
for (const d of days) {
try {
const rows = await generateDay(d, "OMIE_ES_DA", "ES");
all.push(...rows);
} catch (err) {
console.error("Failed for", d, err.message);
}
}
console.log(JSON.stringify(all.slice(0, 12), null, 2)); // preview
}
run().catch(console.error);
This pattern scales to weeks of synthetic data in minutes. To extend to 15-minute resolution, interpolate the hourly base shape to 96 bins and map each 15-min slot to the nearest price interval or source-native granularity if available.
Reliability, Observability, and Best Practices
Synthetic data pipelines are only useful if they’re dependable. Energy markets publish on fixed schedules with occasional provider delays. Build in the following controls so your generator gracefully handles real-world conditions:
- Health checks: Call GET /status before your daily run to confirm provider freshness. If a provider is catching up, log and either skip the day or reuse the previous day’s curve.
- Retries with backoff: On 429 and 5xx responses, try exponential backoff. The API’s error shape is consistent, so you can centralize retry policies.
- Circuit breakers: If GET /electricity/hourly returns 404 for a date (non-publishing day) or repeated timeouts occur, short-circuit and promote a fallback (most recent available). Alert your team only if the fallback period exceeds your SLO.
- Observability: Log each symbol-date pair fetched, the provider date stamp in the response, and the count of intervals returned. These are strong invariants for monitoring.
- Data alignment: Always respect the currencies and frequencies fields when mixing commodities. Even in a synthetic pipeline, unit correctness avoids subtle errors.
On performance, batch where possible. For broad context, GET /latest and GET /timeseries can return multiple symbols in one call. Cache invariant lookups (like /symbols) and memoize recent /carbon-intensity series per country-day in your job.
Advanced Endpoint Patterns for Your Generator
Two additional endpoints help you simulate forward-looking behaviors and anchor results to retail references.
Day-Ahead Auction Lookup: GET /forecast
For auction-based day-ahead markets, GET /forecast returns the next published day-ahead price. That’s valuable when you generate tomorrow’s synthetic dataset immediately after results post. If you’re training models for price-aware demand shifting, this gives you deterministic lookups without writing scrapers.
curl -G https://energy-api.com/api/v1/forecast \
--data-urlencode "symbol=OMIE_ES_DA" \
--data-urlencode "api_key=YOUR_API_KEY"
If the symbol is not auction-based, this endpoint returns 404; handle accordingly by switching to the last known intraday curve.
Spanish PVPC Retail Reference: GET /electricity/pvpc
To generate synthetic retail bills (not just wholesale-based), fetch the hourly PVPC reference for a date. For Spain, this gives you a strong baseline to test billing logic and client communications end-to-end.
curl -G https://energy-api.com/api/v1/electricity/pvpc \
--data-urlencode "date=2026-06-11" \
--data-urlencode "api_key=YOUR_API_KEY"
Use the returned hourly values in place of wholesale prices for cost_EUR calculation when you want a retail-oriented simulation. Keep your profile generation identical; just switch the price vector.
Interpreting Errors and Handling Edge Cases
The API standardizes errors to keep your control flow clean:
- 401 — Missing or invalid credentials. Do not retry blindly; fix configuration.
- 404 — No data for the given symbols or date. For non-publishing days, use the most recent available data or skip.
- 422 — Validation error. Verify parameter names, formats (YYYY-MM-DD), and supported symbols.
- 429 — Rate limit exceeded. Implement exponential backoff and spread calls—for example, fetch timeseries in fewer, larger batches.
All error responses share the shape:
{
"success": false,
"error": "Human-readable message."
}
In data generation jobs, treat 404 and 422 as deterministic errors (fix input or fallback) and 429/5xx as transient (retry with backoff). Record the final action (fetched, skipped, fallback) for each date-symbol and include that lineage with your synthetic outputs.
Real-World Use Cases
These patterns show how teams use the same building blocks for different products.
- Data-science bootstrapping for demand forecasting: Generate six weeks of hourly synthetic load shaped by GET /electricity/hourly and conditioned on historical volatility from GET /timeseries. Attach per-interval carbon intensity from GET /carbon-intensity. Use this to stand up feature pipelines and evaluate model architectures before real meter data access is cleared.
- Price alert and TOU advisory prototype: Use GET /forecast and GET /electricity/hourly to compute tomorrow’s cheapest and most carbon-friendly hours. Feed alerts into a UX prototype while you A/B test how different elasticities would shift load in your synthetic dataset.
- ESG dashboard R&D: Combine GET /latest for multi-commodity context (BRENT_CRUDE, TTF_GAS, EUA_CO2) with GET /carbon-intensity to visualize how wholesale conditions and carbon intensity co-move. Your synthetic meter rows give you safe “customer-like” aggregates for chart and widget development.
FAQ
How often does the TTF gas price update?
TTF_GAS is published on a market schedule and appears as a daily series in the API. Use GET /latest for the most recent value and GET /timeseries for date-bounded history. If a specific day is a non-publishing day, GET /historical will return the most recent value before that date to keep your pipelines stable.
Can I fetch multiple commodities in one call?
Yes. Endpoints like GET /latest and GET /timeseries accept a comma-separated list of symbols. This is useful for synthetic generation where you want electricity, gas, and carbon context together (for example, OMIE_ES_DA, TTF_GAS, and EUA_CO2), reducing round trips and simplifying joins.
Does the API support intraday electricity curves?
Yes, via GET /electricity/hourly. Where sources publish them, you’ll receive 15-minute or hourly curves. The response includes timestamps and a granularity hint so you can map them directly to your 15-min or hourly synthetic intervals without guesswork.
What should I do if I get a 404 when asking for a specific date?
A 404 indicates no data is available for the given symbol/date—often due to non-publishing days or early queries before publication. Use GET /status to verify provider freshness, then fallback to the last available curve or skip generation for that day. Log the action so downstream consumers know how the dataset was produced.
Can I estimate monthly costs without building a full hourly biller?
Yes. Use POST /cost-estimate to multiply the latest price by your monthly kWh. It’s a coarse bound that’s handy for scenario planning and UI development, even if you later switch to a full hourly costing approach using GET /electricity/hourly or GET /electricity/pvpc.
Conclusion + Call to Action
Building reliable energy products starts with reliable data. With Energy API, you can generate lifelike, privacy-safe smart-meter datasets anchored to real market curves, carbon intensity, and multi-commodity context. That means your developers and data scientists can prototype algorithms, validate interfaces, and benchmark infrastructure today—without waiting on sensitive meters or juggling a patchwork of country portals.
The unified JSON interface, consistent error model, and broad endpoint coverage help you move from a one-off script to a production-grade synthetic data pipeline with clean reliability controls. Whether you’re building demand-flexibility features, carbon-aware schedulers, cost optimizers, or ESG dashboards, these endpoints form a stable foundation that scales across regions and use cases.
Start shaping your own synthetic datasets and accelerate your next release. Explore the endpoints, wire up the hourly curves, and set your generator in motion: Try Energy API for free.
Ready to get started?
Get your API key and start querying energy commodity prices in minutes.
Get API KeyRelated posts
Discover how to implement OAuth2 consent flows and enhance customer data privacy with Energy API for secure me...
Read more →
Unlock the potential of Energy API to create personalized tariffs and targeted efficiency programs. Discover h...
Read more →
Discover how Energy API streamlines meter-to-bill reconciliation for utilities, automating netting, tariff rul...
Read more →
Unlock energy efficiency with smart meter integration. Discover how Energy APIs can streamline operations and...
Read more →
Discover how to enhance grid operations by operationalizing anomaly detection with Energy API. Learn to catch...
Read more →