StrimzStrimz

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:

Authorization: Bearer <privy-access-token>

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:

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx

Keys come in two kinds and two modes:

KindModePrefixUse
SecretTestsk_test_server-side, test environment
SecretLivesk_live_server-side, production
PublishableTestpk_test_browser, test environment
PublishableLivepk_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:

RouteUsed 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}/subscriptionhosted checkout, StrimzBrowserClient.checkout
GET /v1/checkout/plans/{id}/termshosted checkout, StrimzBrowserClient.checkout
POST /v1/checkout/sessions/{id}/payerhosted checkout
POST /v1/checkout/plans/{id}/payerhosted checkout
GET /v1/tokens/{address}StrimzBrowserClient.tokens
GET /v1/tokens/{address}/permit-nonceStrimzBrowserClient.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):

agents_read           agents_write
analytics_read
api_keys_read         api_keys_write
customers_read        customers_write
invoices_read         invoices_write
merchants_read
refunds_read          refunds_write
relay_read            relay_write
sessions_read         sessions_write
storefronts_read      storefronts_write
subscriptions_read    subscriptions_write
transactions_read
webhooks_read         webhooks_write

What the less obvious scopes unlock:

ScopeRoutes
merchants_readGET /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_readGET /v1/customers, GET /v1/customers/{id}
customers_writePOST /v1/customers
analytics_readGET /v1/stats/summary, /volume, /conversion, /churn, /mrr, /ltv, /forecast
storefronts_readGET /v1/storefront, GET /v1/storefront/products, GET /v1/storefront/products/{id}
storefronts_writePOST /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/me
  • POST /v1/merchants/me/onboard
  • GET /v1/notifications
  • POST /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_found for 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:

  1. Email verification. Privy verifies your email address.
  2. Two-factor authentication. Enabled in your Privy account settings.
  3. Business onboarding. The self-attested form in the dashboard (business name, sector, country, payout address).
  4. 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 403 with code live_mode_unavailable, whatever your eligibility.
  • On the live deployment, creating or rotating a test key returns 403 with code mode_unavailable.
  • On either deployment, a request made with a key of the other mode returns 403 with code mode_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.

await strimz.apiKeys.revoke('cmq4wwd3p000fje6l1k9s5tqx')
const newKey = await strimz.apiKeys.create({ name: 'production', kind: 'secret', scopes: [...], mode: 'live' })
console.log(newKey.secret)  // shown once

If a key is exposed in a public repo, revoke it straight away and email support@strimz.finance.

On this page