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.

  1. POST /v1/signup con tu correo. Siempre responde 202, exista o no la cuenta.
  2. Abre el enlace del correo (válido 24 h): la página muestra tu clave una sola vez. Por API, envía el token del enlace a POST /v1/verify.
  3. 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 gratuitoPlan plus
Peticiones API + MCP60/min y 1000/día600/min y 20 000/día
Análisis de facturas10/día100/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"]
}
  • 401 clave ausente, mal formada o revocada · 403 cuenta desactivada · 404 plan inexistente
  • 413 cuerpo demasiado grande · 429 límite superado · 503 funció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) o total_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

HerramientaQué hace
explain_tariffExplica cómo funciona una factura 2.0TD (periodos, impuestos, tipos de tarifa) y qué datos pedir al usuario antes de comparar.
list_retailersLista las comercializadoras seguidas.
list_plansLista tarifas vigentes, filtrables por retailer y kind. Precios sin impuestos.
get_planDetalle de una tarifa: precios, promoción, permanencia, requisitos y origen.
compare_offersOrdena 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_billFactura desglosada (potencia, energía, bono social, impuesto eléctrico, contador, IVA) de una tarifa.
analyze_billLee 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.