API v3

Questions about the API

What developers ask before and while connecting: keys, links, how the decision reaches your site, webhooks, the journal, limits and errors. Every method and number here is the one the API uses today.

Address
https://api.consent.link/v3
Key
Authorization: Bearer clk_live_…
Format
JSON, UTF-8, answers in English

Getting started

How do I get an API key?

Register and confirm your email. The first page after sign-in, «Connect via API», creates your key, clk_live_….

The key is shown once, right after you create it, so copy it then: we keep only its hash. An account can hold up to 10 active keys. A key you no longer need is revoked on the same page and stops working at once.

Where is the API and how do I send the key?

The API lives at https://api.consent.link/v3 and speaks JSON. Send the key with every request in the header Authorization: Bearer clk_live_… (the X-AUTH-TOKEN header of our old API v2 works too). A request without a valid key gets 401 unauthenticated.

Call the API from your server only. Whoever holds the key can create links and read your journal with full visitor data.

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

Which methods are there?

All of them at a glance. Each path goes after https://api.consent.link; the public keys for checking decision tokens are at https://app.consent.link/.well-known/jwks.json.

MethodWhat it does
POST/v3/linksCreate a link
POST/v3/links/bulkCreate up to 500 links
GET/v3/linksList links, with filters
GET/v3/links/{id}One link and its numbers
PATCH/v3/links/{id}Change a link
DELETE/v3/links/{id}Switch a link off
GET/v3/profilesList page profiles
POST/v3/profilesCreate a page profile
GET/v3/profiles/{id}One page profile
PATCH/v3/profiles/{id}Change or publish a profile
DELETE/v3/profiles/{id}Delete a profile
POST/v3/consents/verifyCheck a decision token once
GET/v3/webhooksList webhooks
POST/v3/webhooksAdd a webhook
GET/v3/webhooks/{id}One webhook
PATCH/v3/webhooks/{id}Change the URL or events, or switch on and off
DELETE/v3/webhooks/{id}Delete a webhook
GET/v3/consentsThe journal, with filters
GET/v3/consents/{id}One record with its evidence
POST/v3/consents/{id}/withdrawWithdraw a consent on the visitor's behalf
GET/v3/consents/{id}/proofPDF proof of a recordPro and Business
GET/v3/consents/{id}/snapshotThe page the visitor saw, as HTML
GET/v3/consents/{id}/snapshot.pngThe page the visitor saw, as a screenshot
POST/v3/exportsStart a CSV exportPro and Business
GET/v3/exports/{id}An export and its statePro and Business
GET/v3/exports/{id}/fileDownload the CSVPro and Business
GET/v3/stats/summaryTotals for a range of days
GET/v3/stats/dailyNumbers by day (and country)
GET/v3/usageConsents this period and the limit

The consent page

What will visitors see, and in which language?

The page you set up in «Design» in the cabinet: the text template from our library, the data recipients, the label, the link to your privacy policy, and on Business your logo and colour. The link's own type and template take precedence over the design. When you publish a new version, the pages of your links change within a minute.

The language is picked from the visitor's browser: English, Spanish, German, French, Italian, Portuguese or Russian. You choose a template, not words: the texts come from our library, so you can't write your own.

Until something is published in «Design», the page shows no link to your privacy policy and no data recipients. Links are still created, and the responses to POST /v3/links, POST /v3/links/bulk and PATCH /v3/links/{id} carry the header Consent-Link-Warning: design_unpublished while the link's configuration has no published version.

How do I give different links different page settings?

Save several profiles of the page in «Design» or through the API, each with its own id such as black-friday, and name one when you create a link: "profile": "black-friday" in POST /v3/links or in a bulk item. Without profile the link gets your default profile.

A link keeps its profile: making another profile the default later doesn't move old links. PATCH /v3/links/{id} with profile moves one link, and "profile": null moves it to the default of that moment. An unknown id is a 422 on the field profile. The link object shows its profile, and GET /v3/links?profile=black-friday lists one profile's links. How many profiles you can have depends on the plan: 1 on Free, 5 on Pro, 50 on Business, the default included.

How do I manage profiles through the API?

GET /v3/profiles lists them, default first. POST /v3/profiles creates one with id and policy_url (required) and, if you like, name, template, vendors, type, remember, brand_name and default. It is published at once. PATCH /v3/profiles/{id} changes it: the page fields publish a new version straight away, name and "default": true alone publish nothing.

DELETE /v3/profiles/{id} removes a profile that isn't the default (409 profile_is_default) and has no active links (409 profile_in_use). Creating one over the plan limit, or using one that is over the limit after a downgrade, gets 402 profiles_limit_reached. Changes need a live key.

What happens to the parameters in my address, such as utm or gclid?

With consent the visitor goes to your destination exactly as you gave it. Without consent utm_* and your other parameters stay too, and only the ad click IDs are removed: gclid, gbraid, wbraid, fbclid, msclkid, ttclid, twclid, li_fat_id. In both cases we add one parameter, cl_ct, the decision token.

Only the destination's own parameters count: anything added to the short link itself (https://consent.link/nk7Qa?utm_source=…) is not passed on. Put everything your site needs into destination, or create a link per campaign.

Where the visitor goes: with consent, then without
https://shop.example/sale?utm_source=insta&gclid=Cj0K…&cl_ct=eyJhbGciOiJFUzI1NiIs…
https://shop.example/sale?utm_source=insta&cl_ct=eyJhbGciOiJFUzI1NiIs…

Decisions and webhooks

How does my site learn what the visitor decided?

Every redirect from the consent page carries cl_ct, a signed token (JWT, ES256) that lives 10 minutes. Check it from your server with POST /v3/consents/verify: the answer has the decision, and each token can be checked once, so a copied address is worthless. Read the token, then remove it from the address.

What it holds: decision (granted or denied), purposes (what the visitor allowed), cid (the journal record), sid (the visitor's pseudonym in your account, the same on all your links), cexp (until when the consent holds, Unix time), link, mode, iat and exp. No IP, browser or other personal data. A visitor who agreed earlier and kept «Don't ask again» goes straight through, with granted.

You can also check the signature yourself with the keys at https://app.consent.link/.well-known/jwks.json and any JWT library: check exp, and that link is one of yours. Your own check can't tell whether a token was already used; the API method can. Errors: 422 token_invalid, 422 token_expired, 409 token_used.

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

How do I get decisions on my server as they happen?

Create a webhook: POST /v3/webhooks with url and the events you want (the table below). The address must be https on a public host name. The answer has secret (whsec_…) for checking signatures: it is shown once, so save it. An account can have up to 10 active webhooks.

The delivery log, the «Retry» button and switching a webhook off or on are in the cabinet under «Account» → «Webhooks».

Request
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"]
  }'
Answer
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"
}

Which events are there, and what do they carry?

Every event is a POST with a JSON body: id (evt_…), type, created_at, mode and data. A consent record in data.consent has id, link, decision, purposes, scope (domain when the visitor kept «Don't ask again», else link), sid, dest_domain, country, notice_version, created_at and expires_at. Never an IP, browser or cookie.

EventWhendata
consent.recordedA new journal record: an agreement, a refusal or a visitor who agreed earlierconsent
consent.withdrawnA consent was withdrawn: by the visitor on «My consents», or by you through the APIconsent, withdraws
link.blockedWe blocked a link because its destination turned out dangerouslink, destination, reason, message
export.readyA CSV export is finished (or failed)id, status, filter, rows, bytes, sha256, file, expires_at
usage.warning80 % or 100 % of the period's consents are usedpercent, consents, limit, period_start, period_end
consent.expiredA consent lapsed: it was given 6 months ago and not withdrawn sinceconsent, expired_at
What your server receives
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"
    }
  }
}

How do I check that a webhook came from you?

Each request carries the headers Consent-Event, Consent-Delivery and Consent-Signature: t=1793712000,v1=5f3a…. v1 is the hex HMAC-SHA256 of the text t, a dot and the raw request body, keyed with your webhook secret.

Compute it from the body exactly as received (before any JSON parsing), compare in constant time, and refuse a t more than five minutes away from your clock, so a recorded request can't be replayed.

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

What if my server is down when an event is sent?

A delivery counts when your server answers 2xx within 10 seconds; redirects are not followed. Otherwise we try again, waiting 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours between the tries, 6 tries in all. After the last one the delivery is marked failed and stays in the log, where «Retry» sends it again for up to 3 days after the event.

So answer fast and do the work afterwards. The same event can arrive more than once, for example after a timeout: skip ids you have already processed. Events don't always arrive in the order they happened.

How is consent withdrawn, and what happens then?

Visitors see and withdraw their consents on «My consents». If a visitor asks you instead, withdraw it for them: POST /v3/consents/{id}/withdraw, no body, with the id of their granted or reused record. It works for any consent still in force, however far back your plan shows the journal. The answer is 201 with the new withdrawn record, whose first_consent_id is the consent taken back. A record that is not a consent in force (refused, already withdrawn or expired) gets 409 consent_not_in_force; one you can't read gets 404, as with GET /v3/consents/{id}.

Both ways end the same: a withdrawn record in the journal, consent.withdrawn to your webhook with data.withdraws, the id of the consent taken back, and from then on your links ask that visitor again. A withdrawal closes every consent the visitor gave for that domain before it, and it doesn't count towards your limit.

Stop using the visitor's data on your side: match by sid or by the record id.

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

How long does a consent last?

A consent holds for 6 months from when it was given: expires_at in the record, cexp in the decision token. A reused record carries the expires_at of the consent it reuses. After that, your links ask the visitor again.

When a granted consent lapses without having been withdrawn, your webhook gets consent.expired with data.consent, the record, and data.expired_at. We check once an hour, so it arrives within the hour after expires_at, once per consent. reused records get no event of their own: the one for the consent they reuse covers them. The event goes only to webhooks with consent.expired in events. An expiry is announced once, even if no webhook was waiting for it then: it isn't sent again later.

What your server receives
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 and banners

How does my site apply the visitor's decision itself (Google Consent Mode v2)?

Put our script in <head>, before GTM and gtag: https://consent.link/js/consent.js. It sets the Consent Mode v2 default (everything denied but functionality_storage and security_storage, with wait_for_update 500 ms), reads cl_ct from the address, checks its signature against the keys at https://app.consent.link/.well-known/jwks.json right in the browser, and updates consent.

The decision is kept in the cl_consent cookie on your domain: until cexp for a consent, 180 days for a refusal, so the next pages need no banner. Once checked, cl_ct leaves the address bar; your other parameters stay. The script sends us nothing.

If you record the decision on your side, still check the token from your server with POST /v3/consents/verify: the script can't know whether a token was used before.

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

How do I set it up in Google Tag Manager?

Download the template consent-link.tpl; in GTM open Templates → New → ⋮ → Import and save it. Create a tag from it with the «Consent Initialization – All Pages» trigger.

The tag sets the defaults, applies the decision stored in the cl_consent cookie and loads the script, which checks a fresh cl_ct. You then don't need the script in <head> as well.

Which Consent Mode signals does each purpose grant?

The purposes of the token grant these signals. Anything not granted stays denied; a refusal (denied) denies everything but the two that are always granted.

PurposeConsent Mode signals
stats analytics_storage
ads ad_storage, ad_user_data, ad_personalization
app_install ad_storage, ad_user_data
personalization personalization_storage
Always functionality_storage, security_storage

I use Cookiebot, OneTrust or another banner. What do I do with it?

Don't show a second banner to someone who has decided on our page. The script gives the decision in window.consentLink.decision (granted, denied or null) and window.consentLink.has('stats'); window.consentLink.ready resolves once a token in the address has been checked. There is also the consentlink:update event on document and consent_link_update in dataLayer. Load our script before the banner's.

Cookiebot: pass the decision on with Cookiebot.submitCustomConsent and its banner stays away. OneTrust has no public function to record consent from the site, so load its script only when there is no decision. Other banners the same way: pass the purposes on through their API, or don't load the 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);
});

Journal, proof and statistics

How do I read the journal?

GET /v3/consents lists records newest first, with the filters decision (granted, denied, reused, withdrawn), link, country (DE), from, to and the same paging as links. GET /v3/consents/{id} returns one record with its evidence (IP address, browser, language, the page text hash, when the page was shown and how long the visitor took) and its place in the hash chain, chain.

A refusal keeps only the decision, the time, the link and the country, so its evidence is null. The API gives the data in full; the cabinet hides part of it until you turn on two-factor sign-in. How far back you see depends on the plan (the table in «Plans and limits»).

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

How do I get a PDF proof of a consent?

GET /v3/consents/{id}/proof returns the record as a PDF: the decision, who, what was shown, the hashes and how to check them. It is in the language of your cabinet. Available on Pro and Business (402 plan_required on Free), up to 20 a minute. If the PDF can't be made right now, the answer is 503 unavailable with Retry-After.

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

How do I see and check the page the visitor agreed on?

Every granted record carries evidence.snapshot_hash: the SHA-256 of the consent page as the visitor saw it, as HTML. One snapshot serves every visitor of the same page, that is the same design and text versions, language and link type. GET /v3/consents/{id}/snapshot returns that HTML, with the hash in the X-Snapshot-Hash header, and GET /v3/consents/{id}/snapshot.png a screenshot of it. Both follow the rules of GET /v3/consents/{id}, journal depth included.

To check it, compute the SHA-256 of the HTML exactly as received: it equals snapshot_hash, and the record's own hash in the chain covers snapshot_hash, so the page can't be swapped afterwards. What differs from visit to visit is fixed in the snapshot: the destination's host reads example.com, the form's signature is empty and its action is #. The real address is in dest_domain and evidence.destination.

reused records, refusals and records made before snapshots existed have snapshot_hash null, and their snapshot answers 404 not_found («This record has no page snapshot.»). Right after a decision the files may still be being written: 404 not_found with «The page snapshot is not ready yet.» means try again shortly. The HTML comes within seconds, the screenshot a little later. The record's page in the cabinet and the PDF proof show the screenshot and the hash too.

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

sha256sum page.html

How do I export the journal as CSV?

POST /v3/exports with the filters of GET /v3/consents (decision, link, country, from, to) answers 202 at once with the export's id. The file is made in the background: you get the export.ready webhook, or check GET /v3/exports/{id} until status is ready, then download GET /v3/exports/{id}/file. The export also has rows and the file's sha256.

Files are kept 7 days (410 export_expired after that). Up to 3 exports can be in progress at once (429 too_many_exports). Pro and Business.

Request
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"
  }'
Answer
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
}

Can I get statistics and my usage through the API?

GET /v3/stats/summary gives shown, granted, denied, left, reused and withdrawn for UTC days from from to to (2026-10-31, both included; by default the last 30 days, at most 366 days, as far back as your plan shows the journal). Add link for one link. GET /v3/stats/daily gives the same numbers day by day, and on Pro and Business by country with by=country. Bots are never counted.

GET /v3/usage shows this period's consents, your limit, what is remaining, and the period's start and end.

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

Plans and limits

What does each plan include?

The API, webhooks and the consent page in seven languages are on every plan. The plans differ in consents a month, how far back the journal is shown, the request rate and extra features. Prices in euros, excluding VAT.

PlanPrice a monthConsents a monthJournalRequests a minutePage profilesAlso
Free€01,000Journal for 30 days601—
Pro€910,000Journal for 1 year3005CSV export and PDF proof · Statistics by day and country
Business€99250,000Journal for 3 years1,00050CSV export and PDF proof · Statistics by day and country · Your logo and colour · Links on your own domain

What happens when my consents run out?

Only new agreements count: refusals, repeat visits with «Don't ask again», and bots don't. At 80 % and 100 % of the limit you get an email and the usage.warning webhook.

Once the limit is used up, new live links are refused with 402 limit_reached. Everything else keeps working: existing links still ask and record, webhooks and tokens arrive as usual, and nothing is hidden. Creating links opens again at the start of the next period (on Free the 1st of the month, on paid plans the day of payment) or right after an upgrade.

How many requests can I send?

Per account, all keys together: 60 a minute on Free, 300 on Pro, 1,000 on Business. Over that the answer is 429 rate_limited with a Retry-After header; the headers X-RateLimit-Limit and X-RateLimit-Remaining show where you stand. Some methods have their own limit as well: PDF proofs 20 a minute, new exports 10, export downloads 30.

Errors and security

What do errors look like?

Always the same shape: the HTTP status and a JSON body with a stable code and an English message. Act on the code; the message may be reworded. Invalid fields give 422 validation_failed with fields: each field with its problems.

StatusCodeMeaning
401unauthenticatedNo key, or the key is wrong or revoked
402limit_reachedThis period's consents are used up: no new live links
402plan_requiredThe feature needs a higher plan
402profiles_limit_reachedOver the plan's number of page profiles
403account_blockedThe account is blocked: contact support
403links_blockedThe account may not create links: contact support
403mode_mismatchA live link is changed with a live key, a test link with a test key
403live_key_requiredThis needs a live key
404not_foundNothing with this id in your account
405method_not_allowedThis HTTP method isn't allowed on this path
409link_disabledThe link is switched off and can't be changed
409link_blockedThe link was blocked and can't be changed
409idempotency_in_progressA request with this Idempotency-Key is still running
409token_usedThis decision token was already checked
409export_not_readyThe export is still being made
409export_failedThe export failed: start a new one
409consent_not_in_forceThe record is not an in-force consent: refused, withdrawn or expired
409profile_is_defaultThe default profile can't be deleted
409profile_in_useThe profile still has active links
410export_expiredThe file is no longer kept: start a new export
422validation_failedSome fields are invalid: see fields
422destination_blockedThe destination is dangerous or blocked
422idempotency_key_reusedThe Idempotency-Key was used with another body
422token_invalidNot a valid decision token for your account
422token_expiredThe decision token is older than 10 minutes
422webhooks_limit_reachedToo many active webhooks
429rate_limitedToo many requests: wait for Retry-After
429too_many_exportsToo many exports in progress
429daily_limit_reachedNew Free account: the day's links are made, more from 00:00 UTC
500server_errorSomething failed on our side: try again later
503unavailableTry again shortly: see Retry-After
Answer
422 Unprocessable Content
{
  "error": {
    "code": "validation_failed",
    "message": "Some fields are invalid.",
    "fields": {
      "destination": ["The destination must be an absolute http or https URL."]
    }
  }
}

What do I do if a key leaks?

Create a new key on «Connect via API», switch your server to it, then revoke the old one: it stops working at once. Revoking a key doesn't touch your links, the journal or webhooks.

Keep keys in your server's environment variables, never in a browser, a mobile app or a public repository. If a webhook secret may have leaked, switch that webhook off in the cabinet and create a new one.

Ready to connect?

Register, create a key on «Connect via API» and send your first request in a few minutes.

Start free