Authentication
How requests authenticate against Strimz, and how to manage keys + scopes.
The Strimz API has two clients with very different needs: the dashboard, which runs in a merchant's browser, and your server-side code. Each has its own authentication scheme.
Dashboard sessions (Privy access tokens)
When a merchant logs in to the dashboard, Privy handles the auth flow (email, wallet, or social login). The dashboard then sends Privy's access token along with every API request:
The Strimz API verifies the token with Privy's server-side library, finds
the merchant by privyUserId, and attaches them to the request. The very
first time a new merchant logs in, you'll see a merchant_not_synced
error. The dashboard handles this automatically by calling
POST /v1/auth/sync to create the merchant row, then retrying the
original request.
You usually don't need to think about any of this. If you're building a custom admin tool, the browser SDK handles the token round-trip for you.
SDK requests (API keys)
Every server-side integration uses a Strimz API key:
Keys come in two kinds and two modes:
| Kind | Mode | Prefix | Use |
|---|---|---|---|
| Secret | Test | sk_test_ | server-side, test environment |
| Secret | Live | sk_live_ | server-side, production |
| Publishable | Test | pk_test_ | browser, test environment |
| Publishable | Live | pk_live_ | browser, production |
Secret keys must never reach the browser. If one leaks, revoke it and create a new one.
Publishable keys are safe in the browser because they grant no
access. StrimzBrowserClient and @strimz/sdk-react read the mode
(test or live) from the key's prefix and send it with their calls to
the public checkout and token routes, which do not check it. Every
other route rejects a publishable key with 401 authentication_error.
A publishable key cannot create a payment session: create sessions on
your server with a secret key, as shown in
Create sessions on your server.
Calling Strimz from a browser
Browser code may call these routes from any website. They answer with
Access-Control-Allow-Origin: * and never with credentials:
| Route | Used by |
|---|---|
GET /v1/checkout/merchants/{id} | hosted checkout, StrimzBrowserClient.checkout |
GET /v1/checkout/sessions/{id} | hosted checkout, StrimzBrowserClient.checkout, useStrimzSession |
GET /v1/checkout/plans/{id} | hosted checkout, StrimzBrowserClient.checkout |
GET /v1/checkout/plans/{id}/subscription | hosted checkout, StrimzBrowserClient.checkout |
GET /v1/checkout/plans/{id}/terms | hosted checkout, StrimzBrowserClient.checkout |
POST /v1/checkout/sessions/{id}/payer | hosted checkout |
POST /v1/checkout/plans/{id}/payer | hosted checkout |
GET /v1/tokens/{address} | StrimzBrowserClient.tokens |
GET /v1/tokens/{address}/permit-nonce | StrimzBrowserClient.tokens |
Every other route refuses browsers on any origin other than the Strimz
dashboard: the CORS preflight fails and the request never reaches
Strimz. Call those routes from your server with a secret key. If you
see a CORS error from api.strimz.finance, read
Seeing a CORS error?.
Scopes
Every secret key has a scope list. When you issue a new key from the dashboard, you tick the actions it can take. The API enforces the list on every request. If your key has subscriptions_read but tries to call POST /v1/subscriptions/{id}/cancel, you'll get a permission_denied error. A route that does not name a scope rejects every API key.
Available scopes (underscore-separated, alphabetical):
What the less obvious scopes unlock:
| Scope | Routes |
|---|---|
merchants_read | GET /v1/merchants/me, GET /v1/merchants/me/live-mode-eligibility, GET /v1/merchants/me/chain-status, GET /v1/merchants/me/onchain-state, GET /v1/merchants/me/balance |
customers_read | GET /v1/customers, GET /v1/customers/{id} |
customers_write | POST /v1/customers |
analytics_read | GET /v1/stats/summary, /volume, /conversion, /churn, /mrr, /ltv, /forecast |
storefronts_read | GET /v1/storefront, GET /v1/storefront/products, GET /v1/storefront/products/{id} |
storefronts_write | POST /v1/storefront, /publish, /archive, POST /v1/storefront/products, POST /v1/storefront/products/{id}/archive |
The dashboard offers preset scope bundles ("Full access", "Read-only", "Sessions only") so you don't have to tick every box for every key. You can always issue a more permissive key later and revoke the narrow one.
Dashboard-only routes
These routes change your profile, where your money goes, or the dashboard itself. They accept only a dashboard session and return permission_denied to every API key, whatever its scopes:
PATCH /v1/merchants/mePOST /v1/merchants/me/onboardGET /v1/notificationsPOST /v1/notifications/mark-all-read
strimz.merchants.update() calls the first of these, so it does not work with a secret key. It was removed from @strimz/sdk in 0.8.0, together with strimz.merchants.changeTier(). Change your profile and payout address in the dashboard. Strimz sets your pricing tier; it cannot be changed from the dashboard or the API.
Keys that manage keys
A key with api_keys_write can create, rotate and revoke keys, within limits:
- A key can only grant scopes it holds itself, and only in its own mode. A test key cannot create a live key.
- A key only sees keys of its own mode. A test key gets
not_foundfor a live key, on read, revoke and rotate. - A key can only rotate a key whose scopes it holds itself.
A dashboard session manages keys of both modes with any scopes.
Live-mode eligibility
You can't issue a live-mode key the moment you sign up. Live-mode unlocks once you've completed:
- Email verification. Privy verifies your email address.
- Two-factor authentication. Enabled in your Privy account settings.
- Business onboarding. The self-attested form in the dashboard (business name, sector, country, payout address).
- Payout address. A payout wallet address is set on your account.
The dashboard checks GET /v1/merchants/me/live-mode-eligibility,
which returns structured reasons so the UI can point you at the
right step. There is no manual review queue on the basic plan.
The API checks the same list when a live key is created or rotated. If a check fails, it returns 403 with code live_mode_ineligible and the same reasons in error.details.reasons, and no key is written. Existing live keys keep working if a check later fails; the list is checked again on the next create or rotate.
Each Strimz deployment runs in one mode. The sandbox runs on Arc testnet and accepts test keys only. The live deployment runs on Arc mainnet and accepts live keys only.
- On the sandbox, creating or rotating a live key returns
403with codelive_mode_unavailable, whatever your eligibility. - On the live deployment, creating or rotating a test key returns
403with codemode_unavailable. - On either deployment, a request made with a key of the other mode returns
403with codemode_unavailable. Use the sandbox's keys against the sandbox and live keys against the live API.
Rotating a key
Revoke and re-issue at any time. The old key starts returning
authentication_error immediately. Strimz never hard-deletes a key;
it stays in your audit log as revoked so you can prove later when
it stopped working.
If a key is exposed in a public repo, revoke it straight away and email support@strimz.finance.

