Keys and test mode
Every request is authenticated with a static API key — no OAuth flow, no token refresh.
X-Api-Key: YOUR_API_KEYKeys are created and revoked by an org admin from Settings → API & Webhooks. A key is scoped to the org it was created in — there is no cross-org access. Passtastic keys are full-access across all six scopes below; there is currently no scope picker in the UI.
| Scope | Grants |
|---|---|
customers:read | GET a customer's status/balance, or look one up by email/phone. |
customers:write | Enroll customers, or PATCH an existing one's identifier/custom fields. |
loyalty:write | Earn, redeem, and adjust balances. |
cards:read | List your org's cards and their ids. |
cards:write | Change a card's customer identifier question (live key only). |
webhooks:manage | Create, list, update, delete webhook endpoints; view and replay deliveries. |
A key missing a required scope gets a 403 with a body like { "message": "Missing scope(s): loyalty:write" }.
Test and live keys
A test key starts with psk_test_, a live key with psk_live_. A key created before test mode shipped has no mode segment at all (psk_<hex>) — treat it as live. Test keys are available on every plan, including your 14-day trial, and never open the upgrade gate; live keys need Multi-Location or above.
Card ids are identical in test and live — you enroll against the exact same cardId your merchant uses in production. Passtastic mirrors it into a private, per-org sandbox behind the scenes, so a test key never touches real customers or sends a real webhook to anyone but you. Every pass a test key creates carries a TEST · prefix on its name, so it's obviously not a real customer's card.
Test mode holds up to 100 test customers per account, and each one is removed 30 days after it was created — enrolling a new customer past that cap returns 409 TEST_CUSTOMER_LIMIT (an existing test customer can still be matched and updated once the account is at the cap). When a test customer is removed, the wallet pass already installed on a tester's phone simply stops updating.
{
"statusCode": 409,
"error": "TEST_CUSTOMER_LIMIT",
"message": "TEST_CUSTOMER_LIMIT: Test mode holds up to 100 customers. Test customers are removed 30 days after they were created."
}Card settings — PATCH /v1/cards/{cardId} — always need a live key: test mode reads the live card's own settings, so there is nothing in test mode to change. Calling it with a test key returns 400 NOT_AVAILABLE_IN_TEST_MODE.
Every webhook delivery carries "livemode": true or false in its envelope, so one endpoint can tell live traffic from test traffic without a separate URL — see Webhooks.
Idempotency
POST /v1/customers, PATCH /v1/customers/{ref}, .../earn, .../redeem, and .../adjust all accept an optional Idempotency-Key header. Retry the same request with the same key (per org + endpoint) and you get back the first response instead of double-applying the write — safe to retry on a network timeout.
Idempotency-Key: order-4821-earnBase URL https://api.passtastic.io. Every path on this page is prefixed with /api (e.g. https://api.passtastic.io/api/v1/customers).