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" }
}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 | failedRespuesta · 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)
/api/v1/scans/claveLanza una auditoría (sitio + documentos legales). Admite Idempotency-Key.
/api/v1/scans/claveLista tus auditorías. Filtros: status, host__contains, ordering. Paginado.
/api/v1/scans/{id}/claveDetalle de una auditoría: score, behavior_report, documentos y pdf_url firmada.
/api/v1/scans/{id}/claveElimina una auditoría (borrado lógico). Solo el propietario.
/api/v1/scans/{id}/rescan/claveRepite una auditoría con la misma URL y documentos.
/api/v1/scans/{id}/findings/claveHallazgos en formato plano. Filtros: severity, source, is_passing, ordering.
Los hallazgos de IA son privados del propietario.
/api/v1/scans/{id}/documents/claveDocumentos enviados con su tipo detectado por IA y estado de auditoría.
/api/v1/scans/{id}/report.pdfclaveDescarga el informe PDF (también disponible como pdf_url firmada 24 h).
Catálogo y cuenta
/api/v1/rules/públicoCatálogo público de reglas de auditoría. Filtros: category, severity.
/api/v1/rules/{code}/públicoDetalle de una regla, con cita legal y remediación.
/api/v1/account/claveTu identidad, plan, uso del mes y saldo de créditos en una sola llamada.
Webhooks
/api/v1/webhooks/deliveries/claveRegistro de entregas para depurar. Filtros: endpoint, event, status.
/api/v1/webhooks/endpoints/{id}/test/claveEnví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.
| Evento | Cuándo |
|---|---|
scan.started | La auditoría empieza a ejecutarse. |
scan.completed | La auditoría termina con éxito (incluye pdf_url firmada). |
scan.failed | La auditoría falla (incluye error_message). |
ping | Evento 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)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ódigo | Significado |
|---|---|
400 | Petición inválida (URL no admitida, JSON mal formado…). |
401 | API key ausente, inválida, revocada o sin plan con acceso API. |
402 | Sin créditos ni suscripción para financiar la auditoría. |
403 | No tienes permiso sobre ese recurso. |
404 | Recurso no encontrado (o no es tuyo / no es público). |
429 | Has 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)
| code | HTTP | Cuándo |
|---|---|---|
insufficient_credits | 402 | Sin suscripción y sin créditos suficientes para los documentos del scan. Incluye credits_balance, credits_needed y upgrade_url. |
trial_exhausted | 429 | Prueba gratuita anónima agotada (límite por IP/dominio). Solo en el flujo sin clave. |
captcha_failed | 403 | Verificació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_exceeded— Cupo mensual de auditorías IA del plan agotado.no_credits— Sin suscripción ni créditos para financiar la capa de IA.overage_cap_reached— Alcanzado tu tope mensual de gasto en exceso.budget_exceeded— Lí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.