API keys
Every request carries a key as a bearer token: Authorization: Bearer sticky_live_…. Keys are created on the Developers page of the admin (under Advanced in the sidebar) with New API key, shown once, and stored only as a hash. A key belongs to one programme and one mode:
sticky_test_…writes to the programme's sandbox — see Test mode.sticky_live_…writes to the real programme.
Keys issued before the rename (cvos_live_…, cvos_test_…) keep working forever.
Scopes
A key carries one or more of four scopes. Give each integration the least it needs.
| Scope | Lets the key… |
|---|---|
ingest | Send events, and read back the status of the events it sent. |
read | Read members, ledgers, redemptions, reservations, the programme, liability, programme versions and export jobs; run simulations; mint member session tokens; ask for redemption quotes. |
act | Enrol, resolve, update and erase members; adjust balances; reserve, redeem, confirm, void, release; apply referrals; mint portal links; queue and download exports. |
webhooks | Manage webhook endpoints. |
Every operation in the reference states its scope as x-required-scope. A key without it gets 403 forbidden with the missing scope in detail. Two answers are about the programme, not the key: 403 with reason: plan_restricted means the programme's plan does not include the API, and 402 payment-required means the programme has no active plan at all.
A live key with act or ingest can change balances, and one with read or webhooks can reach members' personal details, so only a programme owner or admin can create or rotate a live key, and only within a few minutes of signing in. When the programme asks for second approval, creating one is a request, not a key: nothing exists until a second owner or admin approves it under Approvals. Then the person who asked goes back to Developers, presses Create the approved key within 7 days, and sees the key once — nobody else ever does. (An owner or admin with nobody else to approve creates the key directly, on a fresh sign-in.) Rotating an existing key needs no second approval. Test keys, with any scope, can be created by anyone with access to Developers.
Adjusting balances with a key
POST /v1/members/{id}/adjust (scope act) follows the same rules as a person in the admin:
- Approval limit. An adjustment over the programme's approval limit — or any debit, when debits need approval — is not posted. You get
202withdata.status: "pending_approval"anddata.approval_request_id, and a teammate decides it under Approvals. Resend the sameIdempotency-Keyto see its current status (pending_approval,approved,rejectedorcancelled);balance.updatedfires if it is approved and posted. If nobody but the key's creator could approve it, the call is refused with403andreason: approval_required— make it in the admin instead. - Daily credit cap. Credits through one key (and its rotated replacements) are capped per UTC day: the programme's own setting, else 20 × its approval limit, else 10,000 in the programme's currency (
1000000). Over the cap you get429withreason: daily_credit_cap,cap_cents,used_cents,resets_atand aRetry-Afterthat runs to midnight UTC. Debits are never capped. - No negative balances. A debit larger than the member's spendable balance is
422 balance-insufficient, and nothing is written. A plan that cannot add liability answers credits with409andreason: plan_restricted.
Which key for what
| You are building… | Scopes |
|---|---|
| A store that sends orders and shows balances | ingest, read |
| …that also redeems at checkout or enrols customers | add act |
| A till that issues and confirms vouchers | read, act |
| A reporting sync | read |
| A webhook receiver's configuration script | webhooks |
Rotation and revocation
Rotate mints a new key with the same scopes; the old one keeps working for 24 hours so you can deploy the new one first. A key already rotated can't be rotated again (rotate its replacement), and the replacement shares the old key's daily credit cap and its adjust Idempotency-Keys. Revoke stops a key immediately, with no grace — use it for a leaked key; adjustments it left waiting for approval are cancelled.
Test vs live in one integration
Read the mode from GET /v1/ping (data.mode) rather than parsing the prefix, and never let a test key's data reach live code paths: the sandbox is a separate programme with its own members.
Member session tokens are not API keys
A browser or app never holds an API key. Your server mints a member session token (POST /v1/members/{id}/session-tokens, scope read) — read-only, one member, 15 minutes — and the page uses it against GET /v1/public/member only. See A mobile app.