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/nodes | Lista 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}/history | Serie histórica por capacidad (metric opcional). Una entrada por publicación oficial. |
| GET | /api/v1/events | Cambios detectados: kind, metric, province, network, period (AAAA-MM), watchlist, limit, cursor. |
| GET | /api/v1/sources | Fuentes oficiales con su última publicación archivada. |
| GET | /api/v1/stats | Contadores agregados de la última publicación. |
| GET | /api/v1/map.geojson | Capa GeoJSON de nudos con coordenadas, para tu propio mapa. |
| GET | /api/v1/nodes/export.csv | Exportació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:
| Free | Pro | Team | Enterprise | |
|---|---|---|---|---|
| Peticiones/mes | 500 | 50 000 | 500 000 | Sin límite |
| Peticiones/min | 10 | 60 | 300 | 1 200 |
| Histórico | 3 meses | Completo | Completo | Completo |
| Listas | 3 | Sin límite | Sin límite | Sin límite |
| Reglas de alerta | 1 | 25 | 100 | Sin límite |
| Webhooks | No | 3 | 10 | Sin límite |
| Equipo | 1 | 3 | 10 | Sin límite |
| Exportación a CSV | No | Sí | Sí | Sí |