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/nodesNodes 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}/historyHistorical series per capacity (optional metric). One entry per official publication.
GET/api/v1/eventsDetected changes: kind, metric, province, network, period (YYYY-MM), watchlist, limit, cursor.
GET/api/v1/sourcesOfficial sources with their latest archived publication.
GET/api/v1/statsAggregate counters for the latest publication.
GET/api/v1/map.geojsonGeoJSON layer of nodes with coordinates, for your own map.
GET/api/v1/nodes/export.csvCSV 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:

FreeProTeamEnterprise
Requests/month50050,000500,000Unlimited
Requests/min10603001,200
History3 monthsFullFullFull
Watchlists3UnlimitedUnlimitedUnlimited
Alert rules125100Unlimited
WebhooksNo310Unlimited
Team1310Unlimited
CSV exportNoYesYesYes