API
REST over HTTPS, JSON, authenticated with a key in the X-API-KEY header. Base: https://api.holgura.dev/api/v1
Authentication
Create a key in the console (API keys) and send it with every request in the X-API-KEY header. Authorization: Bearer with the same key also works. Sending both with different values is rejected.
Without a key you can read the public map, the statistics and nodes with the last 3 months of history, with a per-IP limit.
curl -s https://api.holgura.dev/api/v1/nodes/ES-REE-ABADIANO220 \
-H "X-API-KEY: $HOLGURA_KEY"Response format
Every data response carries meta with the request time, the date of the mirrored official publication (source_data_mirror_date), the legal notice and the attribution. Lists paginate with page.next_cursor.
Example: one node
{
"status": "success",
"meta": {
"timestamp_consult": "2026-10-22T06:51:31Z",
"source_data_mirror_date": "2026-10-01",
"disclaimer": "Data mirrored from official REE and CNMC publications. Holgura certifies synchronisation correctness, not grid-access awards. Not investment advice.",
"plan": "pro"
},
"data": {
"node_code_clean": "ES-REE-ABADIANO220",
"official_name": "ABADIANO 220",
"voltage_level_kv": 220,
"network": "transmission",
"operator": "REE",
"location": { "province": "Bizkaia", "autonomous_community": "País Vasco" },
"capacity": {
"available_demand_mw": 0,
"available_storage_mw": 345,
"status": "SATURATED",
"last_official_update": "2026-10-01"
},
"metrics": { "months_saturated_last_year": 3, "saturation_ratio_percent": 100 },
"historical_log": [
{ "date": "2026-08-01", "value_mw": 0, "status": "SATURATED", "source": "ree_demanda" },
{ "date": "2026-09-01", "value_mw": 0, "status": "SATURATED", "source": "ree_demanda" },
{ "date": "2026-10-01", "value_mw": 0, "status": "SATURATED", "source": "ree_demanda" }
]
}
}Endpoints
| GET | /api/v1/nodes | Nodes with filters: network, operator, province, ccaa, status, metric, min_mw, min_kv, q, sort (code, name, mw_desc), limit (1–1000), cursor. |
| GET | /api/v1/nodes/{code} | A node's current state, official notes, saturation metrics and the history of its main capacity. |
| GET | /api/v1/nodes/{code}/history | Historical series per capacity (optional metric). One entry per official publication. |
| GET | /api/v1/events | Detected changes: kind, metric, province, network, period (YYYY-MM), watchlist, limit, cursor. |
| GET | /api/v1/sources | Official sources with their latest archived publication. |
| GET | /api/v1/stats | Aggregate counters for the latest publication. |
| GET | /api/v1/map.geojson | GeoJSON layer of nodes with coordinates, for your own map. |
| GET | /api/v1/nodes/export.csv | CSV export with the same filters as /nodes (Pro and above). |
curl -s "https://api.holgura.dev/api/v1/nodes?network=transmission&status=AVAILABLE&metric=storage_available_mw&min_kv=220&sort=mw_desc&limit=20" \
-H "X-API-KEY: $HOLGURA_KEY"Webhooks
Each alert on the webhook channel sends a JSON POST to your HTTPS endpoint with the X-Holgura-Event and X-Holgura-Signature headers.
The signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of “t.body” with your secret>. Reject signatures older than 5 minutes.
We retry after 1 min, 5 min, 30 min, 2 h and 6 h. After repeated failures we disable the endpoint and notify the admins.
Verify the signature
import crypto from "node:crypto";
export function verify(secret, header, rawBody, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const mac = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(parts.v1 ?? ""));
}POST /your/endpoint
X-Holgura-Event: node.liberated
X-Holgura-Signature: t=1792569600,v1=5f2b…c9
{
"id": "evt_1842_7",
"type": "node.liberated",
"created_at": "2026-11-03T07:12:44Z",
"data": {
"kind": "LIBERATED",
"metric": "demand_available_mw",
"old_value": 0,
"new_value": 85,
"delta_mw": 85,
"period": "2026-11",
"source": "ree_demanda",
"rule_name": "Andalucía 400 kV",
"node": { "code": "ES-REE-SEVILLA400", "name": "SEVILLA 400", "network": "transmission", "voltage_kv": 400, "province": "Sevilla" },
"url": "https://holgura.dev/app/nodo/?code=ES-REE-SEVILLA400"
},
"meta": { "source_data_mirror_date": "2026-11-03", "disclaimer": "…" }
}Errors
Errors use the {"error":{"code","message"}} format. Common codes:
invalid_key- Invalid or revoked key (401).
quota_exceeded- Your plan's monthly quota is used up (429).
rate_limited- Per-minute limit exceeded; respect Retry-After (429).
plan_limit- Feature or limit outside your plan (402).
invalid- Invalid parameter (400).
not_found- No such resource (404).
{ "error": { "code": "quota_exceeded", "message": "monthly request quota of your plan exhausted; upgrade or wait for next month" } }Limits
Every keyed response includes X-Holgura-Plan, X-Holgura-Quota-Limit and X-Holgura-Quota-Remaining. Limits per plan:
| Free | Pro | Team | Enterprise | |
|---|---|---|---|---|
| Requests/month | 500 | 50,000 | 500,000 | Unlimited |
| Requests/min | 10 | 60 | 300 | 1,200 |
| History | 3 months | Full | Full | Full |
| Watchlists | 3 | Unlimited | Unlimited | Unlimited |
| Alert rules | 1 | 25 | 100 | Unlimited |
| Webhooks | No | 3 | 10 | Unlimited |
| Team | 1 | 3 | 10 | Unlimited |
| CSV export | No | Yes | Yes | Yes |