API: Auth
MeerPartners authentication endpoints: MeerID exchange, token rotation and revocation, S2S token-exchange, partner activation and business creation.
The /api/v1/auth/* group handles people signing in and role onboarding. Sign-in is only via MeerID (a single OIDC SSO): there is no email/password/OTP. The base mechanisms and the HMAC signature are described on the Authentication page.
POST /auth/meerid/exchange
/api/v1/auth/meerid/exchange🔒 —First session: exchange the result of MeerID authorization for a local JWT pair. Supports two flows — popup/redirect (via code + PKCE) and FedCM (via idToken). Pass the fields of exactly one flow.
codestringoptionalcodeVerifierstringoptionalredirectUristringoptionalidTokenstringoptionalnoncestringoptional{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
"token_type": "bearer",
"expires_in": 900,
"meerid_id_token": "eyJhbGciOiJSUzI1NiIs...",
"user": {
"user_id": 1042,
"email": "creator@example.com",
"display_name": "Anya",
"is_affiliate": true,
"affiliate_id": 88,
"memberships": [
{ "tenant_id": 17, "role": "merchant_owner" }
]
}
}expires_in is the access token TTL in seconds (900 = 15 minutes). meerid_id_token is needed by the BFF as the id_token_hint when logging out via MeerID (popup flow only; FedCM does not issue it). Limit: 20 requests per minute per IP — see Rate limits.
Errors: MEERID_TOKEN_ERROR (401), MEERID_IDTOKEN_INVALID (401), MEERID_IDTOKEN_NONCE (401), MEERID_ACCESS_DENIED (403 — ban/deletion), MEERID_USERINFO_ERROR (401), MEERID_UNAVAILABLE (502), RATE_LIMIT_EXCEEDED (429).
POST /auth/refresh
/api/v1/auth/refresh🔒 —Refresh-token rotation: returns a new pair, and the old refresh is marked as used (single-use, with a grace window for the race of parallel refreshes). If the user has a linked MeerID refresh, the platform checks it in parallel and, on a ban/account deletion, revokes all local sessions.
refresh_tokenstringrequired{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 900
}Errors: TOKEN_WRONG_TYPE (401), TOKEN_REVOKED (401), TOKEN_EXPIRED (401), TOKEN_INVALID (401), ACCESS_REVOKED (401 — the MeerID account is blocked/deleted).
Refresh is single-use
After a successful refresh, the old refresh token is no longer valid — save the new one. A repeated request with an already-used token within the short grace window returns the same fresh pair; outside the window — TOKEN_REVOKED.
POST /auth/logout
/api/v1/auth/logout🔒 Bearer JWTRevoke the current session (a specific refresh JTI). The access token lives out its TTL. Idempotent: an invalid/already-revoked token still returns success.
refresh_tokenstringrequired{ "message": "Logged out" }POST /auth/logout-all
/api/v1/auth/logout-all🔒 Bearer JWTRevoke all of the user's sessions. Requires a full (not embed-scoped) token.
{ "message": "Sessions revoked: 3" }Errors: EMBED_SCOPE_DENIED (403 — called by a scoped embed token).
POST /auth/token-exchange
/api/v1/auth/token-exchange🔒 HMACS2S exchange (RFC 8693-style): the business server exchanges its user's external identifier for a scoped affiliate token for the embed-SDK. Authentication is the tenant_api_key signature with the sso scope. The tenant_id is taken only from the key in the database; the field of the same name in the body is ignored (anti-IDOR).
subject_tokenstringrequiredsubject_token_typestringoptionalexternal_user_id.grant_typestringoptionalurn:ietf:params:oauth:grant-type:token-exchange.subject_profileobjectoptionalemail, display_name, locale). Untrusted — applied only on first user creation.{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 600,
"scope": "affiliate:embed",
"affiliate_id": 88,
"tenant_id": 17
}The scoped token (scope: affiliate:embed, TTL 10 minutes) grants read-only access to the data of its own affiliate_id in the Affiliate group; profile/KYC mutations and payout methods through it are forbidden.
Errors: TENANT_API_KEY_INVALID (401), TENANT_API_KEY_SCOPE_DENIED (403), EXCHANGE_DENIED (401), EXCHANGE_REPLAY (401), USER_BANNED (403), SERVICE_UNAVAILABLE (503).
POST /auth/affiliate/activate
/api/v1/auth/affiliate/activate🔒 Bearer JWTThe explicit "become a partner" step: creates an affiliate profile for the current user. Idempotent — a repeated call returns the same profile. Requires a full Bearer token.
ref_codestringoptional{ "message": "Affiliate profile activated (affiliate_id=88)" }Errors: UNAUTHORIZED (401), EMBED_SCOPE_DENIED (403).
POST /auth/merchant/create-business
/api/v1/auth/merchant/create-business🔒 Bearer JWTAtomic business registration: creates a tenant + merchant account + zero escrow balance + a merchant_owner membership in a single transaction. The identity is taken from the Bearer token (no email/OTP). Requires a full token.
company_namestringrequiredtenant_slugstringrequired^[a-z0-9][a-z0-9_-]{1,63}$ (lowercase Latin letters, digits, _, -).default_currencystringoptionalRUB.display_namestringoptional{
"tenant_id": 17,
"tenant_slug": "acme",
"merchant_account_id": 21,
"role": "merchant_owner",
"message": "Business created."
}Errors: TENANT_SLUG_TAKEN (409 — slug taken), VALIDATION_ERROR (422), USER_NOT_FOUND (404).
One account — many businesses
The same MeerID user can own several businesses. If you have multiple tenants, specify the active one when calling the Merchant API via the X-Tenant-Id header.