API v3

Questions sur l’API

Ce que demandent les développeurs avant et pendant la connexion : clés, liens, comment la décision arrive sur votre site, webhooks, journal, limites et erreurs. Chaque méthode et chaque chiffre de cette page sont ceux qu’utilise l’API aujourd’hui.

Adresse
https://api.consent.link/v3
Clé
Authorization: Bearer clk_live_…
Format
JSON, UTF-8, réponses en anglais

Premiers pas

Comment obtenir une clé API ?

Inscrivez-vous et confirmez votre adresse e-mail. La première page après la connexion, « Connexion via l’API », crée votre clé clk_live_….

La clé n’est affichée qu’une fois, juste après sa création : copiez-la à ce moment-là, nous n’en gardons que l’empreinte. Un compte peut avoir jusqu’à 10 clés actives. Une clé dont vous n’avez plus besoin se révoque sur la même page et cesse de fonctionner immédiatement.

Où se trouve l’API et comment envoyer la clé ?

L’API se trouve à https://api.consent.link/v3 et parle JSON. Envoyez la clé avec chaque requête dans l’en-tête Authorization: Bearer clk_live_… (l’en-tête X-AUTH-TOKEN de notre ancienne API v2 fonctionne aussi). Une requête sans clé valide reçoit 401 unauthenticated.

Appelez l’API uniquement depuis votre serveur. Qui détient la clé peut créer des liens et lire votre journal avec toutes les données des visiteurs.

Requête
curl https://api.consent.link/v3/usage \
  -H "Authorization: Bearer clk_live_…"
Réponse
{
  "consents": 412,
  "limit": 1000,
  "remaining": 588,
  "period_start": "2026-11-01T00:00:00.000Z",
  "period_end": "2026-12-01T00:00:00.000Z"
}

Quelles méthodes existe-t-il ?

Toutes, d’un coup d’œil. Chaque chemin se place après https://api.consent.link ; les clés publiques pour vérifier les jetons de décision sont sur https://api.consent.link/.well-known/jwks.json.

MéthodeCe qu’elle fait
POST/v3/linksCréer un lien
POST/v3/links/bulkCréer jusqu’à 500 liens
GET/v3/linksLister les liens, avec filtres
GET/v3/links/{id}Un lien et ses chiffres
PATCH/v3/links/{id}Modifier un lien
DELETE/v3/links/{id}Désactiver un lien
GET/v3/profilesLister les profils de page
POST/v3/profilesCréer un profil de page
GET/v3/profiles/{id}Un profil de page
PATCH/v3/profiles/{id}Modifier ou publier un profil
DELETE/v3/profiles/{id}Supprimer un profil
POST/v3/consents/verifyVérifier une fois un jeton de décision
GET/v3/webhooksListe des webhooks
POST/v3/webhooksAjouter un webhook
GET/v3/webhooks/{id}Un webhook
PATCH/v3/webhooks/{id}Modifier l'URL ou les événements, activer ou désactiver
DELETE/v3/webhooks/{id}Supprimer un webhook
GET/v3/consentsLe journal, avec filtres
GET/v3/consents/{id}Un enregistrement et ses preuves
POST/v3/consents/{id}/withdrawRetirer un consentement au nom du visiteur
GET/v3/consents/{id}/proofPreuve PDF d’un enregistrementPro et Business
GET/v3/consents/{id}/snapshotLa page vue par le visiteur, en HTML
GET/v3/consents/{id}/snapshot.pngLa page vue par le visiteur, en capture d’écran
POST/v3/exportsLancer un export CSVPro et Business
GET/v3/exports/{id}Un export et son étatPro et Business
GET/v3/exports/{id}/fileTélécharger le CSVPro et Business
GET/v3/stats/summaryTotaux sur une période de jours
GET/v3/stats/dailyChiffres par jour (et par pays)
GET/v3/usageConsentements de la période et limite

La page de consentement

Que verront les visiteurs, et dans quelle langue ?

La page que vous configurez dans « Apparence » de l’espace client : le modèle de texte de notre bibliothèque, les destinataires des données, la mention, le lien vers votre politique de confidentialité et, sur Business, votre logo et votre couleur. Les type et template propres au lien priment sur l’apparence. Quand vous publiez une nouvelle version, les pages de vos liens changent en moins d’une minute.

La langue est choisie d’après le navigateur du visiteur : anglais, espagnol, allemand, français, italien, portugais ou russe. Vous choisissez un modèle, pas des mots : les textes viennent de notre bibliothèque, vous ne pouvez donc pas écrire les vôtres.

Tant que rien n'est publié dans « Apparence », la page n'affiche ni le lien vers votre politique de confidentialité ni les destinataires des données. Les liens sont tout de même créés, et les réponses à POST /v3/links, POST /v3/links/bulk et PATCH /v3/links/{id} portent l'en-tête Consent-Link-Warning: design_unpublished tant que la configuration du lien n'a pas de version publiée.

Comment donner à différents liens des réglages de page différents ?

Enregistrez plusieurs profils de la page dans « Apparence » ou via l’API, chacun avec son propre identifiant comme black-friday, et indiquez-en un à la création d’un lien : "profile": "black-friday" dans POST /v3/links ou dans un élément de la création en masse. Sans profile, le lien reçoit votre profil par défaut.

Un lien garde son profil : si un autre profil devient ensuite celui par défaut, les anciens liens ne bougent pas. PATCH /v3/links/{id} avec profile déplace un lien, et "profile": null le déplace vers le profil par défaut du moment. Un identifiant inconnu donne une 422 sur le champ profile. L’objet du lien indique son profile, et GET /v3/links?profile=black-friday liste les liens d’un profil. Le nombre de profils possibles dépend de l’offre : 1 sur Free, 5 sur Pro, 50 sur Business, profil par défaut compris.

Comment gérer les profils via l’API ?

GET /v3/profiles les liste, celui par défaut en premier. POST /v3/profiles en crée un avec id et policy_url (obligatoires) et, si vous le souhaitez, name, template, vendors, type, remember, brand_name et default. Il est publié immédiatement. PATCH /v3/profiles/{id} le modifie : les champs de la page publient aussitôt une nouvelle version, name et "default": true seuls ne publient rien.

DELETE /v3/profiles/{id} supprime un profil qui n’est pas celui par défaut (409 profile_is_default) et n’a pas de liens actifs (409 profile_in_use). En créer un au-delà de la limite de l’offre, ou en utiliser un qui dépasse la limite après un passage à une offre inférieure, donne 402 profiles_limit_reached. Les modifications demandent une clé réelle.

Que deviennent les paramètres de mon adresse, comme utm ou gclid ?

Avec consentement, le visiteur arrive sur votre destination exactement telle que vous l’avez donnée. Sans consentement, utm_* et vos autres paramètres restent aussi, et seuls les identifiants de clic publicitaire sont retirés : gclid, gbraid, wbraid, fbclid, msclkid, ttclid, twclid, li_fat_id. Dans les deux cas, nous ajoutons un paramètre, cl_ct, le jeton de décision.

Seuls les paramètres de la destination elle-même comptent : ce qui est ajouté au lien court (https://consent.link/nk7Qa?utm_source=…) n’est pas transmis. Mettez dans destination tout ce dont votre site a besoin, ou créez un lien par campagne.

Où va le visiteur : avec consentement, puis sans
https://shop.example/sale?utm_source=insta&gclid=Cj0K…&cl_ct=eyJhbGciOiJFUzI1NiIs…
https://shop.example/sale?utm_source=insta&cl_ct=eyJhbGciOiJFUzI1NiIs…

Décisions et webhooks

Comment mon site apprend-il ce que le visiteur a décidé ?

Chaque redirection depuis la page de consentement porte cl_ct, un jeton signé (JWT, ES256) valable 10 minutes. Vérifiez-le depuis votre serveur avec POST /v3/consents/verify : la réponse contient la décision, et chaque jeton ne peut être vérifié qu’une fois, si bien qu’une adresse copiée ne vaut rien. Lisez le jeton, puis retirez-le de l’adresse.

Ce qu’il contient : decision (granted ou denied), purposes (ce que le visiteur a autorisé), cid (l’enregistrement du journal), sid (le pseudonyme du visiteur dans votre compte, le même sur tous vos liens), cexp (jusqu’à quand le consentement vaut, en temps Unix), link, mode, iat et exp. Ni IP, ni navigateur, ni autre donnée personnelle. Un visiteur qui a déjà accepté en laissant « Ne plus demander » passe directement, avec granted.

Vous pouvez aussi vérifier la signature vous-même avec les clés de https://api.consent.link/.well-known/jwks.json et n’importe quelle bibliothèque JWT : vérifiez exp, et que link est bien l’un de vos liens. Votre propre vérification ne peut pas savoir si un jeton a déjà servi ; la méthode de l’API, si. Erreurs : 422 token_invalid, 422 token_expired, 409 token_used.

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

Comment recevoir les décisions sur mon serveur en temps réel ?

Créez un webhook : POST /v3/webhooks avec url et les events voulus (le tableau ci-dessous). L’adresse doit être en https sur un nom d’hôte public. La réponse contient secret (whsec_…) pour vérifier les signatures : il n’est affiché qu’une fois, conservez-le. Un compte peut avoir jusqu’à 10 webhooks actifs.

L’historique des livraisons, le bouton « Réessayer » et l’activation ou la désactivation d’un webhook se trouvent dans l’espace client, sous « Compte » → « Webhooks ».

Requête
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"]
  }'
Réponse
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"
}

Quels événements existent, et que contiennent-ils ?

Chaque événement est un POST avec un corps JSON : id (evt_…), type, created_at, mode et data. Un enregistrement de consentement dans data.consent contient id, link, decision, purposes, scope (domain si le visiteur a laissé « Ne plus demander », sinon link), sid, dest_domain, country, notice_version, created_at et expires_at. Jamais d’IP, de navigateur ni de cookie.

ÉvénementQuanddata
consent.recordedUn nouvel enregistrement : un accord, un refus ou un visiteur ayant déjà acceptéconsent
consent.withdrawnUn consentement a été retiré : par le visiteur sur « Mes consentements » ou par vous via l’APIconsent, withdraws
link.blockedNous avons bloqué un lien car sa destination s’est révélée dangereuselink, destination, reason, message
export.readyUn export CSV est terminé (ou a échoué)id, status, filter, rows, bytes, sha256, file, expires_at
usage.warning80 % ou 100 % des consentements de la période sont utiliséspercent, consents, limit, period_start, period_end
consent.expiredUn consentement a expiré : donné il y a 6 mois, sans retrait depuisconsent, expired_at
Ce que reçoit votre serveur
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"
    }
  }
}

Comment vérifier qu’un webhook vient bien de vous ?

Chaque requête porte les en-têtes Consent-Event, Consent-Delivery et Consent-Signature: t=1793712000,v1=5f3a…. v1 est le HMAC-SHA256 en hexadécimal du texte t, d’un point et du corps brut de la requête, avec le secret de votre webhook comme clé.

Calculez-le sur le corps tel que reçu (avant toute analyse du JSON), comparez en temps constant, et refusez un t éloigné de plus de cinq minutes de votre horloge, pour qu’une requête enregistrée ne puisse pas être rejouée.

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));

Et si mon serveur est en panne au moment de l’envoi ?

Une livraison compte quand votre serveur répond 2xx en 10 secondes ; les redirections ne sont pas suivies. Sinon, nous réessayons en attendant 1 minute, 5 minutes, 30 minutes, 2 heures, 12 heures entre les tentatives, 6 tentatives en tout. Après la dernière, la livraison est marquée comme échouée et reste dans l’historique, où « Réessayer » la renvoie jusqu’à 3 jours après l’événement.

Répondez donc vite et faites le travail ensuite. Un même événement peut arriver plus d’une fois, par exemple après un délai dépassé : ignorez les identifiants déjà traités. Les événements n’arrivent pas toujours dans l’ordre où ils se sont produits.

Comment un consentement est-il retiré, et que se passe-t-il ensuite ?

Les visiteurs voient et retirent leurs consentements sur « Mes consentements ». Si un visiteur s’adresse plutôt à vous, retirez-le en son nom : POST /v3/consents/{id}/withdraw, sans corps, avec l’identifiant de son enregistrement granted ou reused. Cela fonctionne pour tout consentement encore en vigueur, quelle que soit la profondeur du journal visible avec votre offre. La réponse est 201 avec le nouvel enregistrement withdrawn, dont le first_consent_id est le consentement retiré. Un enregistrement qui n’est pas un consentement en vigueur (refus, déjà retiré ou expiré) reçoit 409 consent_not_in_force ; un enregistrement que vous ne pouvez pas lire, 404, comme avec GET /v3/consents/{id}.

Les deux voies mènent au même résultat : un enregistrement withdrawn dans le journal, consent.withdrawn envoyé à votre webhook avec data.withdraws, l’identifiant du consentement retiré, et dès lors vos liens redemandent à ce visiteur. Un retrait clôt tous les consentements que le visiteur avait donnés pour ce domaine, et il ne compte pas dans votre limite.

Cessez d’utiliser les données du visiteur de votre côté : retrouvez-les par sid ou par l’identifiant de l’enregistrement.

Requête
curl -X POST https://api.consent.link/v3/consents/01jbz8m3k4x9w2r7d5t6q8v1yh/withdraw \
  -H "Authorization: Bearer clk_live_…"
Réponse
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": {…}
}

Combien de temps dure un consentement ?

Un consentement vaut 6 mois à partir du moment où il a été donné : expires_at dans l’enregistrement, cexp dans le jeton de décision. Un enregistrement reused porte l’expires_at du consentement qu’il réutilise. Ensuite, vos liens redemandent au visiteur.

Quand un consentement granted expire sans avoir été retiré, votre webhook reçoit consent.expired avec data.consent, l’enregistrement, et data.expired_at. Nous vérifions une fois par heure : l’événement arrive dans l’heure qui suit expires_at, une fois par consentement. Les enregistrements reused n’ont pas d’événement propre : celui du consentement qu’ils réutilisent les couvre. L’événement ne part que vers les webhooks qui ont consent.expired dans events. Une expiration n’est annoncée qu’une fois, même si aucun webhook ne l’attendait alors : elle n’est pas renvoyée plus tard.

Ce que reçoit votre serveur
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"
  }
}

Site et bannières

Comment mon site applique-t-il lui-même la décision du visiteur (Google Consent Mode v2) ?

Placez notre script dans <head>, avant GTM et gtag : https://consent.link/js/consent.js. Il fixe les valeurs par défaut de Consent Mode v2 (tout refusé sauf functionality_storage et security_storage, avec wait_for_update de 500 ms), lit cl_ct dans l'adresse, vérifie sa signature avec les clés de https://api.consent.link/.well-known/jwks.json directement dans le navigateur et met à jour le consentement.

La décision est gardée dans le cookie cl_consent de votre domaine : jusqu'à cexp pour un consentement, 180 jours pour un refus, donc les pages suivantes n'ont pas besoin de bannière. Une fois vérifié, cl_ct quitte la barre d'adresse ; vos autres paramètres restent. Le script ne nous envoie rien.

Si vous enregistrez la décision de votre côté, vérifiez aussi le jeton depuis votre serveur avec POST /v3/consents/verify : le script ne peut pas savoir si un jeton a déjà servi.

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

Comment l'installer dans Google Tag Manager ?

Téléchargez le modèle consent-link.tpl ; dans GTM, ouvrez Modèles → Nouveau → ⋮ → Importer et enregistrez. Créez une balise à partir de ce modèle avec le déclencheur « Consent Initialization – All Pages » (« Initialisation du consentement – Toutes les pages »).

La balise fixe les valeurs par défaut, applique la décision gardée dans le cookie cl_consent et charge le script, qui vérifie un cl_ct récent. Inutile alors de mettre aussi le script dans <head>.

Quels signaux Consent Mode chaque finalité accorde-t-elle ?

Les purposes du jeton accordent ces signaux. Ce qui n'est pas accordé reste refusé ; un refus (denied) refuse tout sauf les deux toujours accordés.

FinalitéSignaux Consent Mode
stats analytics_storage
ads ad_storage, ad_user_data, ad_personalization
app_install ad_storage, ad_user_data
personalization personalization_storage
Toujours functionality_storage, security_storage

J'utilise Cookiebot, OneTrust ou une autre bannière. Qu'en faire ?

N'affichez pas une deuxième bannière à qui a déjà décidé sur notre page. Le script donne la décision dans window.consentLink.decision (granted, denied ou null) et window.consentLink.has('stats') ; window.consentLink.ready se résout une fois le jeton de l'adresse vérifié. Il y a aussi l'événement consentlink:update sur document et consent_link_update dans dataLayer. Chargez notre script avant celui de la bannière.

Cookiebot : transmettez la décision avec Cookiebot.submitCustomConsent et sa bannière ne s'affiche pas. OneTrust n'a pas de fonction publique pour enregistrer un consentement depuis le site : chargez donc son script seulement s'il n'y a pas de décision. Pour les autres bannières, pareil : transmettez les finalités par leur API ou ne chargez pas la bannière.

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);
});

Journal, preuves et statistiques

Comment lire le journal ?

GET /v3/consents liste les enregistrements, les plus récents d’abord, avec les filtres decision (granted, denied, reused, withdrawn), link, country (DE), from, to et la même pagination que pour les liens. GET /v3/consents/{id} renvoie un enregistrement avec ses preuves (adresse IP, navigateur, langue, l’empreinte du texte de la page, quand la page a été affichée et combien de temps le visiteur a mis) et sa place dans la chaîne d’empreintes, chain.

Un refus ne garde que la décision, l’heure, le lien et le pays : son evidence vaut donc null. L’API donne les données complètes ; l’espace client en masque une partie tant que vous n’avez pas activé la connexion en deux étapes. La profondeur visible dépend de l’offre (le tableau de « Offres et limites »).

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

Comment obtenir une preuve PDF d’un consentement ?

GET /v3/consents/{id}/proof renvoie l’enregistrement en PDF : la décision, qui, ce qui a été affiché, les empreintes et comment les vérifier. Il est dans la langue de votre espace client. Disponible sur Pro et Business (402 plan_required sur Free), jusqu’à 20 par minute. Si le PDF ne peut pas être produit tout de suite, la réponse est 503 unavailable avec Retry-After.

Requête
curl -o proof.pdf https://api.consent.link/v3/consents/01jbz8m3k4x9w2r7d5t6q8v1yh/proof \
  -H "Authorization: Bearer clk_live_…"

Comment voir et vérifier la page sur laquelle le visiteur a consenti ?

Chaque enregistrement granted porte evidence.snapshot_hash : le SHA-256 de la page de consentement telle que le visiteur l’a vue, en HTML. Un même instantané sert à tous les visiteurs de la même page, c’est-à-dire même version de l’apparence et du texte, même langue et même type de lien. GET /v3/consents/{id}/snapshot renvoie ce HTML avec le hash dans l’en-tête X-Snapshot-Hash, et GET /v3/consents/{id}/snapshot.png une capture d’écran. Les deux suivent les règles de GET /v3/consents/{id}, profondeur du journal comprise.

Pour vérifier, calculez le SHA-256 du HTML exactement tel que reçu : il est égal à snapshot_hash, et le hash propre de l’enregistrement dans la chaîne couvre snapshot_hash, si bien que la page ne peut pas être remplacée après coup. Ce qui change d’un affichage à l’autre est figé dans l’instantané : l’hôte de la destination s’affiche example.com, la signature du formulaire est vide et son action est #. L’adresse réelle se trouve dans dest_domain et evidence.destination.

Les enregistrements reused, les refus et les enregistrements antérieurs aux instantanés ont snapshot_hash à null, et leur instantané répond 404 not_found (« This record has no page snapshot. »). Juste après une décision, les fichiers peuvent être encore en cours d’écriture : 404 not_found avec « The page snapshot is not ready yet. » veut dire de réessayer un peu plus tard. Le HTML arrive en quelques secondes, la capture d’écran un peu après. La page de l’enregistrement dans l’espace client et la preuve PDF montrent aussi la capture et le hash.

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

sha256sum page.html

Comment exporter le journal en CSV ?

POST /v3/exports avec les filtres de GET /v3/consents (decision, link, country, from, to) répond aussitôt 202 avec l’id de l’export. Le fichier est produit en arrière-plan : vous recevez le webhook export.ready, ou interrogez GET /v3/exports/{id} jusqu’à ce que status soit ready, puis téléchargez GET /v3/exports/{id}/file. L’export indique aussi rows et le sha256 du fichier.

Les fichiers sont conservés 7 jours (410 export_expired ensuite). Jusqu’à 3 exports peuvent être en cours à la fois (429 too_many_exports). Pro et Business.

Requête
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"
  }'
Réponse
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
}

Puis-je obtenir des statistiques et ma consommation via l’API ?

GET /v3/stats/summary donne shown, granted, denied, left, reused et withdrawn pour les jours UTC de from à to (2026-10-31, bornes incluses ; par défaut les 30 derniers jours, au plus 366 jours, aussi loin que votre offre montre le journal). Ajoutez link pour un seul lien. GET /v3/stats/daily donne les mêmes chiffres jour par jour et, sur Pro et Business, par pays avec by=country. Les robots ne sont jamais comptés.

GET /v3/usage montre les consentements de la période, votre limit, ce qui reste (remaining) et le début et la fin de la période.

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

Offres et limites

Que comprend chaque offre ?

L’API, les webhooks et la page de consentement en sept langues sont inclus dans chaque offre. Les offres diffèrent par les consentements par mois, la profondeur du journal affiché, le rythme de requêtes et les fonctions supplémentaires. Prix en euros, hors TVA.

OffrePrix par moisConsentements par moisJournalRequêtes par minuteProfils de pageEn plus
Free0 €1 000Journal sur 30 jours601—
Pro9 €10 000Journal sur 1 an3005Export CSV et preuve PDF · Statistiques par jour et par pays
Business99 €250 000Journal sur 3 ans1 00050Export CSV et preuve PDF · Statistiques par jour et par pays · Votre logo et votre couleur · Des liens sur votre propre domaine

Que se passe-t-il quand mes consentements sont épuisés ?

Seuls les nouveaux accords comptent : les refus, les visites répétées avec « Ne plus demander », et les robots ne comptent pas. À 80 % et à 100 % de la limite, vous recevez un e-mail et le webhook usage.warning.

Une fois la limite atteinte, les nouveaux liens réels sont refusés avec 402 limit_reached. Tout le reste continue de fonctionner : les liens existants demandent et enregistrent toujours, les webhooks et les jetons arrivent comme d’habitude, et rien n’est masqué. La création de liens rouvre au début de la période suivante (sur Free le 1er du mois, sur les offres payantes le jour du paiement) ou juste après un passage à une offre supérieure.

Combien de requêtes puis-je envoyer ?

Par compte, toutes clés confondues : 60 par minute sur Free, 300 sur Pro, 1 000 sur Business. Au-delà, la réponse est 429 rate_limited avec un en-tête Retry-After ; les en-têtes X-RateLimit-Limit et X-RateLimit-Remaining indiquent où vous en êtes. Certaines méthodes ont en plus leur propre limite : preuves PDF 20 par minute, nouveaux exports 10, téléchargements d’exports 30.

Erreurs et sécurité

À quoi ressemblent les erreurs ?

Toujours la même forme : le statut HTTP et un corps JSON avec un code stable et un message en anglais. Fiez-vous au code ; le message peut être reformulé. Des champs invalides donnent 422 validation_failed avec fields : chaque champ avec ses problèmes.

StatutCodeSignification
401unauthenticatedPas de clé, ou clé erronée ou révoquée
402limit_reachedLes consentements de la période sont épuisés : pas de nouveaux liens réels
402plan_requiredLa fonction demande une offre supérieure
402profiles_limit_reachedAu-delà du nombre de profils de page de l’offre
403account_blockedLe compte est bloqué : contactez le support
403links_blockedLe compte ne peut pas créer de liens : contactez le support
403mode_mismatchUn lien réel se modifie avec une clé réelle, un lien de test avec une clé de test
403live_key_requiredUne clé réelle est nécessaire
404not_foundRien avec cet identifiant dans votre compte
405method_not_allowedCette méthode HTTP n’est pas autorisée sur ce chemin
409link_disabledLe lien est désactivé et ne peut pas être modifié
409link_blockedLe lien a été bloqué et ne peut pas être modifié
409idempotency_in_progressUne requête avec cette Idempotency-Key est encore en cours
409token_usedCe jeton de décision a déjà été vérifié
409export_not_readyL’export est encore en cours de production
409export_failedL’export a échoué : lancez-en un nouveau
409consent_not_in_forceL’enregistrement n’est pas un consentement en vigueur : refus, retrait ou expiration
409profile_is_defaultLe profil par défaut ne peut pas être supprimé
409profile_in_useLe profil a encore des liens actifs
410export_expiredLe fichier n’est plus conservé : lancez un nouvel export
422validation_failedCertains champs sont invalides : voir fields
422destination_blockedLa destination est dangereuse ou bloquée
422idempotency_key_reusedL’Idempotency-Key a été utilisée avec un autre corps
422token_invalidPas un jeton de décision valide pour votre compte
422token_expiredLe jeton de décision a plus de 10 minutes
422webhooks_limit_reachedTrop de webhooks actifs
429rate_limitedTrop de requêtes : attendez la durée de Retry-After
429too_many_exportsTrop d’exports en cours
429daily_limit_reachedNouveau compte Free : les liens du jour sont épuisés, d’autres dès 00:00 UTC
500server_errorUn problème de notre côté : réessayez plus tard
503unavailableRéessayez dans un instant : voir Retry-After
Réponse
422 Unprocessable Content
{
  "error": {
    "code": "validation_failed",
    "message": "Some fields are invalid.",
    "fields": {
      "destination": ["The destination must be an absolute http or https URL."]
    }
  }
}

Que faire si une clé fuite ?

Créez une nouvelle clé sur « Connexion via l’API », passez votre serveur dessus, puis révoquez l’ancienne : elle cesse de fonctionner immédiatement. Révoquer une clé ne touche ni à vos liens, ni au journal, ni aux webhooks.

Gardez les clés dans les variables d’environnement de votre serveur, jamais dans un navigateur, une application mobile ou un dépôt public. Si le secret d’un webhook a pu fuiter, désactivez ce webhook dans l’espace client et créez-en un nouveau.

Prêt à vous connecter ?

Inscrivez-vous, créez une clé sur « Connexion via l’API » et envoyez votre première requête en quelques minutes.

Commencer gratuitement