pex

Backfill existing users

The recipes in this section handle users going forward. To bring in the base you had before Apex, you have two paths — pick based on how complete you need coverage to be.

Passive (default, zero effort)

Wire your identity trigger to the login / post-authentication event (not just signup):

Now every existing user reconciles automatically the next time they log in. It's safe: the server-side trigger carries the verified internal_user_id, not a shared-device guess. Within one login cycle your active base is covered, with zero migration work.

Active (full or dormant base)

When you need everyone in immediately — including dormant users who won't log in soon — push them in bulk.

CSV import

Upload a CSV of userId,email[,name,plan,avatar_url,...] to the import endpoint. Each row becomes an identified Contact keyed by internal_user_id; later events reconcile against it via the identifier index. Any canonical attribute column (e.g. avatar_url) is captured — include avatar_url to backfill end-user photos.

curl -X POST https://app.apex.inc/api/v1/contacts/import \
  -H "x-api-key: apex_sk_..." \
  -H "content-type: text/csv" \
  --data-binary @users.csv
userId,email,name,plan,avatar_url
u_1001,jane@acme.co,Jane Doe,pro,https://cdn.acme.co/avatars/jane.png
u_1002,sam@globex.com,Sam Lee,free,

The endpoint reports { imported, reconciled, skipped, errors }. It's idempotent — re-importing the same userId updates the existing Contact rather than duplicating it.

Per-provider backfill scripts

If your base lives in your auth provider, page through it and POST in batches of 100 via the Server Events API. Reference scripts live in scripts/identity-backfill/:

ProviderAPI used
CognitoListUsers (paginated)
Auth0Management API GET /api/v2/users
FirebaseAdmin SDK listUsers()
Supabaseauth.admin.listUsers()

Each script reads provider credentials from env, pages through every user, and emits a user_identified event carrying the provider's stable id as data.userId. Pass the provider's profile-photo field (picture / photoURL) as data.avatar_url to backfill end-user photos in the same pass.

Tip

Run the active backfill once, then leave the passive trigger in place. New users and re-engaging dormant users keep reconciling on their own — no second migration.