API v3

Perguntas sobre a API

O que os programadores perguntam antes e durante a ligação: chaves, links, como a decisão chega ao seu site, webhooks, o histórico, limites e erros. Cada método e cada número desta página são os que a API usa hoje.

Endereço
https://api.consent.link/v3
Chave
Authorization: Bearer clk_live_…
Formato
JSON, UTF-8, respostas em inglês

Primeiros passos

Como obtenho uma chave de API?

Registe-se e confirme o seu email. A primeira página depois de iniciar sessão, «Ligar pela API», cria a sua chave clk_live_….

A chave é mostrada uma única vez, logo após ser criada, por isso copie-a nesse momento: guardamos apenas o seu hash. Uma conta pode ter até 10 chaves ativas. Uma chave de que já não precisa é revogada na mesma página e deixa de funcionar de imediato.

Onde está a API e como envio a chave?

A API está em https://api.consent.link/v3 e fala JSON. Envie a chave em cada pedido no cabeçalho Authorization: Bearer clk_live_… (o cabeçalho X-AUTH-TOKEN da nossa antiga API v2 também funciona). Um pedido sem uma chave válida recebe 401 unauthenticated.

Chame a API apenas a partir do seu servidor. Quem tiver a chave pode criar links e ler o seu histórico com todos os dados dos visitantes.

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

Que métodos existem?

Todos de relance. Cada caminho vem depois de https://api.consent.link; as chaves públicas para verificar os tokens de decisão estão em https://api.consent.link/.well-known/jwks.json.

MétodoO que faz
POST/v3/linksCriar um link
POST/v3/links/bulkCriar até 500 links
GET/v3/linksListar links, com filtros
GET/v3/links/{id}Um link e os seus números
PATCH/v3/links/{id}Alterar um link
DELETE/v3/links/{id}Desativar um link
GET/v3/profilesListar perfis de página
POST/v3/profilesCriar um perfil de página
GET/v3/profiles/{id}Um perfil de página
PATCH/v3/profiles/{id}Alterar ou publicar um perfil
DELETE/v3/profiles/{id}Eliminar um perfil
POST/v3/consents/verifyVerificar uma vez um token de decisão
GET/v3/webhooksLista de webhooks
POST/v3/webhooksAdicionar um webhook
GET/v3/webhooks/{id}Um webhook
PATCH/v3/webhooks/{id}Alterar o URL ou os eventos, ativar e desativar
DELETE/v3/webhooks/{id}Eliminar um webhook
GET/v3/consentsO histórico, com filtros
GET/v3/consents/{id}Um registo com as suas provas
POST/v3/consents/{id}/withdrawRetirar um consentimento em nome do visitante
GET/v3/consents/{id}/proofProva em PDF de um registoPro e Business
GET/v3/consents/{id}/snapshotA página que o visitante viu, em HTML
GET/v3/consents/{id}/snapshot.pngA página que o visitante viu, como captura de ecrã
POST/v3/exportsIniciar uma exportação CSVPro e Business
GET/v3/exports/{id}Uma exportação e o seu estadoPro e Business
GET/v3/exports/{id}/fileDescarregar o CSVPro e Business
GET/v3/stats/summaryTotais de um intervalo de dias
GET/v3/stats/dailyNúmeros por dia (e por país)
GET/v3/usageConsentimentos do período e o limite

A página de consentimento

O que verão os visitantes, e em que idioma?

A página que configura em «Aparência» na área de cliente: o modelo de texto da nossa biblioteca, os destinatários dos dados, a indicação, o link para a sua política de privacidade e, no Business, o seu logótipo e a sua cor. Os type e template do próprio link têm prioridade sobre a aparência. Quando publica uma versão nova, as páginas dos seus links mudam em menos de um minuto.

O idioma é escolhido a partir do navegador do visitante: inglês, espanhol, alemão, francês, italiano, português ou russo. Escolhe um modelo, não palavras: os textos vêm da nossa biblioteca, por isso não pode escrever os seus.

Enquanto nada for publicado em «Aparência», a página não mostra o link para a sua política de privacidade nem os destinatários dos dados. Os links são criados mesmo assim, e as respostas a POST /v3/links, POST /v3/links/bulk e PATCH /v3/links/{id} trazem o cabeçalho Consent-Link-Warning: design_unpublished enquanto a configuração do link não tiver uma versão publicada.

Como dou definições de página diferentes a links diferentes?

Guarde vários perfis da página em «Aparência» ou pela API, cada um com o seu próprio id, como black-friday, e indique um ao criar um link: "profile": "black-friday" em POST /v3/links ou num elemento da criação em massa. Sem profile, o link recebe o seu perfil predefinido.

Um link mantém o seu perfil: se mais tarde outro perfil passar a ser o predefinido, os links antigos não mudam. PATCH /v3/links/{id} com profile muda um link de perfil, e "profile": null passa-o para o predefinido desse momento. Um id desconhecido dá 422 no campo profile. O objeto do link mostra o seu profile, e GET /v3/links?profile=black-friday lista os links de um perfil. Quantos perfis pode ter depende do plano: 1 no Free, 5 no Pro, 50 no Business, incluindo o predefinido.

Como giro os perfis pela API?

GET /v3/profiles lista-os, o predefinido primeiro. POST /v3/profiles cria um com id e policy_url (obrigatórios) e, se quiser, name, template, vendors, type, remember, brand_name e default. É publicado de imediato. PATCH /v3/profiles/{id} altera-o: os campos da página publicam logo uma versão nova; name e "default": true sozinhos não publicam nada.

DELETE /v3/profiles/{id} elimina um perfil que não seja o predefinido (409 profile_is_default) e não tenha links ativos (409 profile_in_use). Criar um acima do limite do plano, ou usar um que ficou acima do limite depois de baixar de plano, dá 402 profiles_limit_reached. As alterações precisam de uma chave real.

O que acontece aos parâmetros do meu endereço, como utm ou gclid?

Com consentimento, o visitante vai para o seu destino exatamente como o indicou. Sem consentimento, utm_* e os seus outros parâmetros também ficam, e só são retirados os identificadores de cliques em anúncios: gclid, gbraid, wbraid, fbclid, msclkid, ttclid, twclid, li_fat_id. Em ambos os casos acrescentamos um parâmetro, cl_ct, o token de decisão.

Só contam os parâmetros do próprio destino: o que for acrescentado ao link curto (https://consent.link/nk7Qa?utm_source=…) não é passado. Coloque em destination tudo o que o seu site precisa, ou crie um link por campanha.

Para onde vai o visitante: com consentimento e sem ele
https://shop.example/sale?utm_source=insta&gclid=Cj0K…&cl_ct=eyJhbGciOiJFUzI1NiIs…
https://shop.example/sale?utm_source=insta&cl_ct=eyJhbGciOiJFUzI1NiIs…

Decisões e webhooks

Como fica o meu site a saber o que o visitante decidiu?

Cada redirecionamento a partir da página de consentimento leva cl_ct, um token assinado (JWT, ES256) que vale 10 minutos. Verifique-o a partir do seu servidor com POST /v3/consents/verify: a resposta traz a decisão, e cada token só pode ser verificado uma vez, por isso um endereço copiado não serve de nada. Leia o token e depois retire-o do endereço.

O que contém: decision (granted ou denied), purposes (o que o visitante permitiu), cid (o registo no histórico), sid (o pseudónimo do visitante na sua conta, o mesmo em todos os seus links), cexp (até quando vale o consentimento, em tempo Unix), link, mode, iat e exp. Nada de IP, navegador ou outros dados pessoais. Um visitante que já aceitou e deixou marcado «Não perguntar novamente» passa diretamente, com granted.

Também pode verificar a assinatura por si, com as chaves em https://api.consent.link/.well-known/jwks.json e qualquer biblioteca JWT: verifique exp e que link é um dos seus links. A sua própria verificação não sabe se um token já foi usado; o método da API sabe. Erros: 422 token_invalid, 422 token_expired, 409 token_used.

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

Como recebo as decisões no meu servidor no momento em que acontecem?

Crie um webhook: POST /v3/webhooks com url e os events que quiser (a tabela abaixo). O endereço tem de ser https num nome de host público. A resposta traz secret (whsec_…) para verificar as assinaturas: é mostrado uma única vez, por isso guarde-o. Uma conta pode ter até 10 webhooks ativos.

O registo de entregas, o botão «Tentar novamente» e a ativação ou desativação de um webhook estão na área de cliente, em «Conta» → «Webhooks».

Pedido
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"]
  }'
Resposta
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"
}

Que eventos existem, e o que trazem?

Cada evento é um POST com um corpo JSON: id (evt_…), type, created_at, mode e data. Um registo de consentimento em data.consent tem id, link, decision, purposes, scope (domain se o visitante deixou marcado «Não perguntar novamente», caso contrário link), sid, dest_domain, country, notice_version, created_at e expires_at. Nunca um IP, um navegador ou um cookie.

EventoQuandodata
consent.recordedUm registo novo: uma aceitação, uma recusa ou um visitante que já tinha aceitadoconsent
consent.withdrawnUm consentimento foi retirado: pelo visitante em «Meus consentimentos» ou por si através da APIconsent, withdraws
link.blockedBloqueámos um link porque o seu destino se revelou perigosolink, destination, reason, message
export.readyUma exportação CSV terminou (ou falhou)id, status, filter, rows, bytes, sha256, file, expires_at
usage.warningForam usados 80 % ou 100 % dos consentimentos do períodopercent, consents, limit, period_start, period_end
consent.expiredUm consentimento expirou: foi dado há 6 meses e não foi retirado desde entãoconsent, expired_at
O que o seu servidor recebe
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"
    }
  }
}

Como verifico que um webhook vem de vocês?

Cada pedido leva os cabeçalhos Consent-Event, Consent-Delivery e Consent-Signature: t=1793712000,v1=5f3a…. v1 é o HMAC-SHA256 em hexadecimal do texto t, um ponto e o corpo bruto do pedido, com o segredo do seu webhook como chave.

Calcule-o sobre o corpo tal como chegou (antes de interpretar o JSON), compare em tempo constante e recuse um t que se afaste mais de cinco minutos do seu relógio, para que um pedido gravado não possa ser repetido.

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

E se o meu servidor estiver em baixo quando um evento é enviado?

Uma entrega conta quando o seu servidor responde 2xx em 10 segundos; os redirecionamentos não são seguidos. Caso contrário, tentamos de novo, esperando 1 minuto, 5 minutos, 30 minutos, 2 horas, 12 horas entre as tentativas, 6 tentativas ao todo. Depois da última, a entrega é marcada como falhada e fica no registo, onde «Tentar novamente» a envia de novo até 3 dias depois do evento.

Por isso responda depressa e faça o trabalho depois. O mesmo evento pode chegar mais de uma vez, por exemplo depois de um tempo de espera esgotado: ignore os id que já processou. Os eventos nem sempre chegam pela ordem em que aconteceram.

Como se retira um consentimento, e o que acontece depois?

Os visitantes veem e retiram os seus consentimentos em «Meus consentimentos». Se um visitante lhe pedir a si, retire-o em nome dele: POST /v3/consents/{id}/withdraw, sem corpo, com o id do registo granted ou reused dele. Funciona para qualquer consentimento ainda em vigor, seja qual for a profundidade do histórico que o seu plano mostra. A resposta é 201 com o novo registo withdrawn, cujo first_consent_id é o consentimento retirado. Um registo que não é um consentimento em vigor (recusado, já retirado ou expirado) recebe 409 consent_not_in_force; um que não pode ler recebe 404, como em GET /v3/consents/{id}.

Os dois caminhos dão o mesmo resultado: um registo withdrawn no histórico, consent.withdrawn para o seu webhook com data.withdraws, o id do consentimento retirado, e a partir daí os seus links voltam a perguntar a esse visitante. Uma retirada fecha todos os consentimentos que o visitante deu antes para esse domínio, e não conta para o seu limite.

Deixe de usar os dados do visitante do seu lado: encontre-os por sid ou pelo id do registo.

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

Quanto tempo dura um consentimento?

Um consentimento vale 6 meses a partir do momento em que foi dado: expires_at no registo, cexp no token de decisão. Um registo reused traz o expires_at do consentimento que reutiliza. Depois disso, os seus links voltam a perguntar ao visitante.

Quando um consentimento granted expira sem ter sido retirado, o seu webhook recebe consent.expired com data.consent, o registo, e data.expired_at. Verificamos uma vez por hora, por isso chega na hora seguinte a expires_at, uma vez por consentimento. Os registos reused não têm evento próprio: cobre-os o do consentimento que reutilizam. O evento vai só para webhooks com consent.expired em events. Cada expiração é anunciada uma só vez, mesmo que nenhum webhook a esperasse nessa altura: não volta a ser enviada mais tarde.

O que o seu servidor recebe
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 e banners

Como é que o meu site aplica sozinho a decisão do visitante (Google Consent Mode v2)?

Coloque o nosso script no <head>, antes do GTM e do gtag: https://consent.link/js/consent.js. Define os valores predefinidos do Consent Mode v2 (tudo recusado exceto functionality_storage e security_storage, com wait_for_update de 500 ms), lê cl_ct do endereço, verifica a assinatura com as chaves de https://api.consent.link/.well-known/jwks.json no próprio navegador e atualiza o consentimento.

A decisão fica guardada no cookie cl_consent do seu domínio: até cexp para um consentimento e 180 dias para uma recusa, por isso as páginas seguintes não precisam de banner. Depois da verificação, cl_ct sai da barra de endereço; os outros parâmetros ficam. O script não nos envia nada.

Se regista a decisão do seu lado, verifique também o token a partir do seu servidor com POST /v3/consents/verify: o script não sabe se o token já foi usado.

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

Como configuro no Google Tag Manager?

Descarregue o modelo consent-link.tpl; no GTM abra Modelos → Novo → ⋮ → Importar e guarde. Crie uma etiqueta a partir dele com o acionador «Consent Initialization – All Pages» («Inicialização do consentimento – Todas as páginas»).

A etiqueta define os valores predefinidos, aplica a decisão guardada no cookie cl_consent e carrega o script, que verifica um cl_ct novo. Assim não precisa de pôr também o script no <head>.

Que sinais do Consent Mode concede cada finalidade?

As purposes do token concedem estes sinais. O que não é concedido fica recusado; uma recusa (denied) recusa tudo exceto os dois sempre concedidos.

FinalidadeSinais do Consent Mode
stats analytics_storage
ads ad_storage, ad_user_data, ad_personalization
app_install ad_storage, ad_user_data
personalization personalization_storage
Sempre functionality_storage, security_storage

Uso Cookiebot, OneTrust ou outro banner. O que faço com ele?

Não mostre um segundo banner a quem já decidiu na nossa página. O script dá a decisão em window.consentLink.decision (granted, denied ou null) e window.consentLink.has('stats'); window.consentLink.ready resolve-se quando o token do endereço foi verificado. Há também o evento consentlink:update em document e consent_link_update no dataLayer. Carregue o nosso script antes do script do banner.

Cookiebot: passe a decisão com Cookiebot.submitCustomConsent e o banner dele não aparece. O OneTrust não tem uma função pública para registar o consentimento a partir do site, por isso carregue o script dele só quando não houver decisão. Com outros banners, o mesmo: passe as finalidades pela API deles ou não carregue o 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);
});

Histórico, provas e estatísticas

Como leio o histórico?

GET /v3/consents lista os registos, os mais recentes primeiro, com os filtros decision (granted, denied, reused, withdrawn), link, country (DE), from, to e a mesma paginação dos links. GET /v3/consents/{id} devolve um registo com as suas provas (endereço IP, navegador, idioma, o hash do texto da página, quando a página foi mostrada e quanto tempo o visitante demorou) e o seu lugar na cadeia de hashes, chain.

Uma recusa guarda apenas a decisão, a hora, o link e o país, por isso o seu evidence é null. A API dá os dados completos; a área de cliente esconde uma parte até ativar o início de sessão em dois passos. Até onde pode ver para trás depende do plano (a tabela em «Planos e limites»).

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

Como obtenho uma prova em PDF de um consentimento?

GET /v3/consents/{id}/proof devolve o registo em PDF: a decisão, quem, o que foi mostrado, os hashes e como verificá-los. Está no idioma da sua área de cliente. Disponível no Pro e no Business (402 plan_required no Free), até 20 por minuto. Se o PDF não puder ser gerado neste momento, a resposta é 503 unavailable com Retry-After.

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

Como vejo e verifico a página em que o visitante deu consentimento?

Cada registo granted traz evidence.snapshot_hash: o SHA-256 da página de consentimento tal como o visitante a viu, em HTML. Um mesmo instantâneo serve todos os visitantes da mesma página, ou seja, a mesma versão de aspeto e de texto, idioma e tipo de link. GET /v3/consents/{id}/snapshot devolve esse HTML com o hash no cabeçalho X-Snapshot-Hash, e GET /v3/consents/{id}/snapshot.png uma captura de ecrã. Para ambos valem as regras de GET /v3/consents/{id}, incluindo até onde o seu plano mostra o histórico.

Para a verificar, calcule o SHA-256 do HTML exatamente como o recebeu: é igual a snapshot_hash, e o hash do próprio registo na cadeia cobre snapshot_hash, por isso a página não pode ser trocada depois. O que muda de uma visita para outra está fixo no instantâneo: o host do destino aparece como example.com, a assinatura do formulário está vazia e a sua action é #. O endereço real está em dest_domain e evidence.destination.

Os registos reused, as recusas e os registos anteriores aos instantâneos têm snapshot_hash null, e o seu instantâneo responde 404 not_found («This record has no page snapshot.»). Logo a seguir a uma decisão os ficheiros podem ainda estar a ser escritos: 404 not_found com «The page snapshot is not ready yet.» quer dizer que tente de novo daqui a pouco. O HTML chega em segundos, a captura de ecrã um pouco depois. A página do registo na área de cliente e a prova em PDF também mostram a captura e o hash.

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

sha256sum page.html

Como exporto o histórico em CSV?

POST /v3/exports com os filtros de GET /v3/consents (decision, link, country, from, to) responde logo 202 com o id da exportação. O ficheiro é gerado em segundo plano: recebe o webhook export.ready, ou consulta GET /v3/exports/{id} até status ser ready, e depois descarrega GET /v3/exports/{id}/file. A exportação traz também rows e o sha256 do ficheiro.

Os ficheiros são guardados 7 dias (depois disso, 410 export_expired). Podem estar em curso até 3 exportações ao mesmo tempo (429 too_many_exports). Pro e Business.

Pedido
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"
  }'
Resposta
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
}

Posso obter estatísticas e o meu consumo pela API?

GET /v3/stats/summary dá shown, granted, denied, left, reused e withdrawn para os dias UTC de from a to (2026-10-31, ambos incluídos; por omissão os últimos 30 dias, no máximo 366 dias, até onde o seu plano mostra o histórico). Acrescente link para um só link. GET /v3/stats/daily dá os mesmos números dia a dia e, no Pro e no Business, por país com by=country. Os bots nunca contam.

GET /v3/usage mostra os consentimentos deste período, o seu limit, o que resta (remaining) e o início e o fim do período.

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

Planos e limites

O que inclui cada plano?

A API, os webhooks e a página de consentimento em sete idiomas estão em todos os planos. Os planos diferem nos consentimentos por mês, em até onde o histórico é mostrado para trás, no ritmo de pedidos e nas funções extra. Preços em euros, sem IVA.

PlanoPreço por mêsConsentimentos por mêsHistóricoPedidos por minutoPerfis de páginaAlém disso
Free€ 01.000Histórico de 30 dias601—
Pro€ 910.000Histórico de 1 ano3005Exportação CSV e prova em PDF · Estatísticas por dia e país
Business€ 99250.000Histórico de 3 anos1.00050Exportação CSV e prova em PDF · Estatísticas por dia e país · O seu logótipo e a sua cor · Links no seu próprio domínio

O que acontece quando os consentimentos se esgotam?

Só contam as novas aceitações: as recusas, as visitas repetidas com «Não perguntar novamente», e os bots não contam. Aos 80 % e aos 100 % do limite recebe um email e o webhook usage.warning.

Depois de esgotado o limite, os novos links reais são recusados com 402 limit_reached. Tudo o resto continua a funcionar: os links existentes continuam a perguntar e a registar, os webhooks e os tokens chegam como sempre e nada fica escondido. A criação de links reabre no início do período seguinte (no Free no dia 1 do mês, nos planos pagos no dia do pagamento) ou logo após subir de plano.

Quantos pedidos posso enviar?

Por conta, todas as chaves em conjunto: 60 por minuto no Free, 300 no Pro, 1.000 no Business. Acima disso, a resposta é 429 rate_limited com um cabeçalho Retry-After; os cabeçalhos X-RateLimit-Limit e X-RateLimit-Remaining mostram em que ponto está. Alguns métodos têm ainda um limite próprio: provas em PDF 20 por minuto, novas exportações 10, transferências de exportações 30.

Erros e segurança

Como são os erros?

Sempre com a mesma forma: o estado HTTP e um corpo JSON com um code estável e uma message em inglês. Atue com base no code; a mensagem pode ser reformulada. Campos inválidos dão 422 validation_failed com fields: cada campo com os seus problemas.

EstadoCódigoSignificado
401unauthenticatedSem chave, ou a chave está errada ou revogada
402limit_reachedOs consentimentos do período esgotaram-se: sem novos links reais
402plan_requiredA função precisa de um plano superior
402profiles_limit_reachedAcima do número de perfis de página do plano
403account_blockedA conta está bloqueada: contacte o suporte
403links_blockedA conta não pode criar links: contacte o suporte
403mode_mismatchUm link real altera-se com uma chave real, um link de teste com uma chave de teste
403live_key_requiredÉ preciso uma chave real
404not_foundNada com este id na sua conta
405method_not_allowedEste método HTTP não é permitido neste caminho
409link_disabledO link está desativado e não pode ser alterado
409link_blockedO link foi bloqueado e não pode ser alterado
409idempotency_in_progressUm pedido com esta Idempotency-Key ainda está em curso
409token_usedEste token de decisão já foi verificado
409export_not_readyA exportação ainda está a ser gerada
409export_failedA exportação falhou: inicie uma nova
409consent_not_in_forceO registo não é um consentimento em vigor: recusado, retirado ou expirado
409profile_is_defaultO perfil predefinido não pode ser eliminado
409profile_in_useO perfil ainda tem links ativos
410export_expiredO ficheiro já não está guardado: inicie uma nova exportação
422validation_failedAlguns campos são inválidos: veja fields
422destination_blockedO destino é perigoso ou está bloqueado
422idempotency_key_reusedA Idempotency-Key foi usada com outro corpo
422token_invalidNão é um token de decisão válido para a sua conta
422token_expiredO token de decisão tem mais de 10 minutos
422webhooks_limit_reachedDemasiados webhooks ativos
429rate_limitedDemasiados pedidos: espere o que indica Retry-After
429too_many_exportsDemasiadas exportações em curso
429daily_limit_reachedConta Free nova: os links do dia acabaram, mais a partir das 00:00 UTC
500server_errorAlgo falhou do nosso lado: tente de novo mais tarde
503unavailableTente de novo dentro de pouco tempo: veja Retry-After
Resposta
422 Unprocessable Content
{
  "error": {
    "code": "validation_failed",
    "message": "Some fields are invalid.",
    "fields": {
      "destination": ["The destination must be an absolute http or https URL."]
    }
  }
}

O que faço se uma chave for divulgada?

Crie uma chave nova em «Ligar pela API», passe o seu servidor para ela e depois revogue a antiga: deixa de funcionar de imediato. Revogar uma chave não afeta os seus links, o histórico nem os webhooks.

Guarde as chaves nas variáveis de ambiente do seu servidor, nunca num navegador, numa app móvel ou num repositório público. Se o segredo de um webhook puder ter sido divulgado, desative esse webhook na área de cliente e crie um novo.

Pronto para ligar?

Registe-se, crie uma chave em «Ligar pela API» e envie o seu primeiro pedido em poucos minutos.

Comece grátis