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

  • optOut is idempotent. Calling it twice is the same as once.
  • After optOut, every subsequent capture() for that distinctId is 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.
  • 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.