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): Promise<void>
vevee.analytics.optIn(distinctId: string): Promise<void>
vevee.analytics.isOptedOut(distinctId: string): Promise<boolean>Behaviour
optOutis idempotent. Calling it twice is the same as once.- After
optOut, every subsequentcapture()for thatdistinctIdis silently dropped. The response shape looks normal - you cannot tell a dropped event from a recorded one. That’s deliberate: the user has objected to being tracked; they shouldn’t be able to be tracked at the response-shape level either. - 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.