Publisher consent API
A page-level JavaScript API for reading the visitor's consent state and reacting when it changes — available under every privacy regime, including the ones with no industry signal at all.
Why it exists
Under the IAB TCF you have __tcfapi; under GPP, __gpp; with Google's
tags, Consent Mode. Under Brazil's LGPD, Türkiye's KVKK and most other regimes there is no such
thing, and a publisher who wants to ask "may I run analytics for this visitor?" has nothing
to call. __fcapi is that contract.
It is deliberately available everywhere, not only where no standard exists — you should not need
one code path for a German visitor and another for a Brazilian one. Where TCF or GPP do apply their
strings are included here too, so __fcapi is a superset rather than a rival.
Availability
It is defined at parse time, before any tag on the page, by a stub inside the consent script — so
you can call it from the top of your <head> without waiting for anything. Before
the full bundle loads the stub answers ping and accepts listeners; anything else is
queued and replayed. Listeners you register against the stub are adopted by the real
implementation, not dropped.
Commands
__fcapi(command, callback, parameter). The callback receives
(data, success); the value is also returned synchronously once the CMP has loaded.
getConsentState
__fcapi('getConsentState', function (state) {
if (state.consented.analytics) startAnalytics();
});
{
apiVersion: "1.0", cmpId: 317, cmpStatus: "loaded", cmpLoaded: true,
regime: "gdpr", gdprApplies: true,
hasChoice: true, // false while the banner is still unanswered
consented: { essential: true, analytics: false, marketing: false, … },
categoriesById: { "1": true, "2": false, … },
acceptedCategoryIds: ["1"],
tcString: "…", // when TCF applies, else ""
gppString: "…" // when GPP applies, else ""
}
consented is the field to gate on. Check
hasChoice first: false means the visitor has not decided yet, which is not
the same as a refusal.
Do not key on the category's display name — it is translated per language and a site owner can
rename it at any time. consented and categoriesById are stable; the label
is not.
addEventListener / removeEventListener
var id = __fcapi('addEventListener', function (ev) {
// ev.eventName: "listenerRegistered" | "consentChanged"
// ev.reason: "cmpLoaded" | "save" | "update"
// ev.state: the same object getConsentState returns
if (ev.state && ev.state.consented.marketing) loadAdTags();
});
__fcapi('removeEventListener', null, id);
The listener fires on every persisted change, including a withdrawal made mid-page — the case you most need to hear about and cannot poll for. Your handler is individually guarded: if it throws, other listeners still run.
ping
{ apiVersion: "1.0", cmpId: 317, cmpStatus: "stub" | "loaded", cmpLoaded: false | true,
regime: null | "gdpr" | "canada" | "turkey" | "us" | "none", hasChoice: false | true }
regime is null while the visitor's region is still being resolved —
deliberately, so you can tell "not yet" from "no regime applies"
("none").
Worked example
<script>
// Safe at the very top of <head>: the stub is already there.
window.__fcapi && __fcapi('addEventListener', function (ev) {
var c = ev.state && ev.state.consented;
if (!c) return;
if (c.analytics && !window.myAnalyticsLoaded) {
window.myAnalyticsLoaded = 1;
startAnalytics();
}
if (!c.marketing) disableAdPersonalisation(); // also fires on a mid-page withdrawal
});
</script>
window._fc_gcm — the computed Google Consent Mode signals
A companion global: the seven Google Consent Mode v2 types as the page computed them for this visitor, from cookie categories combined with IAB TCF purposes.
window._fc_gcm
// { ad_storage: "denied", ad_user_data: "denied", ad_personalization: "denied",
// analytics_storage: "granted", functionality_storage: "granted",
// personalization_storage: "denied", security_storage: "denied" }
It is refreshed before the __fcapi listeners are told about a change, so a
handler that reads it is reading this decision and not the last one. It is published whether or not
the project has Consent Mode switched on — that switch governs whether we push consent commands to
your page, not whether the visitor decided. It never carries wait_for_update, which
belongs to a consent default and which Google rejects on an update.
This is what our Google Tag Manager tag reads, and it is there for anyone driving Consent Mode by hand for the same reason.
Stability
apiVersion is 1.0. Changes are additive only — once you
ship against this we will not quietly alter the shape; apiVersion moves if it ever has
to.