SDK How it works
How it works
Two circuits that never mix: your users talk to ID Side about their preferences, and your backend talks to ID Side about AI calls. The developer sits on exactly one side of that wall — which is what makes the proof worth something.
Circuit A — preferences
The user talks to ID Side. You don't listen in.
The Preference Center widget is an iframe served from our domain, embedded in your pages. When Marie opts out of AI training, that choice travels from her browser to our servers directly. Your frontend hosts the iframe; it cannot read what happens inside it, and your backend has no endpoint to write a preference — the API simply does not exist.
Preferences can also arrive without the widget: the browser's Global Privacy Control signal is detected and recorded as its own consent source. GPC is legally binding in about a dozen US states (California among them); in Europe it currently has no legal force — we record it as an explicit signal of the user's intent, nothing more, and an explicit widget choice always outranks it.
Circuit A′ — the policy
When nobody is asked: the organization's policy
There is a second way a preference reaches a call, and it is not the person's. An organization declares what it refuses for everyone working under it — for the whole company, or for one team of it — and that declaration is read on every authorization, on top of whatever the person's own state resolved to.
It can only restrict. Only the refusals in a policy are read. It cannot switch an option back on, and a person's own refusal survives a policy that would allow it. That is not politeness: a policy able to permit would let a customer overturn their users' choices from their own dashboard, which is the one thing this whole design exists to make impossible.
At work, consent is not available as a basis. The GDPR does not treat an employee's consent as freely given, so an organization's policy carries legitimate interest or contractual obligation — or nothing at all. It is not a rule we remember to apply: the model does not accept the value, so the form cannot offer it.
What an employee is owed is not a popup, it is a statement. Each member has a transparency page naming their organization, their teams, the basis invoked, exactly which options are refused for them, and every figure their employer can see about them. The same figures, by a rule a test enforces — nothing is shown to the employer that is hidden from the person.
Where this mode stops
It assumes an employment relationship or an equivalent one. Nothing in the code stops you from pointing it at your visitors, and we would rather explain than forbid. Someone who was never asked, and who does not work for you, resolves with no legal basis established — not_established, the value stamped into the ACT token, chained into the ledger and returned by the verification endpoint, on every single call. The document you came here for is the document that says it.
Claiming legitimate interest over people who are not your staff does not repair it either. It signs and dates the claim, in your own append-only ledger, for whoever asks about it later. That is the whole design: dissuasive and evidential, never policing.
Circuit B — the AI call
Your backend asks permission before every call
Before each request reaches the provider, the wrapped client calls ID Side with a pseudonymous user reference and a hash of the request — never the content. We resolve the user's current preferences, decide what must be enforced, and answer with two kinds of injection. If we are unreachable, the SDK fails closed: it raises an error and the AI call does not go out. No authorization, no request.
- decision
- allow
- mode
- live
- consent_state
- training.foundation
- false
- memory
- false
- retention.conversation
- true
- source
- widget
- legal_basis
- consent
- binding
- user_id
- params_forced
- store=false
- act_token
- eyJhbGciOiJFUzI1NiJ9…
- token_id
- act_01kycmhhx6jw4614ze3gt66n8b
- expires_at
- 2026-07-25T13:38:31Z
One authorized call, and what it leaves behind.
The ledger entry is not a field of this response, and not a job that runs afterwards: it is appended synchronously, in the same transaction as the token record, so the proof exists before the call goes out. A response you can read is a call that is already on the record.
Two kinds of injection
Forced provider parameters
Where the provider exposes a real privacy lever, the SDK sets it on the request itself and your code cannot unset it. For OpenAI, a user who refused training gets store=false on every call — actually applied, not merely logged. The mapping lives in our provider registry, so when a provider changes its API, we update a row instead of shipping you a new SDK. Where no such lever exists, the trust level for that provider says so plainly.
The ACT token
Alongside the forced parameters, each request carries a signed AI Consent Token: a compact proof binding this one request (by hash and nonce, one hour of validity, no reuse) to the consent state it was authorized under. AI providers do not act on it today — we are candid about that. Its value is for you: a portable, cryptographic receipt that anyone can verify against our published public keys, anchored in the ledger.
One account, every app
Marie shouldn't have to repeat "no AI training" to every product she uses. With an ID Side account she says it once. When she links an app — through a first-party popup where she proves she controls her email — her preferences apply there immediately, and every future app is one click.
The linking flow is also what keeps identity honest: the developer supplies only his local user_id; Marie supplies the verified identity. Neither side can assert both halves, so no developer can quietly point his users at a permanently opted-in account.