Authentication

How CashSDK authenticates every request: the publishable and secret key types, what each one is allowed to do, and where to manage them.

CashSDK authenticates every request with a bearer token in the Authorization header.

bash
Authorization: Bearer csk_sk_live_...

Token types#

Publishable keycsk_pk_…

Safe to embed in client apps. Used by the SDKs to fetch offerings and start purchases. Cannot read customer data or perform privileged operations.

One per app, with no test/live split. Ship the same value in every build, TestFlight and Play internal testing included. See Test and live keys.

Secret keycsk_sk_live_… / csk_sk_test_…

Full server-side access. Used by your backend and the CLI. Never ship a secret key in a client app.

Restricted keycsk_rk_live_… / csk_rk_test_…

A secret key narrowed to the resources you pick: transactions:read, customers:read, and so on. Anything outside the list returns 403 insufficient_scope. Use one for a support tool, a dashboard widget or a vendor integration that has no business reading everything.

web_sessions:write (Create customer web sessions in the dashboard) lets your backend issue and revoke web customer sessions and open the billing portal for a customer. A web, Chrome extension or direct Mac backend needs it together with customers:read.

User-token secretcsk_uts_…

Not a key, and it never goes in an Authorization header. It is the HS256 secret your backend signs X-CashSDK-User-Token with, so CashSDK can tell which of your users a device request is for. Reveal it under App settings → API keys. One per app, no mode: your backend signs a token for a user, and it has no way to know whether that user is on a TestFlight build or the App Store release.

MCP tokencsk_mcp_…

The durable credential for the MCP server, and what you paste into Claude Code, Cursor or Codex. Workspace-scoped (or narrowed to one app), does not expire, and revocable from Settings → MCP. It can never do more than the member who created it, and billing, membership, SSO, store credentials and workspace deletion are refused outright.

Setup tokencsk_st_…

A scoped, short-lived token for onboarding and provisioning flows, handed out in the dashboard's integration prompt. One app, setup routes only, 24-hour expiry, and minting a new one revokes the last, so use an MCP token for anything that lives in a config file.

Treat secret keys like passwords. If one leaks, roll it from Settings → API keys, with cashsdk, or with the rotate_api_key MCP tool. Rolling issues the replacement immediately and lets you choose when the old value stops working (now, 1 hour, 24 hours, or 7 days), so a rotation does not have to be an outage.

Test and live keys#

Server keys come in pairs. Device keys do not.

KeyModesWhy
csk_sk_…, csk_rk_…test and liveRuns on your server, where you decide which data a call touches.
csk_pk_…, csk_uts_…noneShips in your app, where the store decides.

Server keys#

csk_sk_test_… reaches Sandbox data and csk_sk_live_… reaches Production data. Both are created with your app and sit side by side under the test/live tabs in App settings → API keys.

A server key cannot reach the other environment. If a caller asks for it (via ?environment= or X-CashSDK-Environment) it gets a 400 environment_mismatch rather than a quiet switch, because silently returning an empty Sandbox result to something that asked for Production looks exactly like your customers vanishing.

bash
# a test key asking for production data
curl https://api.cashsdk.com/v1/transactions?environment=Production \
  -H "Authorization: Bearer csk_sk_test_..."

{ "error": { "code": "environment_mismatch",
             "message": "this is a test key, which can only read Sandbox data..." } }

Pointing a staging server at real customers then takes deploying a visibly different key, which shows up in a config diff, instead of a flag nobody re-reads.

Your publishable key#

There is one csk_pk_… per app and it works in every environment. Put it in every build configuration you have.

That is not a shortcut, it is the only correct answer. TestFlight distributes the exact binary you submit to the App Store, and purchases made through TestFlight run in Apple's sandbox while App Store purchases run in production. Google Play internal testing works the same way. One key is compiled in for both, so a key pinned to one environment would lock out every user of the other, and there is no build setting that avoids it.

Sandbox and production data stay separate anyway, by something stronger than a key prefix:

  • On write, CashSDK reads the environment out of the store's own signed payload. A client cannot spoof it, and it is the same field whether the purchase came from your simulator, a TestFlight tester or a paying customer.
  • On read, the SDK asks for the environment the purchase resolved into. iOS takes it from Transaction.environment; Android takes it from the verify response, because Play Billing does not tell the client. Both send it as X-CashSDK-Environment.
  • Entitlements and consumable balances are resolved per environment, so a Sandbox purchase never grants Production access, and sandbox coins are never spendable in the App Store build, for the same user.

Verification itself is deliberately never refused on environment grounds: a real purchase submitted by a mis-built app is still a purchase your customer paid for, so it is recorded under the environment the store attests.

See Testing.

Base URLs#

bash
https://api.cashsdk.com
bash
https://mcp.cashsdk.com/mcp

All endpoints are versioned under /v1. The MCP hostname is the same service, see the MCP server.

Example request#

bash
curl https://api.cashsdk.com/v1/apps \
  -H "Authorization: Bearer csk_sk_..."

A missing or invalid token returns 401 Unauthorized. See Errors for the full list.