Skip to main content
Vellaro has three levels of credentials, with clear boundaries.

1. Publishable key: identifies the store

  • What it does: tells the API which store. Like Stripe’s publishable keys, it can live in the client (web/mobile).
  • What it does NOT do: it does not authenticate a user and is rejected on the /admin/* endpoints (403 api_key.admin_route_forbidden). The blast radius of a leaked key is limited to the storefront.
  • Format: cx_pk_<live|test>_<slug>_<random>. Use test only in development.
  • It can have origin restrictions and can be rotated or revoked by the operator. A wrong or revoked key: 401 api_key.invalid; an origin that is not allowed: 403 api_key.origin_not_allowed.
If your calls originate from the store’s domain, the key is optional: the store is resolved from the domain. It’s needed for mobile apps and server-side clients without a domain.

2. Customer session: shopper account

For account areas (orders, addresses, wishlist, subscriptions) the customer logs in and gets a token pair:
Registration and login: POST /auth/register, POST /auth/login (with 2FA if enabled), POST /auth/refresh, POST /auth/logout, Google and Apple social login when the store has enabled them (GET /auth/social-providers). See the API Reference.
Customer accounts belong to a store: the same email can have an account in different stores. Registration and login must therefore happen in the store’s context, that is from its domain or with the X-Vellaro-Key header.

Rotating refresh

Every login opens a session. On every POST /auth/refresh the refresh token you present is superseded and the response carries a new one: always store it in place of the old one, together with the new access token.
How the API answers depending on the token it receives: Refresh tokens issued before the October 10, 2026 release are valid once: on first use they open a new session and move to the new scheme, so the release signs nobody out. Each account keeps at most 20 sessions: beyond that, the oldest one is closed. In the client:
  1. On a 401 from any call, call POST /auth/refresh, store the new pair, and retry the request.
  2. Run one refresh at a time: requests that get a 401 at the same moment wait for the same refresh.
  3. On a 401 from /auth/refresh (auth.session_revoked, auth.refresh_token_reused, auth.refresh_token_invalid) or on a 403 auth.tenant_unavailable, clear the tokens and send the user to the login screen.
  4. On network errors or 5xx, do not drop the session: try again later.
Don’t share the same refresh token across processes or devices. For a staff account, two processes refreshing the same token more than 60 seconds apart close each other’s session. For long-running automations, use an API token.

Logout

  • POST /auth/logout with {"refresh_token": "..."} in the body closes this session: from then on that refresh token answers 401 auth.session_revoked. It’s idempotent and doesn’t need the access token. The access token stays valid until it expires: drop it in the client.
  • POST /auth/logout?all=true closes all the account’s sessions, on every device (“sign out everywhere”). It needs a valid credential: the Authorization: Bearer <access_token> header, or the refresh token in the body if the access token has already expired. Without one: 401 auth.not_authenticated.
After a revocation, every token issued before it answers 401 auth.session_revoked: back to login. Sessions are also revoked by a password reset, a password change (see the note below), enabling and disabling 2FA, and the store deactivating the account.
Calls that revoke sessions from the user’s device return new tokens for that device: adopt them right away, otherwise the next call gets a 401 auth.session_revoked. This applies to POST /auth/change-password (always revokes), to POST /auth/2fa/verify on first activation, and to POST /auth/2fa/disable. POST /users/me/change-password revokes and returns tokens only if you send the X-Session-Rotation: 1 header; without it, it answers as before and closes no session.

Two-factor authentication (2FA)

  1. Activation: POST /auth/2fa/setup with {"current_password": "..."} returns secret and qr_uri to show as a QR code. Then POST /auth/2fa/verify with {"code": "123456", "current_password": "..."} enables 2FA, closes the other sessions, and answers with new tokens plus 10 recovery codes (recovery_codes).
  2. Recovery codes: they look like xxxxx-xxxxx, are valid once, and must be shown to the user only once, in the activation response. Each one replaces the app code at login and to disable 2FA.
  3. Login: with 2FA enabled, the first POST /auth/login answers 401 auth.totp_required. Repeat the same call with totp_code, which accepts the app code or a recovery code. Each TOTP code is valid once: the same code presented again gives 401 auth.totp_invalid.
  4. Regeneration: POST /auth/2fa/recovery-codes with {"current_password": "..."} returns 10 new codes; the old ones stop working. GET /auth/me exposes recovery_codes_remaining (null while 2FA is off): when it gets close to 0, prompt the user to regenerate them.
  5. Deactivation: POST /auth/2fa/disable with {"code": "..."} (app code or recovery code). It closes the other sessions, clears the recovery codes, and returns new tokens.
GET /auth/2fa/setup is deprecated: it no longer creates a secret. Typical 2FA errors: 400 auth.current_password_wrong, auth.no_password_set (social-only account), auth.totp_already_enabled, auth.totp_not_enabled, auth.totp_invalid.

Deleted store

If the account’s store no longer exists:
  • POST /auth/login (with the right password) and POST /auth/refresh answer 403 auth.tenant_unavailable;
  • any other authenticated call with a token of that store answers 401 auth.tenant_unavailable.
Don’t retry the refresh: clear the tokens and show a clear message.

3. Staff token: Admin API

The /admin/* endpoints require a staff account (JWT) with the right permissions (catalog, sales, customers, marketing, content, reviews, reports, tax, shipping, system, users).
  • Login is the same POST /auth/login, with a staff user’s credentials. The same rotation rules apply, with the strict reuse rule described above.
  • The token carries the tenant_id claim: every session is bound to its store (leak-proof multi-tenant isolation).
  • For automations and long-running agents there is the API token: it’s used as a Bearer, with no login or refresh, and has a mandatory expiry. See Manage your store via API.
  • Admin mutations are audit-logged.
  • Publishable keys never grant access here.
Never embed a staff token in a public client (web/mobile). The Admin API is for backends, server-side integrations, and automations (including the admin MCP).

Common auth error codes

The /auth/* endpoints have per-IP limits (login 10 per minute, refresh 30 per minute, 2FA and password change 5 or 10 per minute). Beyond the limit the response is a 429, without the standard envelope: {"detail": "Rate limit exceeded: ..."}.