Skip to main content
4D has four credential types. Which one you use depends on where the call is made from:

Organization API keys

Every partner API call authenticates with a bearer header:
The key is bound to your organization. There is no org id parameter anywhere in the API - your key is the tenancy. Isolation is enforced with row-level security at the database layer, so a request can only ever read or write rows belonging to your organization.
Treat org_ keys like passwords: server-side only, never in client bundles, rotate if exposed. Issue one from an authenticated partner admin session as described in the Quickstart.
An org_ key authenticates a service identity scoped to one organization. It cannot publish a module, install a module, issue a module credential, or mint another org_ key. Use a partner admin session for those operations. When setting up a private module, author and publish it with the admin session, then mint the org_ key afterward for any service that needs partner reads.

Admin session tokens

Key management, tracking-key management and module authoring need a partner admin session rather than an org_ key. A session token comes from signing in, either in the console or over the email OTP endpoints:
  1. POST /v1/auth/email-otp/request with {"email": "<admin email>"} sends a one-time code to that address.
  2. POST /v1/auth/email-otp/verify with {"email": "<admin email>", "otp": "<code>"} returns a token, the user, and enrollmentRequired.
  3. If enrollmentRequired is true, the administrator has no authenticator yet. POST /api/totp/enroll with the token as a bearer returns secret (base32), otpauthUri (for a QR code) and backupCodes. Add the secret to an authenticator app and keep the backup codes; they are shown once. GET /api/totp/status with the same bearer answers {"enrolled": <bool>, "enrollmentRequired": <bool>} at any point.
  4. Administrators must complete the second factor before any admin operation: POST /api/auth/two-factor/verify-totp with {"code": "<authenticator code>"} and the token as a bearer. Until then, admin routes answer 401 with the reason 2FA_REQUIRED.
  5. POST /v1/session/active-organization with {"organizationId": "<organization id>"} and the token as a bearer makes that organization the session’s active one. Sign-in picks the admin’s earliest seat on its own, so an administrator with seats on more than one organization does this before minting a key or publishing a module, or the key or module lands in the other organization.
The first verify-totp after enrolment replaces the session. The token from step 2 stops working, and the new session token arrives in the set-auth-token response header. The token in that response’s body is the old one, so do not use it. Use the header value for step 5 and every call after it. Later sign-ins, with the authenticator already enrolled, keep the step 2 token and send no set-auth-token header.
Send the token as Authorization: Bearer <token> on the key and module endpoints. A session belongs to the admin who signed in and acts on the session’s active organization; a key response carries that organization as organization_id and a module response as owner_organization_id, so check the one you meant. The token is not a credential to store or share.

Publishable tracking keys

Browser event tracking and publisher ad slots can’t hide a secret, so they don’t use one. POST /v1/track, POST /v1/content-interactions and GET /v1/ads/serve (publisher ads) authenticate with a publishable ek_live_… key that ships in client code, like an analytics measurement id. Abuse is bounded by two controls on the key itself, not by secrecy:
  • a per-key origin allowlist - tracking and ad requests are only accepted from browser origins you configured for the key;
  • per-key rate limiting.
You issue, list and revoke tracking keys with a partner admin session token (Admin session tokens), not with an org_ key: an org_ key gets 403 FORBIDDEN on /v1/engagement/keys. The console has no tracking-key screen yet, so keys are managed over the API only. See Trigger an event.

Signed-in fans on ad requests

A publishable key identifies your site, not the fan reading it, so GET /v1/ads/serve never takes a member id from the page. To target a signed-in fan, your server mints a signed identity with its organization API key and hands it to the page:
Name the fan by your own member_external_id or by the platform user_id, exactly one. The answer is 404 when no active member of your organization matches. The token is opaque, bound to your organization and that member, and expires after one hour, so mint a new one before then. The page passes it as identity_token on serve. A missing, expired, altered or other-club token serves the slot as if the fan were anonymous rather than failing, and so do the unsigned member_external_id and user_id serve parameters, which no longer identify anyone. Impressions and clicks are recorded against the member only when the fan holds your club’s personalised content consent; without it they are counted anonymously. Sponsors never see which fan saw or clicked an ad.

Content keys

Reading a club’s published articles from its own website has the same problem and the same answer: a publishable ck_live_… key in the page, with a per-key origin allowlist as the control. You issue content keys in the console rather than over the API. See Content API authentication.

Module credentials

Installed modules push events server-to-server via POST /v1/ingest with an mk_live_... bearer credential plus an HMAC signature header (x-4d-signature) and an Idempotency-Key. Issue this credential from an admin session after installing a private module. See Publish a private module.

Error envelope

All authenticated surfaces return errors in one envelope:
Missing or invalid credentials are a 401; a valid credential without permission for the operation is a 403 or, where the resource’s existence shouldn’t be revealed, a 404. code values are the stable contract to program against.