Spain OMIE Day-Ahead Price API in Python

Spain OMIE Day-Ahead Price API in Python

You need a reliable way to fetch Spain’s OMIE day-ahead electricity price for financial modeling, risk, or settlement logic. By the end of this guide you will call the Spain OMIE Day-Ahead symbol from the Energy API using Python, parse the price safely, handle units and base currency, and add guardrails (timeouts, retries, and caching) suitable for a finance pipeline.

What you will implement

This tutorial walks through retrieving the latest day-ahead price for Spain’s OMIE market using the Energy API and the symbol “OMIE_ES_DA”. You will:

  • Make a GET request to /api/v1/latest with the correct query parameters.
  • Read the price from rates.OMIE_ES_DA, check the associated date in dates.OMIE_ES_DA, and validate currency from currencies.OMIE_ES_DA.
  • Build a small Python module with timeouts, retries, and a minimal cache to avoid redundant calls.
  • Understand the unit (EUR/MWh), how to handle base currency, and how to avoid common integration mistakes that break downstream finance logic.

API facts you must get right

Copy these into your runbook to avoid common mistakes:

  • Symbol name: Spain OMIE Day-Ahead
  • Symbol code: OMIE_ES_DA
  • Unit: EUR/MWh
  • Auth: api_key query parameter. Use the placeholder value YOUR_API_KEY when testing examples.
  • Do not pass base=USD when you also need TTF_GAS or EUA_CO2.
  • No MCP host. Starter $19.99/mo, trial 7 days / 50 calls.
  • Endpoint: GET https://energy-api.com/api/v1/latest
  • Read values from: rates.OMIE_ES_DA, dates.OMIE_ES_DA, currencies.OMIE_ES_DA. The top-level base may be MIXED.

For account creation and API reference, use these links:

cURL smoke test

Before you write Python code, verify your connectivity and authentication with cURL:

curl -G "https://energy-api.com/api/v1/latest" --data-urlencode "symbols=OMIE_ES_DA" --data-urlencode "api_key=YOUR_API_KEY"

Successful response (use these exact values to validate parsing and tests):

{"success":true,"date":"2026-09-29","base":"EUR","rates":{"OMIE_ES_DA":130.0965},"dates":{"OMIE_ES_DA":"2026-09-29"},"currencies":{"OMIE_ES_DA":"EUR"},"base_filter_note":null}

Field usage notes:

  • rates.OMIE_ES_DA is the price (numeric) you will store and use in calculations.
  • dates.OMIE_ES_DA indicates the effective market date for that price.
  • currencies.OMIE_ES_DA provides the currency for the price (EUR), while the unit is EUR/MWh.
  • base may be “EUR” or “MIXED” depending on your query; treat your downstream logic as symbol-specific rather than relying solely on the top-level base.

Python: minimal client to fetch and parse OMIE_ES_DA

The following Python example performs a single call, validates fields, and returns a dataclass with the value, unit, date, and currency. You can drop this into a finance ETL or a pricing function.

import os
import requests
from dataclasses import dataclass
from typing import Optional

API_URL = "https://energy-api.com/api/v1/latest"
SYMBOL = "OMIE_ES_DA"
API_KEY = os.environ.get("ENERGY_API_KEY", "YOUR_API_KEY") # replace in production

@dataclass
class OMIESpot:
symbol: str
price_eur_per_mwh: float
date: str
currency: str # should be "EUR"

class EnergyAPIError(Exception):
pass

def fetch_omie_es_da(timeout: float = 10.0) -> OMIESpot:
params = {
"symbols": SYMBOL,
"api_key": API_KEY,
}
try:
resp = requests.get(API_URL, params=params, timeout=timeout)
except requests.RequestException as e:
raise EnergyAPIError(f"Network error calling Energy API: {e}") from e

if resp.status_code != 200:
raise EnergyAPIError(f"Unexpected HTTP {resp.status_code}: {resp.text}")

try:
payload = resp.json()
except ValueError as e:
raise EnergyAPIError(f"Invalid JSON: {e}") from e

# Basic schema checks for required fields
if not payload.get("success", False):
raise EnergyAPIError(f"API indicated failure: {payload}")

rates = payload.get("rates", {})
dates = payload.get("dates", {})
currencies = payload.get("currencies", {})

if SYMBOL not in rates or SYMBOL not in dates or SYMBOL not in currencies:
raise EnergyAPIError(
f"Missing expected fields for {SYMBOL}. "
f"rates keys={list(rates.keys())}, dates keys={list(dates.keys())}, currencies keys={list(currencies.keys())}"
)

price = float(rates[SYMBOL])
date = str(dates[SYMBOL])
currency = str(currencies[SYMBOL])

# Optional sanity checks for finance usage
if currency != "EUR":
raise EnergyAPIError(f"Unexpected currency for {SYMBOL}: {currency}")
if price < 0:
raise EnergyAPIError(f"Negative price for {SYMBOL}: {price}")

return OMIESpot(
symbol=SYMBOL,
price_eur_per_mwh=price,
date=date,
currency=currency,
)

if __name__ == "__main__":
spot = fetch_omie_es_da()
print(f"{spot.symbol} {spot.date}: {spot.price_eur_per_mwh} {spot.currency}/MWh")

When run against the API, you will parse the same JSON structure shown in the cURL example above. Ensure you map the numeric rate directly to your pricing models without unit conversion, since the unit is already EUR/MWh.

Field semantics: unit, base currency, and symbol-scoped values

For finance integrations, correctness around units and currency is core to avoiding P&L distortions or hedge misalignment. Keep these rules:

  • Unit is EUR/MWh for OMIE_ES_DA. If your system stores power prices in EUR/MWh, you can persist the rate as-is.
  • Always read currency from currencies.OMIE_ES_DA. Even if the top-level base is EUR, the canonical source of truth is the symbol-level currency.
  • The top-level base may be MIXED when you query multiple symbols with heterogeneous currencies. Your calculations should be symbol-aware rather than assuming a single top-level base.
  • Do not pass base=USD when you also need TTF_GAS or EUA_CO2. Keep prices in their native currencies when combining symbols that do not share a common base to avoid silent currency conversions.

Below is the same valid response repeated so you can verify your parsing and unit/currency assertions without ambiguity. Use it in your tests to ensure your code reads the correct nested fields and respects symbol-level currency.

{"success":true,"date":"2026-09-29","base":"EUR","rates":{"OMIE_ES_DA":130.0965},"dates":{"OMIE_ES_DA":"2026-09-29"},"currencies":{"OMIE_ES_DA":"EUR"},"base_filter_note":null}

Robustness for production: retries, timeouts, caching

Financial systems typically require predictable performance and resilience. Add the following practices to your integration:

  • Timeouts: Always specify a client-side timeout to avoid hanging processes.
  • Retries: Retry transient network errors with exponential backoff; cap the total time to respect job SLAs.
  • Idempotent reads: GET calls are safe to retry.
  • Caching: Cache the latest successfully fetched value for a short period to avoid redundant calls and to stay within trial/plan quotas.

The snippet below wraps the basic fetch with simple on-disk caching keyed by date and symbol. This pattern helps limit calls to the API while ensuring you have a last-known-good value if your process restarts.

import json
import os
import time
from pathlib import Path
from typing import Optional

CACHE_DIR = Path(".energy_cache")
CACHE_TTL_SECONDS = 300 # 5 minutes

def _cache_path(symbol: str) -> Path:
CACHE_DIR.mkdir(exist_ok=True)
return CACHE_DIR / f"{symbol}.json"

def load_cached(symbol: str) -> Optional[dict]:
p = _cache_path(symbol)
if not p.exists():
return None
try:
stat = p.stat()
if (time.time() - stat.st_mtime) > CACHE_TTL_SECONDS:
return None
with p.open("r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return None

def save_cache(symbol: str, payload: dict) -> None:
p = _cache_path(symbol)
with p.open("w", encoding="utf-8") as f:
json.dump(payload, f, separators=(",", ":"))

def fetch_with_cache(timeout: float = 10.0) -> OMIESpot:
# Try cache first
cached = load_cached(SYMBOL)
if cached and cached.get("success") is True and SYMBOL in cached.get("rates", {}):
rate = float(cached["rates"][SYMBOL])
d = str(cached["dates"][SYMBOL])
ccy = str(cached["currencies"][SYMBOL])
return OMIESpot(symbol=SYMBOL, price_eur_per_mwh=rate, date=d, currency=ccy)

# Otherwise fetch live
params = {"symbols": SYMBOL, "api_key": API_KEY}
r = requests.get(API_URL, params=params, timeout=timeout)
r.raise_for_status()
payload = r.json()

if not payload.get("success"):
raise EnergyAPIError(f"API indicated failure: {payload}")

# Save and return
save_cache(SYMBOL, payload)
rate = float(payload["rates"][SYMBOL])
d = str(payload["dates"][SYMBOL])
ccy = str(payload["currencies"][SYMBOL])
return OMIESpot(symbol=SYMBOL, price_eur_per_mwh=rate, date=d, currency=ccy)

if __name__ == "__main__":
spot = fetch_with_cache()
print(f"Cache-aware fetch: {spot.symbol} {spot.date} = {spot.price_eur_per_mwh} {spot.currency}/MWh")

If your cache writes a previously fetched payload, it should match the valid response structure. The example below is the same valid payload you can store as a JSON file for testing cache reads:

{"success":true,"date":"2026-09-29","base":"EUR","rates":{"OMIE_ES_DA":130.0965},"dates":{"OMIE_ES_DA":"2026-09-29"},"currencies":{"OMIE_ES_DA":"EUR"},"base_filter_note":null}

Validation and downstream finance logic

When using OMIE_ES_DA in positions, settlement curves, or VaR simulations, ensure you gate inputs through a simple validator. The typical checks include:

  • Non-null, non-negative rates for OMIE_ES_DA.
  • Expected currency is EUR for this symbol.
  • The price date is the day you expect to attribute in your ledger or risk model (verify mapping between market date and your books-date if needed).
  • Detect unchanged values across multiple fetches and alert only when the date flips or the rate materially deviates beyond a configured threshold.

For automated tests, embed a fixture using this exact JSON to ensure your parsers read symbol-scoped fields correctly:

{"success":true,"date":"2026-09-29","base":"EUR","rates":{"OMIE_ES_DA":130.0965},"dates":{"OMIE_ES_DA":"2026-09-29"},"currencies":{"OMIE_ES_DA":"EUR"},"base_filter_note":null}

This keeps test expectations stable and prevents accidental reliance on the top-level base when integrating multiple symbols later.

Operational considerations: quotas, scheduling, and non-trading days

Plan your polling schedule to fit within your trial and plan limits while ensuring data freshness:

  • Plan: Starter $19.99/mo; trial is 7 days with 50 calls. Use caching to avoid duplicates within the same run.
  • Batching: When you eventually add more symbols, consider batching them in a single GET call where appropriate, but remember the base may be MIXED and each symbol can carry its own currency.
  • Scheduling: Align your job timing with when day-ahead results are expected to be available for your use case. If your job runs before the new value is posted, carry forward the last-known-good value and recheck later.
  • Alerting: Alert on missing fields or unexpected currency changes per symbol; silence alerts when the value is unchanged and within a configured quiet window to prevent noise.

If you snapshot responses to a datastore (e.g., a time-series DB), persist the full payload for traceability alongside the extracted numeric value and unit. This makes audits and backfills faster.

Troubleshooting

Issues typically fall into one of these categories:

  • Authentication: Ensure the api_key query parameter is provided. For local tests, you can set ENERGY_API_KEY in your environment or embed YOUR_API_KEY in dev only.
  • Field access: Always read rates.OMIE_ES_DA, dates.OMIE_ES_DA, and currencies.OMIE_ES_DA. Do not assume the top-level base applies to each symbol.
  • Currency mishandling: Do not pass base=USD when you also need TTF_GAS or EUA_CO2 in the same call. Keep native currencies per symbol.
  • Parsing errors: Validate that the response is JSON and that success is true before reading fields.

Use the known-good JSON below while stepping through your parser in a debugger:

{"success":true,"date":"2026-09-29","base":"EUR","rates":{"OMIE_ES_DA":130.0965},"dates":{"OMIE_ES_DA":"2026-09-29"},"currencies":{"OMIE_ES_DA":"EUR"},"base_filter_note":null}

FAQ

How do I authenticate my requests?

Pass your key with the api_key query parameter. For example: ?symbols=OMIE_ES_DA&api_key=YOUR_API_KEY. Do not use headers for auth with this endpoint.

Which field is the actual OMIE Spain day-ahead price?

Read the numeric price from rates.OMIE_ES_DA. The date for that price is in dates.OMIE_ES_DA, and its currency in currencies.OMIE_ES_DA.

What is the unit, and do I need to convert it?

The unit is EUR/MWh. If your internal models use EUR/MWh, no conversion is required. Store the exact float you receive.

How should I handle base currency?

Treat base as informational at the top level; always rely on symbol-level currencies for calculations. The top-level base may be MIXED when aggregating cross-currency symbols.

Can I request multiple symbols together and force USD?

Avoid passing base=USD when you also need TTF_GAS or EUA_CO2 in the same call. Keep native currencies and handle FX separately in your pipeline if needed.

Ready to integrate the Spain OMIE Day-Ahead price into your finance stack? Create your account and get an API key, then ship your first call in minutes: Register. For endpoint details and field definitions, see the Documentation.

Ready to get started?

Get your API key and start querying energy commodity prices in minutes.

Get API Key

Related posts