Organizations are tenants
Your organization is a tenant of 4D. Your API key is bound to it, your data lives inside it, and isolation is enforced at the database layer with row-level security - not just in application code. There is no cross-tenant read path: a partner API call can only ever see its own organization’s rows.Users and members: a two-level identity
4D separates who a person is from their relationship with your organization:- A user is a person’s single, global 4D identity, keyed by email (matched case-insensitively). Core profile fields - name, country, date of birth, locale - live here, along with their wallet.
- A member is that user’s relationship with one organization: your
external_idfor them, their role, their status (active,locked,removed), and when they joined.
POST /v1/users with an email that already has a 4D
account, the platform doesn’t create a duplicate - it attaches the
existing user to your organization as a new member. Your view of them is your
membership plus their profile; other organizations’ relationships with the
same user are invisible to you.
Two consequences worth designing around:
external_idis yours. It’s unique per organization, so you can address members by the id your own systems already use.- Age is derived, never stored. The API returns a coarse
age_bandcomputed fromdob+countryat read time; the raw date of birth is redacted from partner reads. A member with unknown age is treated as a minor.
Wallets
Every user gets an EVM wallet, provisioned automatically in the background when they’re created (wallet.status moves from provisioning to active).
It’s global to the user - one wallet across all their memberships - and it’s
where XP settles on-chain. You never manage keys; wallets are platform-custodied.
Attributes: your org’s custom fields
Beyond the fixed core profile, each organization enables its own typed attribute registry -text, number, date, boolean,
single_choice, multi_choice - and writes per-member values against it
(PUT /v1/users/{userId}/attributes). Choice-typed attributes validate
against your configured choice list. Every value carries provenance, so you
can always tell whether a value came from the API, an admin, the member, or a
bulk import.
Labels: lightweight admin grouping
Labels are free-form strings admins put on members (“u18-squad”, “season-ticket”) - normalized (trimmed, lowercased), capped at 100 per member. They’re deliberately not entities: no schema to manage, just consistent strings to filter and segment by in the console.Where to go next
- Create a user - the create/attach lifecycle in practice.
- Migrate an existing member base - the same create-or-attach semantics, in bulk.