GDPR Art. 21 - right to object. Stop processing future events for a specific user, then resume on request. All three require a secret key (sk_*) - never call them from the browser.
Signatures
vevee.analytics.optOut(distinctId: string, scope?: 'analytics' | 'ai_personalization' | 'prompt_logging' | 'all'): Promise<void> // default 'all'
vevee.analytics.optIn(distinctId: string, scope?: 'analytics' | 'ai_personalization' | 'prompt_logging' | 'all'): Promise<void> // default 'all'
vevee.analytics.isOptedOut(distinctId: string, scope?: 'analytics' | 'ai_personalization' | 'prompt_logging'): Promise<boolean> // default 'analytics'scope picks the purpose. 'analytics' governs capture(); 'ai_personalization' governs compose(); 'prompt_logging' governs the prompt / response strings on track(), reserve(), commit() and release(); 'all' (the default for opt-out/opt-in) covers every purpose. One-argument calls are unchanged. Requires @vevee/sdk 1.6.0 or later for the prompt_logging scope.
Behaviour
optOutis idempotent. Calling it twice is the same as once.- After
optOut(id, 'analytics'),capture()keeps recording that user's events, but under a random pseudonym - their realdistinctIdis never written to new events or the pseudonym profile, so aggregate funnels still work while the activity is no longer linked to them. You are responsible for not sending identifyingpropertiesfor opted-out users. UsedeletePerson()to erase stored data. - After
optOut(id, 'prompt_logging'), prompt and response strings sent for that user are never stored, even when the app's prompt logging toggle is on. This includes the prompt passed toreserve(): it is not held on the reservation either. Metering is unaffected: events, counters and limits work exactly as before, so you can keep passingpromptunconditionally. Pass the same id you use asuserIdin the metering methods. - Opt-outs are per mode: a call made with an
sk_test_key only affects sandbox data. - Existing data stays put. Use
deletePerson()to erase it. optInre-enables processing. A no-op if the person was never opted out.isOptedOutreturns a boolean. Use it to populate the privacy settings checkbox in your app.
Settings-page integration
Add a toggle to your account settings, then propagate the change to APL from your backend - the sk_* key must never reach the browser:
// React / Next.js client component
'use client';
export function PrivacySettings({ userId }: { userId: string }) {
const [enabled, setEnabled] = useState(true);
useEffect(() => {
fetch(`/api/privacy/status?userId=${userId}`)
.then(r => r.json())
.then(({ optedOut }) => setEnabled(!optedOut));
}, [userId]);
async function toggle(next: boolean) {
setEnabled(next);
await fetch('/api/privacy/toggle', {
method: 'POST',
body: JSON.stringify({ userId, enable: next }),
});
}
return (
<label>
<input
type="checkbox"
checked={enabled}
onChange={(e) => toggle(e.target.checked)}
/>
Enable product analytics
</label>
);
}The backend routes - calling APL with the secret key:
// app/api/privacy/toggle/route.ts
import { aplClient } from '@/lib/vevee'; // createClient({ apiKey: 'sk_live_…' })
export async function POST(req: Request) {
const { userId, enable } = await req.json();
if (enable) await aplClient.analytics.optIn(userId);
else await aplClient.analytics.optOut(userId);
return Response.json({ ok: true });
}
// app/api/privacy/status/route.ts
export async function GET(req: Request) {
const userId = new URL(req.url).searchParams.get('userId')!;
const optedOut = await aplClient.analytics.isOptedOut(userId);
return Response.json({ optedOut });
}CMP integration
Wire the same calls to your consent management platform’s change events - the pattern is the same regardless of provider (Cookiebot, Iubenda, OneTrust, Usercentrics, Didomi):
cmp.onConsentChange((consents) => {
// Call your own backend route - never expose sk_* in the browser.
fetch('/api/privacy/toggle', {
method: 'POST',
body: JSON.stringify({ userId: currentUserId, enable: consents.analytics === true }),
});
});Errors
| Code | Status | When |
|---|---|---|
requires_secret_key | 403 | Called with a pk_* key. The SDK throws before the network call to fail fast in client-side misuse. |
invalid_request | 400 | Empty or oversized distinctId. |