leyzly

API para desarrolladores

Automatiza auditorías legales con la API de Leyzly

Una API REST sobre HTTPS, con autenticación por clave, respuestas JSON y webhooks firmados. Lanza auditorías de RGPD, LSSI y cookies, consulta los hallazgos y descarga el informe — todo desde tu código.

El acceso a la API se incluye en el plan Pro. URL base: https://api.leyzly.com

Autenticación

Cada petición se autentica con tu API key en la cabecera X-Api-Key. También admitimos Authorization: Api-Key <clave>. Genera y revoca tus claves desde el panel — por seguridad, una API key no puede crear ni revocar otras claves.

curl https://api.leyzly.com/api/v1/account/ \
  -H "X-Api-Key: $LEYZLY_API_KEY"

Respuesta · 200 OK

{
  "account": {
    "id": "8f14e45f-ceea-467f-9a36-dedd4bea2543",
    "email": "cliente@empresa.es",
    "full_name": "Cliente Ejemplo"
  },
  "plan": { "code": "pro", "name": "Pro", "api_access": true },
  "is_subscriber": true,
  "ai_audits": { "used_this_month": 12, "limit": 49, "unlimited": false },
  "credits": { "balance": 0, "unit_price_eur": "2.00" }
}
Guarda la clave como secreto (variable de entorno). Si se filtra, revócala desde el panel de API y crea una nueva.

Inicio rápido: lanza una auditoría

Envía un POST con la URL del sitio y la lista de documentos legales a auditar (la IA clasifica cada documento). La respuesta llega de inmediato con status: "queued"; la auditoría se ejecuta en segundo plano. Usa la cabecera Idempotency-Key para que un reintento no cree (ni cobre) dos auditorías.

curl -X POST https://api.leyzly.com/api/v1/scans/ \
  -H "X-Api-Key: $LEYZLY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://midominio.com",
    "documents": [
      { "url": "https://midominio.com/privacidad" },
      { "url": "https://midominio.com/cookies" }
    ]
  }'

Respuesta · 201 Created

{
  "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "slug": "K7M2P9QXZ4",
  "url": "https://midominio.com",
  "host": "midominio.com",
  "status": "queued",
  "score": null,
  "created_at": "2026-06-15T10:32:04.512831Z",
  "started_at": null,
  "finished_at": null,
  "n_findings": 0,
  "public_url": "https://leyzly.com/audit/K7M2P9QXZ4",
  "is_public": true
}

La respuesta inmediata llega con status: "queued" y sin resultados aún. Si reenvías la misma Idempotency-Key, recibes la auditoría original con 200 OK en lugar de 201.

Después, sondea el detalle hasta que status sea completed o failed — o mejor, suscríbete a un webhook y evita el polling.

curl https://api.leyzly.com/api/v1/scans/SCAN_ID/ \
  -H "X-Api-Key: $LEYZLY_API_KEY"
# status: queued -> running -> completed | failed

Respuesta · 200 OK · auditoría completada

{
  "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "slug": "K7M2P9QXZ4",
  "url": "https://midominio.com",
  "host": "midominio.com",
  "status": "completed",
  "score": 68,
  "created_at": "2026-06-15T10:32:04.512831Z",
  "started_at": "2026-06-15T10:32:06.001000Z",
  "finished_at": "2026-06-15T10:33:11.882000Z",
  "n_findings": 1,
  "public_url": "https://leyzly.com/audit/K7M2P9QXZ4",
  "is_public": true,
  "engine_version": "0.1.0",
  "error_message": "",
  "source": "user",
  "ai_skip_reason": "",
  "pdf_url": "https://api.leyzly.com/api/v1/reports/scans/f47ac10b…/pdf/?sig=…&exp=…",
  "findings_grouped": {
    "critical": [],
    "high": [],
    "medium": [],
    "low": [],
    "passing": [],
    "ai": {
      "privacy_policy": [
        {
          "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "rule": {
            "code": "AI_PRIV_NO_RETENTION",
            "title": "Falta plazo de conservación de los datos",
            "severity": "high",
            "category": "legal_pages",
            "legal_refs": [
              {
                "law": "RGPD",
                "article": "Art. 13.2.a",
                "quote": "el plazo durante el cual se conservarán los datos personales",
                "source_url": "https://eur-lex.europa.eu/legal-content/ES/TXT/?uri=CELEX:32016R0679"
              }
            ],
            "remediation_html": "<p>Añade una cláusula con el plazo de conservación…</p>"
          },
          "severity": "high",
          "is_passing": false,
          "source": "ai",
          "confidence": 0.88,
          "evidence_quote": "Conservaremos sus datos durante el tiempo necesario.",
          "audited_url": "https://midominio.com/privacidad",
          "provenance": { "provider": "google", "model": "gemini-2.5-flash", "task": "audit_privacy" }
        }
      ],
      "cookie_policy": [],
      "legal_notice": [],
      "terms": [],
      "coherence": []
    }
  },
  "behavior_report": {
    "trackers": [
      { "key": "ga4", "label": "Google Analytics 4", "detected_via": "request" }
    ],
    "cookies_pre_consent": [
      { "name": "_ga", "domain": ".midominio.com", "tracker": "ga4", "secure": false, "http_only": false }
    ],
    "security": {
      "https": true,
      "hsts": false,
      "x_content_type_options": true,
      "referrer_policy": false
    },
    "banner": { "present": true, "has_reject": false, "has_granular": false },
    "forms": [
      { "action": "/contacto", "sensitive_fields": ["email"], "has_consent": false, "has_privacy_link": true }
    ],
    "built_at": "2026-06-15T10:33:09.120000Z"
  },
  "documents": [
    {
      "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
      "url": "https://midominio.com/privacidad",
      "type_label": "Política de Privacidad",
      "status": "audited",
      "detected_type": "privacy_policy",
      "type_matches": true,
      "validation_confidence": 0.96,
      "counts_as_usage": true,
      "error_message": "",
      "created_at": "2026-06-15T10:32:04.600000Z"
    }
  ]
}

Cada elemento de findings_grouped sigue la forma de un hallazgo de la sección Consultar hallazgos. Los hallazgos de IA se agrupan bajo ai por tipo de documento y son privados del propietario. La pdf_url es una URL firmada (válida 24 h) y solo se devuelve al propietario de una auditoría completada — en otro caso es null.

Consultar hallazgos

Obtén los hallazgos en formato plano y filtrable. Cada hallazgo incluye la regla (rule) con su cita legal, la severidad, y — para los hallazgos de IA — la cita textual del documento (evidence_quote) y la confianza. Los hallazgos de IA son privados del propietario de la auditoría.

curl "https://api.leyzly.com/api/v1/scans/SCAN_ID/findings/?severity=critical&source=ai" \
  -H "X-Api-Key: $LEYZLY_API_KEY"

Respuesta · 200 OK (paginada)

{
  "count": 7,
  "next": "https://api.leyzly.com/api/v1/scans/f47ac10b…/findings/?page=2",
  "previous": null,
  "results": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "rule": {
        "code": "AI_PRIV_NO_RETENTION",
        "title": "Falta plazo de conservación de los datos",
        "positive_title": "Plazo de conservación especificado",
        "description": "La política debe indicar cuánto tiempo se conservan los datos personales.",
        "severity": "high",
        "category": "legal_pages",
        "legal_refs": [
          {
            "law": "RGPD",
            "article": "Art. 13.2.a",
            "quote": "el plazo durante el cual se conservarán los datos personales",
            "source_url": "https://eur-lex.europa.eu/legal-content/ES/TXT/?uri=CELEX:32016R0679"
          }
        ],
        "remediation_html": "<p>Añade una cláusula con el plazo de conservación…</p>"
      },
      "severity": "high",
      "is_passing": false,
      "evidence": { "kind": "privacy_policy" },
      "location": "",
      "created_at": "2026-06-15T10:33:05.400000Z",
      "source": "ai",
      "confidence": 0.88,
      "evidence_quote": "Conservaremos sus datos durante el tiempo necesario.",
      "audited_url": "https://midominio.com/privacidad",
      "provenance": { "provider": "google", "model": "gemini-2.5-flash", "task": "audit_privacy" }
    }
  ]
}

developers.findings.after

Referencia de endpoints

Todos los endpoints viven bajo /api/v1/ y devuelven JSON. Los marcados como clave requieren tu API key; los públicos no.

Auditorías (scans)

POST/api/v1/scans/clave

Lanza una auditoría (sitio + documentos legales). Admite Idempotency-Key.

GET/api/v1/scans/clave

Lista tus auditorías. Filtros: status, host__contains, ordering. Paginado.

GET/api/v1/scans/{id}/clave

Detalle de una auditoría: score, behavior_report, documentos y pdf_url firmada.

DELETE/api/v1/scans/{id}/clave

Elimina una auditoría (borrado lógico). Solo el propietario.

POST/api/v1/scans/{id}/rescan/clave

Repite una auditoría con la misma URL y documentos.

GET/api/v1/scans/{id}/findings/clave

Hallazgos en formato plano. Filtros: severity, source, is_passing, ordering.

Los hallazgos de IA son privados del propietario.

GET/api/v1/scans/{id}/documents/clave

Documentos enviados con su tipo detectado por IA y estado de auditoría.

GET/api/v1/scans/{id}/report.pdfclave

Descarga el informe PDF (también disponible como pdf_url firmada 24 h).

Catálogo y cuenta

GET/api/v1/rules/público

Catálogo público de reglas de auditoría. Filtros: category, severity.

GET/api/v1/rules/{code}/público

Detalle de una regla, con cita legal y remediación.

GET/api/v1/account/clave

Tu identidad, plan, uso del mes y saldo de créditos en una sola llamada.

Webhooks

GET/api/v1/webhooks/deliveries/clave

Registro de entregas para depurar. Filtros: endpoint, event, status.

POST/api/v1/webhooks/endpoints/{id}/test/clave

Envía un evento de prueba (ping) firmado a tu endpoint. Requiere sesión.

Esquema completo OpenAPI: https://api.leyzly.com/api/v1/schema/ · Swagger UI en /api/v1/docs/.

Webhooks

Registra uno o varios endpoints HTTPS desde el panel y Leyzly te notificará los eventos del ciclo de vida de cada auditoría con un POST firmado. Cada entrega se reintenta hasta 5 veces con backoff exponencial; un 2xx la marca como entregada.

EventoCuándo
scan.startedLa auditoría empieza a ejecutarse.
scan.completedLa auditoría termina con éxito (incluye pdf_url firmada).
scan.failedLa auditoría falla (incluye error_message).
pingEvento de prueba que disparas tú desde el panel o la API.

Ejemplo del cuerpo de un evento scan.completed:

{
  "event": "scan.completed",
  "scan": {
    "id": "8f3c…",
    "slug": "a1B2c3",
    "url": "https://midominio.com",
    "host": "midominio.com",
    "status": "completed",
    "score": 72,
    "source": "user",
    "started_at": "2026-06-15T10:00:00Z",
    "finished_at": "2026-06-15T10:01:03Z",
    "findings_by_severity": { "critical": 1, "high": 2, "medium": 4, "low": 0 },
    "documents": [
      { "url": "https://midominio.com/privacidad", "status": "audited", "detected_type": "privacy_policy" }
    ],
    "pdf_url": "https://api.leyzly.com/api/v1/reports/signed/…",
    "api_url": "/api/v1/scans/8f3c…/"
  }
}

Verificar la firma

developers.webhooks.verifyIntro

import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
    # header == "t=1700000000,v1=abcd..."
    parts = dict(p.split("=", 1) for p in header.split(","))
    ts, sig = int(parts["t"]), parts["v1"]
    if abs(time.time() - ts) > tolerance:
        return False  # stale: replay protection
    signed = f"{ts}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

# Flask: verify(request.get_data(), request.headers["X-Leyzly-Signature"], SECRET)
developers.webhooks.debugNote

Convenciones, límites y errores

Paginación

developers.conventions.paginationBody

Idempotencia

Envía Idempotency-Key al crear auditorías; un reintento con la misma clave devuelve la auditoría original.

Límites

Hasta 200 auditorías al día por cuenta. Al superarlo recibes 429.

Formato

JSON en peticiones y respuestas (Content-Type: application/json). Fechas en ISO 8601 UTC.

CódigoSignificado
400Petición inválida (URL no admitida, JSON mal formado…).
401API key ausente, inválida, revocada o sin plan con acceso API.
402Sin créditos ni suscripción para financiar la auditoría.
403No tienes permiso sobre ese recurso.
404Recurso no encontrado (o no es tuyo / no es público).
429Has superado el límite de peticiones (200 auditorías/día).

Formato del cuerpo de error

Las respuestas de error son JSON en una de estas tres formas. Los errores de validación devuelven un objeto con los mensajes por campo:

400 · Validación

{
  "url": ["Solo se admiten URLs http(s)."]
}

La autenticación, los permisos y los 404 devuelven solo detail:

401 · Autenticación

{
  "detail": "Las credenciales de autenticación no se proveyeron."
}

Los errores de negocio añaden un campo code estable (para programar contra él) y, a veces, datos extra. Por ejemplo, al lanzar una auditoría sin saldo:

402 · Negocio (con code)

{
  "detail": "Necesitas 2 crédito(s) y tienes 0. Compra auditorías sueltas o suscríbete.",
  "code": "insufficient_credits",
  "credits_balance": 0,
  "credits_needed": 2,
  "upgrade_url": "/precios"
}

Códigos de negocio (code)

codeHTTPCuándo
insufficient_credits402Sin suscripción y sin créditos suficientes para los documentos del scan. Incluye credits_balance, credits_needed y upgrade_url.
trial_exhausted429Prueba gratuita anónima agotada (límite por IP/dominio). Solo en el flujo sin clave.
captcha_failed403Verificación anti-bots (Turnstile) no superada. Solo en el flujo anónimo, no con API key.

Si un suscriptor agota su cupo mensual (o se alcanza un tope de gasto), la auditoría no falla con un error HTTP: termina completed con el informe de comportamiento, pero ai_skip_reason indica por qué se omitió la capa de IA:

  • quota_exceededCupo mensual de auditorías IA del plan agotado.
  • no_creditsSin suscripción ni créditos para financiar la capa de IA.
  • overage_cap_reachedAlcanzado tu tope mensual de gasto en exceso.
  • budget_exceededLímite global de presupuesto de IA del servicio.

¿List@ para integrar?

Crea tu API key, conecta un webhook y lanza tu primera auditoría en minutos. ¿Dudas? Escríbenos a hola@leyzly.com.