Skip to main content
Expys uses a two-token model. Getting this right is the single most confusable part of the integration, so read this page before writing any code.

The two credentials

The Org-API-Key is a secret with full org authority. Never ship it in an app, a mobile binary, an environment variable shipped to clients, or any client-side code. A member token that leaks only exposes one member for a few minutes; an Org-API-Key that leaks exposes everything.

How the flow works

Your backend holds the Org-API-Key and exchanges it for a short-lived member token scoped to one of your users. The app uses that member token as a Bearer credential for data calls, and asks your backend for a fresh one when it nears expiry.

Step 1: Exchange (server-side)

Your backend calls exchangeToken with the Org-API-Key. The request identifies the member by your own stable id, and may set profile fields used elsewhere (display name, email, and the member’s tier).
string
required
Your stable identifier for the member. Scopes the minted token to this user.
string
Optional display name stored on the member profile.
string
Optional email stored on the member profile.
string
Optional tier for the member; flows into eligibility.
The response is a TokenGrant:
string
required
The short-lived member token. Hand this to the app.
string
required
ISO-8601 expiry. Pass it to the SDK as tokenExpiresAt(Ms) so it can refresh proactively.

Step 2: Use and refresh (in the app)

Initialize the SDK with the member token, its expiry, and a refreshToken hook that calls back to your backend (which holds the Org-API-Key) to mint a new token. The hook returns the new token and expiry.

The refresh contract

The SDKs implement refresh identically:
1

Proactive

Before a request, if the token expires within refreshSkew (default 30s), the SDK calls refreshToken first and uses the new token.
2

Reactive (once)

If a request still returns 401, the SDK calls refreshToken once and retries the request a single time with the new token.
3

Failure propagates

If refreshToken throws, the SDK does not retry it - the error propagates so your app can send the user back through your own auth. A failed refresh is never retried in a loop.
Provide tokenExpiresAt(Ms) whenever you can. Without it the SDK cannot refresh proactively and falls back to reactive (401-triggered) refresh only, which adds one round-trip on the first expired call.

Security checklist

Org-API-Key is only ever read on your backend.
The app receives only member tokens, with their expiry.
refreshToken calls your backend, not the Expys exchange endpoint directly.
Member tokens are short-lived; do not persist them beyond their expiry.