API v3

Preguntas sobre la API

Lo que preguntan los desarrolladores antes y durante la conexión: claves, enlaces, cómo llega la decisión a tu sitio, webhooks, el registro, límites y errores. Cada método y cada cifra de esta página son los que usa hoy la API.

Dirección
https://api.consent.link/v3
Clave
Authorization: Bearer clk_live_…
Formato
JSON, UTF-8, respuestas en inglés

Primeros pasos

¿Cómo consigo una clave de API?

Regístrate y confirma tu correo. La primera página tras iniciar sesión, «Conectar por API», crea tu clave clk_live_….

La clave se muestra una sola vez, justo al crearla, así que cópiala en ese momento: solo guardamos su hash. Una cuenta puede tener hasta 10 claves activas. La clave que ya no necesites se revoca en la misma página y deja de funcionar al instante.

¿Dónde está la API y cómo envío la clave?

La API está en https://api.consent.link/v3 y habla JSON. Envía la clave en cada petición en la cabecera Authorization: Bearer clk_live_… (también vale la cabecera X-AUTH-TOKEN de nuestra antigua API v2). Una petición sin una clave válida recibe 401 unauthenticated.

Llama a la API solo desde tu servidor. Quien tenga la clave puede crear enlaces y leer tu registro con todos los datos de los visitantes.

Petición
curl https://api.consent.link/v3/usage \
  -H "Authorization: Bearer clk_live_…"
Respuesta
{
  "consents": 412,
  "limit": 1000,
  "remaining": 588,
  "period_start": "2026-11-01T00:00:00.000Z",
  "period_end": "2026-12-01T00:00:00.000Z"
}

¿Qué métodos hay?

Todos de un vistazo. Cada ruta va detrás de https://api.consent.link; las claves públicas para comprobar los tokens de decisión están en https://api.consent.link/.well-known/jwks.json.

MétodoQué hace
POST/v3/linksCrear un enlace
POST/v3/links/bulkCrear hasta 500 enlaces
GET/v3/linksListar enlaces, con filtros
GET/v3/links/{id}Un enlace y sus cifras
PATCH/v3/links/{id}Cambiar un enlace
DELETE/v3/links/{id}Desactivar un enlace
GET/v3/profilesListar perfiles de página
POST/v3/profilesCrear un perfil de página
GET/v3/profiles/{id}Un perfil de página
PATCH/v3/profiles/{id}Cambiar o publicar un perfil
DELETE/v3/profiles/{id}Eliminar un perfil
POST/v3/consents/verifyComprobar una vez un token de decisión
GET/v3/webhooksLista de webhooks
POST/v3/webhooksAñadir un webhook
GET/v3/webhooks/{id}Un webhook
PATCH/v3/webhooks/{id}Cambiar la URL o los eventos, o activar y desactivar
DELETE/v3/webhooks/{id}Eliminar un webhook
GET/v3/consentsEl registro, con filtros
GET/v3/consents/{id}Un registro con sus pruebas
POST/v3/consents/{id}/withdrawRetirar un consentimiento en nombre del visitante
GET/v3/consents/{id}/proofPDF de prueba de un registroPro y Business
GET/v3/consents/{id}/snapshotLa página que vio el visitante, en HTML
GET/v3/consents/{id}/snapshot.pngLa página que vio el visitante, como captura de pantalla
POST/v3/exportsIniciar una exportación CSVPro y Business
GET/v3/exports/{id}Una exportación y su estadoPro y Business
GET/v3/exports/{id}/fileDescargar el CSVPro y Business
GET/v3/stats/summaryTotales de un rango de días
GET/v3/stats/dailyCifras por día (y por país)
GET/v3/usageConsentimientos del periodo y el límite

La página de consentimiento

¿Qué verán los visitantes y en qué idioma?

La página que configuras en «Diseño» en el panel: la plantilla de texto de nuestra biblioteca, los destinatarios de los datos, la etiqueta, el enlace a tu política de privacidad y, en Business, tu logotipo y tu color. Los type y template propios del enlace tienen prioridad sobre el diseño. Cuando publicas una versión nueva, las páginas de tus enlaces cambian en menos de un minuto.

El idioma se toma del navegador del visitante: inglés, español, alemán, francés, italiano, portugués o ruso. Eliges una plantilla, no palabras: los textos vienen de nuestra biblioteca, así que no puedes escribir los tuyos.

Mientras no haya nada publicado en «Diseño», la página no muestra el enlace a tu política de privacidad ni los destinatarios de los datos. Los enlaces se crean igualmente, y las respuestas a POST /v3/links, POST /v3/links/bulk y PATCH /v3/links/{id} llevan la cabecera Consent-Link-Warning: design_unpublished mientras la configuración del enlace no tenga una versión publicada.

¿Cómo doy a distintos enlaces distintos ajustes de página?

Guarda varios perfiles de la página en «Diseño» o con la API, cada uno con su propio id, como black-friday, e indica uno al crear un enlace: "profile": "black-friday" en POST /v3/links o en un elemento de la creación masiva. Sin profile, el enlace recibe tu perfil predeterminado.

Un enlace conserva su perfil: si más tarde otro perfil pasa a ser el predeterminado, los enlaces antiguos no cambian. PATCH /v3/links/{id} con profile mueve un enlace, y "profile": null lo mueve al predeterminado de ese momento. Un id desconocido da 422 en el campo profile. El objeto del enlace muestra su profile, y GET /v3/links?profile=black-friday lista los enlaces de un perfil. Cuántos perfiles puedes tener depende del plan: 1 en Free, 5 en Pro, 50 en Business, contando el predeterminado.

¿Cómo gestiono los perfiles con la API?

GET /v3/profiles los lista, el predeterminado primero. POST /v3/profiles crea uno con id y policy_url (obligatorios) y, si quieres, name, template, vendors, type, remember, brand_name y default. Se publica al instante. PATCH /v3/profiles/{id} lo cambia: los campos de la página publican una versión nueva en el acto; name y "default": true por sí solos no publican nada.

DELETE /v3/profiles/{id} elimina un perfil que no sea el predeterminado (409 profile_is_default) y que no tenga enlaces activos (409 profile_in_use). Crear uno por encima del límite del plan, o usar uno que quedó por encima del límite tras bajar de plan, da 402 profiles_limit_reached. Los cambios necesitan una clave real.

¿Qué pasa con los parámetros de mi dirección, como utm o gclid?

Con consentimiento, el visitante va a tu destino exactamente como lo indicaste. Sin consentimiento, utm_* y tus demás parámetros también se quedan, y solo se quitan los identificadores de clic de anuncios: gclid, gbraid, wbraid, fbclid, msclkid, ttclid, twclid, li_fat_id. En ambos casos añadimos un parámetro, cl_ct, el token de decisión.

Solo cuentan los parámetros del propio destino: lo que se añada al enlace corto (https://consent.link/nk7Qa?utm_source=…) no se transmite. Pon en destination todo lo que necesita tu sitio, o crea un enlace por campaña.

Adónde va el visitante: con consentimiento y sin él
https://shop.example/sale?utm_source=insta&gclid=Cj0K…&cl_ct=eyJhbGciOiJFUzI1NiIs…
https://shop.example/sale?utm_source=insta&cl_ct=eyJhbGciOiJFUzI1NiIs…

Decisiones y webhooks

¿Cómo sabe mi sitio qué decidió el visitante?

Cada redirección desde la página de consentimiento lleva cl_ct, un token firmado (JWT, ES256) que vive 10 minutos. Compruébalo desde tu servidor con POST /v3/consents/verify: la respuesta trae la decisión, y cada token se puede comprobar una sola vez, así que una dirección copiada no sirve de nada. Lee el token y luego quítalo de la dirección.

Lo que contiene: decision (granted o denied), purposes (lo que permitió el visitante), cid (el registro), sid (el seudónimo del visitante en tu cuenta, el mismo en todos tus enlaces), cexp (hasta cuándo vale el consentimiento, en tiempo Unix), link, mode, iat y exp. Ni IP, ni navegador, ni otros datos personales. Un visitante que ya aceptó y dejó marcado «No volver a preguntar» pasa directamente, con granted.

También puedes comprobar la firma tú mismo con las claves de https://api.consent.link/.well-known/jwks.json y cualquier biblioteca JWT: comprueba exp y que link sea uno de tus enlaces. Tu propia comprobación no sabe si un token ya se usó; el método de la API sí. Errores: 422 token_invalid, 422 token_expired, 409 token_used.

Petición
curl -X POST https://api.consent.link/v3/consents/verify \
  -H "Authorization: Bearer clk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"token": "eyJhbGciOiJFUzI1NiIs…"}'
Respuesta
{
  "decision": "granted",
  "purposes": ["stats", "ads"],
  "cid": "01jbz8m3k4x9w2r7d5t6q8v1yh",
  "sid": "psd_Qm9xV3c…",
  "cexp": 1809345604,
  "link": "nk7Qa",
  "mode": "live",
  "iat": 1793712004,
  "exp": 1793712604
}

¿Cómo recibo las decisiones en mi servidor en el momento?

Crea un webhook: POST /v3/webhooks con url y los events que quieras (la tabla de abajo). La dirección debe ser https con un nombre de host público. La respuesta trae secret (whsec_…) para comprobar las firmas: se muestra una sola vez, así que guárdalo. Una cuenta puede tener hasta 10 webhooks activos.

El historial de entregas, el botón «Reintentar» y la activación o desactivación de un webhook están en el panel, en «Cuenta» → «Webhooks».

Petición
curl -X POST https://api.consent.link/v3/webhooks \
  -H "Authorization: Bearer clk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://shop.example/hooks/consent",
    "events": ["consent.recorded", "consent.withdrawn"]
  }'
Respuesta
201 Created
{
  "id": "wh_01jbz9a7c2m5n8p3q6r9s2t5v8",
  "url": "https://shop.example/hooks/consent",
  "events": ["consent.recorded", "consent.withdrawn"],
  "mode": "live",
  "status": "active",
  "secret": "whsec_…",
  "created_at": "2026-11-03T12:00:00.000Z"
}

¿Qué eventos hay y qué llevan?

Cada evento es un POST con un cuerpo JSON: id (evt_…), type, created_at, mode y data. Un registro de consentimiento en data.consent tiene id, link, decision, purposes, scope (domain si el visitante dejó marcado «No volver a preguntar», si no link), sid, dest_domain, country, notice_version, created_at y expires_at. Nunca una IP, un navegador ni una cookie.

EventoCuándodata
consent.recordedUn registro nuevo: una aceptación, un rechazo o un visitante que ya había aceptadoconsent
consent.withdrawnSe retiró un consentimiento: el visitante en «Mis consentimientos» o tú mediante la APIconsent, withdraws
link.blockedBloqueamos un enlace porque su destino resultó peligrosolink, destination, reason, message
export.readyUna exportación CSV ha terminado (o ha fallado)id, status, filter, rows, bytes, sha256, file, expires_at
usage.warningSe ha usado el 80 % o el 100 % de los consentimientos del periodopercent, consents, limit, period_start, period_end
consent.expiredUn consentimiento caducó: se dio hace 6 meses y no se retiró desde entoncesconsent, expired_at
Lo que recibe tu servidor
POST /hooks/consent
Content-Type: application/json
Consent-Event: consent.recorded
Consent-Delivery: whd_01jbz9d4f7h0k3m6p9r2t5w8y1
Consent-Signature: t=1793712005,v1=5f3a…

{
  "id": "evt_01jbz9d4f6g9j2l5n8q1s4v7x0",
  "type": "consent.recorded",
  "created_at": "2026-11-03T12:00:05.012Z",
  "mode": "live",
  "data": {
    "consent": {
      "id": "01jbz8m3k4x9w2r7d5t6q8v1yh",
      "link": "nk7Qa",
      "decision": "granted",
      "purposes": ["stats", "ads"],
      "scope": "domain",
      "sid": "psd_Qm9xV3c…",
      "dest_domain": "shop.example",
      "country": "DE",
      "notice_version": "statsads-v1",
      "created_at": "2026-11-03T12:00:04.398Z",
      "expires_at": "2027-05-03T12:00:04.398Z"
    }
  }
}

¿Cómo compruebo que un webhook viene de vosotros?

Cada petición lleva las cabeceras Consent-Event, Consent-Delivery y Consent-Signature: t=1793712000,v1=5f3a…. v1 es el HMAC-SHA256 en hexadecimal del texto t, un punto y el cuerpo bruto de la petición, con el secreto de tu webhook como clave.

Calcúlalo sobre el cuerpo tal como llegó (antes de interpretar el JSON), compara en tiempo constante y rechaza un t que se aleje más de cinco minutos de tu reloj, para que una petición grabada no se pueda reenviar.

PHP
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_CONSENT_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', ($sig['t'] ?? '').'.'.$body, $secret);
if (abs(time() - (int) ($sig['t'] ?? 0)) > 300 || ! hash_equals($expected, $sig['v1'] ?? '')) {
    http_response_code(400);
    exit;
}
Node.js
const crypto = require('node:crypto');
const sig = Object.fromEntries(req.get('Consent-Signature').split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', secret).update(`${sig.t}.${rawBody}`).digest('hex');
const ok = Math.abs(Date.now() / 1000 - Number(sig.t)) <= 300
  && typeof sig.v1 === 'string' && sig.v1.length === expected.length
  && crypto.timingSafeEqual(Buffer.from(sig.v1), Buffer.from(expected));

¿Y si mi servidor está caído cuando se envía un evento?

Una entrega cuenta cuando tu servidor responde 2xx en 10 segundos; las redirecciones no se siguen. Si no, lo intentamos de nuevo, esperando 1 minuto, 5 minutos, 30 minutos, 2 horas, 12 horas entre intentos, 6 intentos en total. Tras el último, la entrega se marca como fallida y se queda en el historial, donde «Reintentar» la vuelve a enviar hasta 3 días después del evento.

Así que responde rápido y haz el trabajo después. El mismo evento puede llegar más de una vez, por ejemplo tras un tiempo de espera agotado: omite los id que ya procesaste. Los eventos no siempre llegan en el orden en que ocurrieron.

¿Cómo se retira un consentimiento y qué pasa después?

Los visitantes ven y retiran sus consentimientos en «Mis consentimientos». Si un visitante te lo pide a ti, retíralo en su nombre: POST /v3/consents/{id}/withdraw, sin cuerpo, con el id de su registro granted o reused. Funciona para cualquier consentimiento aún vigente, sin importar hasta dónde muestre tu plan el registro. La respuesta es 201 con el nuevo registro withdrawn, cuyo first_consent_id es el consentimiento retirado. Un registro que no es un consentimiento vigente (rechazado, ya retirado o caducado) recibe 409 consent_not_in_force; uno que no puedes leer, 404, como con GET /v3/consents/{id}.

Las dos vías dan el mismo resultado: un registro withdrawn, consent.withdrawn en tu webhook con data.withdraws, el id del consentimiento retirado, y a partir de ese momento tus enlaces vuelven a preguntar a ese visitante. Una retirada cierra todos los consentimientos que el visitante dio antes para ese dominio, y no cuenta para tu límite.

Deja de usar los datos del visitante por tu parte: búscalos por sid o por el id del registro.

Petición
curl -X POST https://api.consent.link/v3/consents/01jbz8m3k4x9w2r7d5t6q8v1yh/withdraw \
  -H "Authorization: Bearer clk_live_…"
Respuesta
201 Created
{
  "id": "01jc4t7v0x3z6b9d2f5h8k1m4p",
  "link": "nk7Qa",
  "decision": "withdrawn",
  "purposes": [],
  "scope": null,
  "sid": "psd_Qm9xV3c…",
  "dest_domain": "shop.example",
  "country": null,
  "notice_version": null,
  "created_at": "2026-12-14T09:30:12.051Z",
  "expires_at": null,
  "first_consent_id": "01jbz8m3k4x9w2r7d5t6q8v1yh",
  "gpc": false,
  "settings_version": null,
  "evidence": null,
  "chain": {…}
}

¿Cuánto dura un consentimiento?

Un consentimiento vale 6 meses desde que se dio: expires_at en el registro, cexp en el token de decisión. Un registro reused lleva el expires_at del consentimiento que reutiliza. Después, tus enlaces vuelven a preguntar al visitante.

Cuando un consentimiento granted caduca sin haberse retirado, tu webhook recibe consent.expired con data.consent, el registro, y data.expired_at. Lo comprobamos una vez por hora, así que llega en la hora siguiente a expires_at, una vez por consentimiento. Los registros reused no tienen evento propio: los cubre el del consentimiento que reutilizan. El evento solo va a los webhooks con consent.expired en events. Cada caducidad se avisa una sola vez, aunque entonces ningún webhook la esperara: no se vuelve a enviar más tarde.

Lo que recibe tu servidor
POST /hooks/consent
Content-Type: application/json
Consent-Event: consent.expired
Consent-Delivery: whd_01jxq3m6p9r2t5w8y1b4d7f0h3
Consent-Signature: t=1809349502,v1=9c1e…

{
  "id": "evt_01jxq3m6p8q1s4v7x0z3b6d9f2",
  "type": "consent.expired",
  "created_at": "2027-05-03T13:05:02.117Z",
  "mode": "live",
  "data": {
    "consent": {
      "id": "01jbz8m3k4x9w2r7d5t6q8v1yh",
      "link": "nk7Qa",
      "decision": "granted",
      "purposes": ["stats", "ads"],
      "scope": "domain",
      "sid": "psd_Qm9xV3c…",
      "dest_domain": "shop.example",
      "country": "DE",
      "notice_version": "statsads-v1",
      "created_at": "2026-11-03T12:00:04.398Z",
      "expires_at": "2027-05-03T12:00:04.398Z"
    },
    "expired_at": "2027-05-03T12:00:04.398Z"
  }
}

Sitio y banners

¿Cómo aplica mi sitio la decisión del visitante por sí mismo (Google Consent Mode v2)?

Coloca nuestro script en <head>, antes de GTM y gtag: https://consent.link/js/consent.js. Fija los valores predeterminados de Consent Mode v2 (todo denegado salvo functionality_storage y security_storage, con wait_for_update de 500 ms), lee cl_ct de la dirección, comprueba su firma con las claves de https://api.consent.link/.well-known/jwks.json en el propio navegador y actualiza el consentimiento.

La decisión se guarda en la cookie cl_consent de tu dominio: hasta cexp si hay consentimiento y 180 días si hay rechazo, así que las páginas siguientes no necesitan banner. Tras comprobarlo, cl_ct sale de la barra de direcciones; tus otros parámetros se quedan. El script no nos envía nada.

Si guardas la decisión en tu lado, comprueba también el token desde tu servidor con POST /v3/consents/verify: el script no puede saber si el token ya se usó.

HTML
<script src="https://consent.link/js/consent.js"></script>

¿Cómo lo configuro en Google Tag Manager?

Descarga la plantilla consent-link.tpl; en GTM abre Plantillas → Nueva → ⋮ → Importar y guárdala. Crea una etiqueta con ella y el activador «Consent Initialization – All Pages» («Inicialización del consentimiento – Todas las páginas»).

La etiqueta fija los valores predeterminados, aplica la decisión guardada en la cookie cl_consent y carga el script, que comprueba un cl_ct nuevo. Así no hace falta poner también el script en <head>.

¿Qué señales de Consent Mode concede cada finalidad?

Las purposes del token conceden estas señales. Lo que no se concede sigue denegado; un rechazo (denied) deniega todo salvo las dos que siempre se conceden.

FinalidadSeñales de Consent Mode
stats analytics_storage
ads ad_storage, ad_user_data, ad_personalization
app_install ad_storage, ad_user_data
personalization personalization_storage
Siempre functionality_storage, security_storage

Uso Cookiebot, OneTrust u otro banner. ¿Qué hago con él?

No muestres un segundo banner a quien ya decidió en nuestra página. El script da la decisión en window.consentLink.decision (granted, denied o null) y window.consentLink.has('stats'); window.consentLink.ready se resuelve cuando se ha comprobado el token de la dirección. También están el evento consentlink:update en document y consent_link_update en dataLayer. Carga nuestro script antes que el del banner.

Cookiebot: pasa la decisión con Cookiebot.submitCustomConsent y su banner no aparece. OneTrust no tiene una función pública para registrar el consentimiento desde el sitio, así que carga su script solo cuando no haya decisión. Con otros banners igual: pasa las finalidades por su API o no cargues el banner.

Cookiebot
window.addEventListener('CookiebotOnLoad', function () {
  window.consentLink.ready.then(function (cl) {
    if (cl.decision && !Cookiebot.hasResponse) {
      Cookiebot.submitCustomConsent(cl.has('personalization'), cl.has('stats'), cl.has('ads'));
    }
  });
});
OneTrust
window.consentLink.ready.then(function (cl) {
  if (cl.decision) return; // decided on consent.link: no second banner
  var s = document.createElement('script');
  s.src = 'https://cdn.cookielaw.org/scripttemplates/otSDKStub.js';
  s.setAttribute('data-domain-script', 'YOUR-ONETRUST-ID');
  document.head.appendChild(s);
});

Registro, pruebas y estadísticas

¿Cómo leo el registro?

GET /v3/consents lista los registros, los más nuevos primero, con los filtros decision (granted, denied, reused, withdrawn), link, country (DE), from, to y la misma paginación que los enlaces. GET /v3/consents/{id} devuelve un registro con sus pruebas (dirección IP, navegador, idioma, el hash del texto de la página, cuándo se mostró la página y cuánto tardó el visitante) y su lugar en la cadena de hashes, chain.

Un rechazo guarda solo la decisión, la hora, el enlace y el país, así que su evidence es null. La API da los datos completos; el panel oculta una parte hasta que activas el inicio de sesión en dos pasos. Hasta dónde puedes ver hacia atrás depende del plan (la tabla de «Planes y límites»).

Petición
curl "https://api.consent.link/v3/consents?decision=granted&from=2026-11-01&limit=100" \
  -H "Authorization: Bearer clk_live_…"

¿Cómo obtengo un PDF de prueba de un consentimiento?

GET /v3/consents/{id}/proof devuelve el registro en PDF: la decisión, quién, qué se mostró, los hashes y cómo comprobarlos. Está en el idioma de tu panel. Disponible en Pro y Business (402 plan_required en Free), hasta 20 por minuto. Si el PDF no se puede generar ahora mismo, la respuesta es 503 unavailable con Retry-After.

Petición
curl -o proof.pdf https://api.consent.link/v3/consents/01jbz8m3k4x9w2r7d5t6q8v1yh/proof \
  -H "Authorization: Bearer clk_live_…"

¿Cómo veo y compruebo la página en la que el visitante dio su consentimiento?

Cada registro granted lleva evidence.snapshot_hash: el SHA-256 de la página de consentimiento tal como la vio el visitante, en HTML. Una misma captura sirve para todos los visitantes de la misma página, es decir, la misma versión de diseño y de texto, idioma y tipo de enlace. GET /v3/consents/{id}/snapshot devuelve ese HTML con el hash en la cabecera X-Snapshot-Hash, y GET /v3/consents/{id}/snapshot.png, una captura de pantalla. Para ambos valen las reglas de GET /v3/consents/{id}, incluido hasta dónde muestra tu plan el registro.

Para comprobarla, calcula el SHA-256 del HTML exactamente como lo recibiste: coincide con snapshot_hash, y el hash del propio registro en la cadena cubre snapshot_hash, así que la página no se puede cambiar después. Lo que varía de una visita a otra está fijado en la captura: el host del destino aparece como example.com, la firma del formulario está vacía y su action es #. La dirección real está en dest_domain y evidence.destination.

Los registros reused, los rechazos y los registros anteriores a las capturas tienen snapshot_hash null, y su captura responde 404 not_found («This record has no page snapshot.»). Justo después de una decisión los archivos pueden estar escribiéndose aún: 404 not_found con «The page snapshot is not ready yet.» significa que lo intentes de nuevo en un momento. El HTML llega en segundos y la captura de pantalla algo después. La página del registro en el panel y la prueba en PDF también muestran la captura y el hash.

Petición
curl -o page.html https://api.consent.link/v3/consents/01jbz8m3k4x9w2r7d5t6q8v1yh/snapshot \
  -H "Authorization: Bearer clk_live_…"

sha256sum page.html

¿Cómo exporto el registro en CSV?

POST /v3/exports con los filtros de GET /v3/consents (decision, link, country, from, to) responde 202 al instante con el id de la exportación. El archivo se genera en segundo plano: recibes el webhook export.ready, o consultas GET /v3/exports/{id} hasta que status sea ready, y luego descargas GET /v3/exports/{id}/file. La exportación también trae rows y el sha256 del archivo.

Los archivos se guardan 7 días (después, 410 export_expired). Puede haber hasta 3 exportaciones en curso a la vez (429 too_many_exports). Pro y Business.

Petición
curl -X POST https://api.consent.link/v3/exports \
  -H "Authorization: Bearer clk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "decision": "granted",
    "from": "2026-10-01",
    "to": "2026-11-01"
  }'
Respuesta
202 Accepted
{
  "id": "exp_01jbz9f2h5k8m1p4r7t0w3y6a9",
  "type": "csv",
  "status": "queued",
  "source": "api",
  "filter": {
    "decision": "granted",
    "from": "2026-10-01T00:00:00.000Z",
    "to": "2026-11-01T00:00:00.000Z"
  },
  "rows": null,
  "bytes": null,
  "sha256": null,
  "created_at": "2026-11-03T12:00:00.000Z",
  "finished_at": null,
  "expires_at": null,
  "file": null
}

¿Puedo obtener estadísticas y mi consumo con la API?

GET /v3/stats/summary da shown, granted, denied, left, reused y withdrawn para los días UTC de from a to (2026-10-31, ambos incluidos; por defecto los últimos 30 días, como máximo 366 días, y hasta donde tu plan muestra el registro). Añade link para un solo enlace. GET /v3/stats/daily da las mismas cifras día a día y, en Pro y Business, por país con by=country. Los bots nunca se cuentan.

GET /v3/usage muestra los consentimientos de este periodo, tu limit, lo que queda (remaining) y el inicio y el fin del periodo.

Petición
curl "https://api.consent.link/v3/stats/summary?from=2026-10-01&to=2026-10-31&link=nk7Qa" \
  -H "Authorization: Bearer clk_live_…"
Respuesta
{
  "from": "2026-10-01",
  "to": "2026-10-31",
  "link": "nk7Qa",
  "shown": 1840,
  "granted": 1215,
  "denied": 402,
  "left": 198,
  "reused": 25,
  "withdrawn": 3
}

Planes y límites

¿Qué incluye cada plan?

La API, los webhooks y la página de consentimiento en siete idiomas están en todos los planes. Los planes se diferencian en los consentimientos al mes, hasta dónde se muestra el registro hacia atrás, el ritmo de peticiones y las funciones extra. Precios en euros, sin IVA.

PlanPrecio al mesConsentimientos al mesRegistroPeticiones por minutoPerfiles de páginaAdemás
Free0 €1.000Registro de 30 días601—
Pro9 €10.000Registro de 1 año3005Exportación CSV y prueba en PDF · Estadísticas por día y país
Business99 €250.000Registro de 3 años1.00050Exportación CSV y prueba en PDF · Estadísticas por día y país · Tu logotipo y tu color · Enlaces en tu propio dominio

¿Qué pasa cuando se me acaban los consentimientos?

Solo cuentan las aceptaciones nuevas: los rechazos, las visitas repetidas con «No volver a preguntar», y los bots no. Al 80 % y al 100 % del límite recibes un correo y el webhook usage.warning.

Una vez agotado el límite, los enlaces reales nuevos se rechazan con 402 limit_reached. Todo lo demás sigue funcionando: los enlaces existentes siguen preguntando y registrando, los webhooks y los tokens llegan como siempre y no se oculta nada. La creación de enlaces se reabre al empezar el periodo siguiente (en Free el día 1 del mes, en los planes de pago el día del pago) o justo después de subir de plan.

¿Cuántas peticiones puedo enviar?

Por cuenta, con todas las claves juntas: 60 por minuto en Free, 300 en Pro, 1.000 en Business. Por encima, la respuesta es 429 rate_limited con una cabecera Retry-After; las cabeceras X-RateLimit-Limit y X-RateLimit-Remaining muestran dónde estás. Algunos métodos tienen además su propio límite: PDF de prueba 20 por minuto, exportaciones nuevas 10, descargas de exportaciones 30.

Errores y seguridad

¿Cómo son los errores?

Siempre con la misma forma: el estado HTTP y un cuerpo JSON con un code estable y un message en inglés. Actúa según el code; el mensaje puede cambiar de redacción. Los campos no válidos dan 422 validation_failed con fields: cada campo con sus problemas.

EstadoCódigoSignificado
401unauthenticatedSin clave, o la clave es incorrecta o está revocada
402limit_reachedSe han agotado los consentimientos del periodo: no hay enlaces reales nuevos
402plan_requiredLa función necesita un plan superior
402profiles_limit_reachedPor encima del número de perfiles de página del plan
403account_blockedLa cuenta está bloqueada: contacta con soporte
403links_blockedLa cuenta no puede crear enlaces: contacta con soporte
403mode_mismatchUn enlace real se cambia con una clave real, uno de prueba con una clave de prueba
403live_key_requiredHace falta una clave real
404not_foundNo hay nada con este id en tu cuenta
405method_not_allowedEste método HTTP no está permitido en esta ruta
409link_disabledEl enlace está desactivado y no se puede cambiar
409link_blockedEl enlace fue bloqueado y no se puede cambiar
409idempotency_in_progressUna petición con esta Idempotency-Key aún está en curso
409token_usedEste token de decisión ya se comprobó
409export_not_readyLa exportación aún se está generando
409export_failedLa exportación falló: inicia una nueva
409consent_not_in_forceEl registro no es un consentimiento vigente: rechazado, retirado o caducado
409profile_is_defaultEl perfil predeterminado no se puede eliminar
409profile_in_useEl perfil aún tiene enlaces activos
410export_expiredEl archivo ya no se guarda: inicia una exportación nueva
422validation_failedAlgunos campos no son válidos: mira fields
422destination_blockedEl destino es peligroso o está bloqueado
422idempotency_key_reusedLa Idempotency-Key se usó con otro cuerpo
422token_invalidNo es un token de decisión válido para tu cuenta
422token_expiredEl token de decisión tiene más de 10 minutos
422webhooks_limit_reachedDemasiados webhooks activos
429rate_limitedDemasiadas peticiones: espera lo que indique Retry-After
429too_many_exportsDemasiadas exportaciones en curso
429daily_limit_reachedCuenta Free nueva: enlaces del día agotados, más desde las 00:00 UTC
500server_errorAlgo falló por nuestra parte: vuelve a intentarlo más tarde
503unavailableVuelve a intentarlo en breve: mira Retry-After
Respuesta
422 Unprocessable Content
{
  "error": {
    "code": "validation_failed",
    "message": "Some fields are invalid.",
    "fields": {
      "destination": ["The destination must be an absolute http or https URL."]
    }
  }
}

¿Qué hago si se filtra una clave?

Crea una clave nueva en «Conectar por API», cambia tu servidor a ella y luego revoca la antigua: deja de funcionar al instante. Revocar una clave no afecta a tus enlaces, al registro ni a los webhooks.

Guarda las claves en las variables de entorno de tu servidor, nunca en un navegador, una app móvil o un repositorio público. Si puede haberse filtrado el secreto de un webhook, desactiva ese webhook en el panel y crea uno nuevo.

¿Listo para conectar?

Regístrate, crea una clave en «Conectar por API» y envía tu primera petición en unos minutos.

Empieza gratis