API y servidor MCP
Los mismos datos del comparador, para tu código (REST, JSON) o para Claude y otras IA (MCP).
Base: https://luz.example/v1 · MCP: https://luz.example/mcp
1. Autenticación
Todas las rutas salvo /v1/health, /v1/signup y /v1/verify necesitan una clave de API
(formato lz_<id>_<secreto>) en la cabecera Authorization. La misma clave sirve para el servidor MCP.
POST /v1/signupcon tu correo. Siempre responde202, exista o no la cuenta.- Abre el enlace del correo (válido 24 h): la página muestra tu clave una sola vez.
Por API, envía el
tokendel enlace aPOST /v1/verify. - Envía
Authorization: Bearer lz_…en cada petición.
Pedir un enlace nuevo y verificarlo, o llamar a POST /v1/keys/rotate, emite una clave nueva y anula la anterior.
export LUZ_KEY="lz_xxxxxxxx_..." # guárdala como un secreto, nunca en el código
curl -s https://luz.example/v1/me -H "Authorization: Bearer $LUZ_KEY"
2. Límites y cabeceras
| Qué | Plan gratuito | Plan plus |
|---|---|---|
| Peticiones API + MCP | 60/min y 1000/día | 600/min y 20 000/día |
| Análisis de facturas | 10/día | 100/día |
Altas (/v1/signup) | 5/hora por IP y 3/día por correo | |
Las respuestas autenticadas incluyen X-RateLimit-Limit, X-RateLimit-Remaining y
X-RateLimit-Reset (segundos hasta que se recupera la ventana más restrictiva). Al superar un límite
recibes 429 con Retry-After en segundos: espera ese tiempo antes de reintentar.
GET /v1/me muestra tus límites.
3. Errores
Los errores usan application/problem+json (RFC 9457). En los 422, problems lista cada campo erróneo:
{
"type": "about:blank",
"title": "Unprocessable Content",
"status": 422,
"detail": "request validation failed",
"problems": ["usage.power_p1_kw: Input should be greater than 0"]
}
401clave ausente, mal formada o revocada ·403cuenta desactivada ·404plan inexistente413cuerpo demasiado grande ·429límite superado ·503función no disponible en este servidor- Cada respuesta lleva
X-Request-ID: inclúyelo si nos reportas un problema.
4. Precios y unidades
- Solo tarifas domésticas a precio fijo con peaje 2.0TD (≤ 15 kW). Sin PVPC ni tarifas indexadas.
- Los importes son cadenas con 2 decimales (
"48.73"); los precios unitarios, hasta 6 ("0.108712"). Nunca floats. En las peticiones puedes enviar números o cadenas. - Precios de las tarifas sin impuestos; potencia en €/kW·día (y €/kW·año). Los totales de factura incluyen
bono social, impuesto eléctrico, alquiler del contador e IVA/IGIC/IPSI según
region(peninsula,baleares,canarias,ceuta,melilla). - Consumo:
kwh_p1/kwh_p2/kwh_p3(punta/llano/valle) ototal_kwh, que se reparte con el perfil medio de la CNMC.equipment(ev,solar,battery) declara instalaciones que algunas tarifas exigen. - Tipos de tarifa (
kind):single,periods,flat,offpeak_days.
5. Endpoints
Ejemplos con curl; define antes BASE=https://luz.example y LUZ_KEY.
GET /v1/health
Estado del servicio. Sin clave.
curl -s $BASE/v1/health
# {"status":"ok","version":"0.1.0"}
POST /v1/signup
Envía un enlace de verificación al correo. Respuesta 202 siempre igual.
curl -s -X POST $BASE/v1/signup -H "Content-Type: application/json" \
-d '{"email": "ana@example.com"}'
POST /v1/verify
Canjea el token del enlace por una clave nueva (se muestra una sola vez).
curl -s -X POST $BASE/v1/verify -H "Content-Type: application/json" \
-d '{"token": "TOKEN_DEL_ENLACE"}'
# {"api_key":"lz_ab12cd34_...","client_id":"...","email":"ana@example.com","tier":"free",...}
GET /v1/me
Tu cuenta, plan y límites.
curl -s $BASE/v1/me -H "Authorization: Bearer $LUZ_KEY"
POST /v1/keys/rotate
Emite una clave nueva y revoca la usada en la petición.
curl -s -X POST $BASE/v1/keys/rotate -H "Authorization: Bearer $LUZ_KEY"
GET /v1/retailers
Comercializadoras seguidas.
curl -s $BASE/v1/retailers -H "Authorization: Bearer $LUZ_KEY"
GET /v1/plans
Tarifas vigentes. Filtros opcionales: retailer, kind, include_stale (por defecto true).
curl -s "$BASE/v1/plans?retailer=octopus&kind=single" -H "Authorization: Bearer $LUZ_KEY"
GET /v1/plans/{plan_id}
Detalle de una tarifa: precios, promoción, permanencia, requisitos y origen.
curl -s $BASE/v1/plans/iberdrola-plan-estable -H "Authorization: Bearer $LUZ_KEY"
POST /v1/compare
Calcula la factura de cada oferta para tu consumo y las ordena de menor a mayor. Con current
(un plan_id del catálogo o tus precios sin impuestos) cada oferta muestra el ahorro por factura y al año.
Las ofertas que no puedes contratar aparecen en excluded con el motivo.
curl -s -X POST $BASE/v1/compare -H "Authorization: Bearer $LUZ_KEY" \
-H "Content-Type: application/json" -d '{
"usage": {"power_p1_kw": "4.6", "power_p2_kw": "4.6",
"kwh_p1": "85", "kwh_p2": "75", "kwh_p3": "140",
"days": 30, "region": "peninsula", "equipment": []},
"current": {"power_p1": "0.108192", "power_p2": "0.046548", "energy_eur_kwh": "0.1599"},
"filters": {"max_permanence_months": 0, "green_only": false},
"limit": 5
}'
Respuesta (resumida):
{
"usage": {"total_kwh": "300", "annual_kwh": "3650", ...},
"current": {"plan": {...}, "bill": {"total": "90.07", ...}},
"ranking": [{
"rank": 1,
"plan": {"plan_id": "...", "retailer_id": "...", "name": "...", "promo_months": 0, "permanence_months": 0, ...},
"bill": {"power_cost": "31.05", "energy_cost": "27.00", "bono_social": "0.74", "electricity_tax": "3.01",
"meter_rental": "0.80", "vat": "13.15", "vat_name": "IVA", "total": "75.75", "yearly_equivalent": "921.63", ...},
"savings": "14.32", "savings_yearly": "174.23",
"has_promo": false, "has_permanence": false, "stale": false
}],
"excluded": [{"plan": {...}, "reasons": ["requires contracted power >= 10 kW"]}],
"disclaimers": {"oldest_fetched_at": "...", "newest_fetched_at": "...", ...}
}
Si solo conoces el total: "usage": {"power_p1_kw": "3.45", "total_kwh": "250", "days": 31}.
Para usar una tarifa del catálogo como actual: "current": {"plan_id": "iberdrola-plan-estable"}.
Si tu potencia viene en €/kW·año, añade "power_unit": "eur_kw_year".
POST /v1/simulate
Factura desglosada de una sola tarifa.
curl -s -X POST $BASE/v1/simulate -H "Authorization: Bearer $LUZ_KEY" \
-H "Content-Type: application/json" \
-d '{"plan_id": "iberdrola-plan-estable", "usage": {"power_p1_kw": "3.45", "total_kwh": "250", "days": 31}}'
POST /v1/bills/analyze
Sube tu factura (PDF, PNG, JPEG o WEBP; máx. 10 MB por defecto) como campo file de un
multipart/form-data. Una IA lee los datos y se compara con todas las ofertas. El archivo no se guarda.
Responde 503 si el servidor no tiene activado el análisis. Parámetro opcional limit.
curl -s -X POST "$BASE/v1/bills/analyze?limit=5" -H "Authorization: Bearer $LUZ_KEY" \
-F "file=@factura.pdf;type=application/pdf"
# {"extracted": {...}, "comparison": {...igual que /v1/compare...}, "warnings": [...]}
6. Servidor MCP
Endpoint Streamable HTTP (sin estado) en https://luz.example/mcp,
autenticado con la misma cabecera Authorization: Bearer lz_…. Las llamadas cuentan para tus límites.
Claude Code
claude mcp add --transport http luz https://luz.example/mcp \
--header "Authorization: Bearer lz_xxxxxxxx_..."
Después, en Claude Code: «Compara tarifas para 4,6 kW y 300 kWh al mes».
Claude Desktop
Añádelo a claude_desktop_config.json (Ajustes → Desarrollador → Editar configuración) usando el puente
mcp-remote (requiere Node.js), y reinicia Claude Desktop:
{
"mcpServers": {
"luz": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://luz.example/mcp",
"--header", "Authorization:${LUZ_AUTH}"],
"env": { "LUZ_AUTH": "Bearer lz_xxxxxxxx_..." }
}
}
}
Otros clientes MCP
Cualquier cliente compatible con Streamable HTTP que permita cabeceras personalizadas: URL
https://luz.example/mcp y cabecera Authorization: Bearer lz_….
Prueba rápida con curl (JSON-RPC):
curl -s -X POST https://luz.example/mcp \
-H "Authorization: Bearer $LUZ_KEY" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'
7. Herramientas MCP
| Herramienta | Qué hace |
|---|---|
explain_tariff | Explica cómo funciona una factura 2.0TD (periodos, impuestos, tipos de tarifa) y qué datos pedir al usuario antes de comparar. |
list_retailers | Lista las comercializadoras seguidas. |
list_plans | Lista tarifas vigentes, filtrables por retailer y kind. Precios sin impuestos. |
get_plan | Detalle de una tarifa: precios, promoción, permanencia, requisitos y origen. |
compare_offers | Ordena todas las ofertas por el total de la factura para un consumo (usage), con ahorro frente a current_plan_id o current_prices, filtros y limit. |
simulate_bill | Factura desglosada (potencia, energía, bono social, impuesto eléctrico, contador, IVA) de una tarifa. |
analyze_bill | Lee una factura (PDF o foto en base64, file_base64 + media_type) y la compara con todas las ofertas. Limitado a pocas llamadas al día. |
Los esquemas de entrada y salida son los mismos que los de la API REST: consulta la referencia OpenAPI o ReDoc para el detalle de cada campo.