API

REST sobre HTTPS, JSON, autenticación con clave en la cabecera X-API-KEY. Base: https://api.holgura.dev/api/v1

Autenticación

Crea una clave en la consola (Claves de API) y envíala en cada petición en la cabecera X-API-KEY. También aceptamos Authorization: Bearer con la misma clave. Si envías las dos con valores distintos, la petición se rechaza.

Sin clave puedes consultar el mapa público, las estadísticas y los nudos con los últimos 3 meses de histórico, con un límite por IP.

curl -s https://api.holgura.dev/api/v1/nodes/ES-REE-ABADIANO220 \
  -H "X-API-KEY: $HOLGURA_KEY"

Formato de respuesta

Toda respuesta de datos lleva meta con la fecha de consulta, la fecha de la publicación oficial reflejada (source_data_mirror_date), la advertencia legal y la atribución. Las listas paginan con page.next_cursor.

Ejemplo: un nudo

{
  "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/nodesLista de nudos con filtros: 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}Estado actual de un nudo, notas oficiales, métricas de saturación e histórico de su capacidad principal.
GET/api/v1/nodes/{code}/historySerie histórica por capacidad (metric opcional). Una entrada por publicación oficial.
GET/api/v1/eventsCambios detectados: kind, metric, province, network, period (AAAA-MM), watchlist, limit, cursor.
GET/api/v1/sourcesFuentes oficiales con su última publicación archivada.
GET/api/v1/statsContadores agregados de la última publicación.
GET/api/v1/map.geojsonCapa GeoJSON de nudos con coordenadas, para tu propio mapa.
GET/api/v1/nodes/export.csvExportación CSV con los mismos filtros que /nodes (Pro y superior).
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

Cada alerta con canal webhook envía un POST JSON a tu endpoint HTTPS con las cabeceras X-Holgura-Event y X-Holgura-Signature.

La firma es t=<segundos unix>,v1=<HMAC-SHA256 en hexadecimal de «t.cuerpo» con tu secreto>. Rechaza firmas con más de 5 minutos de antigüedad.

Reintentamos a 1 min, 5 min, 30 min, 2 h y 6 h. Tras fallos repetidos desactivamos el endpoint y avisamos a los administradores.

Verificar la firma

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": "…" }
}

Errores

Los errores usan el formato {"error":{"code","message"}}. Códigos habituales:

invalid_key
Clave inválida o revocada (401).
quota_exceeded
Cuota mensual del plan agotada (429).
rate_limited
Límite por minuto superado; respeta Retry-After (429).
plan_limit
Función o límite fuera de tu plan (402).
invalid
Parámetro no válido (400).
not_found
Recurso inexistente (404).
{ "error": { "code": "quota_exceeded", "message": "monthly request quota of your plan exhausted; upgrade or wait for next month" } }

Límites

Cada respuesta con clave incluye X-Holgura-Plan, X-Holgura-Quota-Limit y X-Holgura-Quota-Remaining. Límites por plan:

FreeProTeamEnterprise
Peticiones/mes50050 000500 000Sin límite
Peticiones/min10603001 200
Histórico3 mesesCompletoCompletoCompleto
Listas3Sin límiteSin límiteSin límite
Reglas de alerta125100Sin límite
WebhooksNo310Sin límite
Equipo1310Sin límite
Exportación a CSVNoSíSíSí