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

  • optOut is 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 real distinctId is 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 identifying properties for opted-out users. Use deletePerson() to erase stored data.
  • Existing data stays put. Use deletePerson() to erase it.
  • optIn re-enables processing. A no-op if the person was never opted out.
  • isOptedOut returns 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

CodeStatusWhen
requires_secret_key403Called with a pk_* key. The SDK throws before the network call to fail fast in client-side misuse.
invalid_request400Empty or oversized distinctId.
i
Related: deletePerson() - Art. 17 erasure (irreversible), exportPerson()- Art. 15 / 20 access & portability, and the Privacy & GDPR guide for the full picture.