MMeerPartners docs

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

POST/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.

codestringoptional
Authorization code from the MeerID popup/redirect flow. Required for popup.
codeVerifierstringoptional
PKCE code_verifier. Required for popup.
redirectUristringoptional
The redirect_uri used during authorization. Required for popup.
idTokenstringoptional
ID token from the FedCM flow. Required for FedCM.
noncestringoptional
The nonce associated with the FedCM request. Required for FedCM.
200 response
{
  "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

POST/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
A valid refresh token (at least 10 characters).
200 response
{
  "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

POST/api/v1/auth/logout🔒 Bearer JWT

Revoke 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
The refresh token of the session being closed.
200 response
{ "message": "Logged out" }

POST /auth/logout-all

POST/api/v1/auth/logout-all🔒 Bearer JWT

Revoke all of the user's sessions. Requires a full (not embed-scoped) token.

200 response
{ "message": "Sessions revoked: 3" }

Errors: EMBED_SCOPE_DENIED (403 — called by a scoped embed token).

POST /auth/token-exchange

POST/api/v1/auth/token-exchange🔒 HMAC

S2S 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_tokenstringrequired
The user's external identifier in the business system (1–255 characters). Stored as an opaque string.
subject_token_typestringoptional
The identifier type. Defaults to external_user_id.
grant_typestringoptional
Reserved: urn:ietf:params:oauth:grant-type:token-exchange.
subject_profileobjectoptional
Optional profile (email, display_name, locale). Untrusted — applied only on first user creation.
200 response
{
  "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

POST/api/v1/auth/affiliate/activate🔒 Bearer JWT

The 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
Optional referral code of the inviter (up to 16 characters).
200 response
{ "message": "Affiliate profile activated (affiliate_id=88)" }

Errors: UNAUTHORIZED (401), EMBED_SCOPE_DENIED (403).

POST /auth/merchant/create-business

POST/api/v1/auth/merchant/create-business🔒 Bearer JWT

Atomic 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_namestringrequired
Company name (1–160 characters).
tenant_slugstringrequired
A unique business identifier. Pattern ^[a-z0-9][a-z0-9_-]{1,63}$ (lowercase Latin letters, digits, _, -).
default_currencystringoptional
Default currency, ISO code. Defaults to RUB.
display_namestringoptional
Display name (up to 160 characters).
201 response
{
  "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.

What's next