vevee.analytics.optOut() · optIn() · isOptedOut()POST /api/v1/opt-outPOST /api/v1/opt-inGET /api/v1/opted-outsk_live_
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' | 'all'): Promise<void> // default 'all'
vevee.analytics.optIn(distinctId: string, scope?: 'analytics' | 'ai_personalization' | 'all'): Promise<void> // default 'all'
vevee.analytics.isOptedOut(distinctId: string, scope?: 'analytics' | 'ai_personalization'): Promise<boolean> // default 'analytics'scope picks the purpose. 'analytics' governs capture(); 'ai_personalization' governs compose(); 'all' (the default for opt-out/opt-in) covers both. One-argument calls are unchanged.
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. - 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. |
Related: deletePerson() - Art. 17 erasure (irreversible), exportPerson()- Art. 15 / 20 access & portability, and the Privacy & GDPR guide for the full picture.