Authentication
Every server-side request must send a secret API key in the Authorization header or the X-Merchant-API-Key header. Keys come in pairs: a public key (pk_live_*) and a
secret key (sk_live_*). Only the secret is sent; the
public key is an internal label.
Creating a key
Open the cabinet at /cabinet/merchant/api-keys, give it a
label, choose scopes, and (optionally) lock it to a CIDR allowlist.
The secret is shown exactly once on creation. Store
it in your server secret manager. We cannot recover it.
Scopes
Every endpoint declares the scope it needs. Issue narrow keys per integration role.
| Scope | What it unlocks |
|---|---|
invoices.write | POST /invoices, cancel |
invoices.read | GET /invoices, /invoices/:id |
payouts.write | POST /payouts |
payouts.read | GET payouts |
refunds.write | POST /refunds |
refunds.read | GET refunds |
balance.read | GET /balance, /balance/ledger |
webhooks.write | Manage webhooks (cabinet only) |
Header forms
Authorization: Bearer sk_live_abcdef...
# or
X-Merchant-API-Key: sk_live_abcdef...Idempotency
Every POST / PUT that creates or mutates
state requires the Idempotency-Key header. Send a
UUIDv4 per logical operation. Retrying with the same key returns
the same response without doing the work twice.
Rate limits
Per-key. Default 60 requests / minute. Configurable up
to 6000 via the cabinet. Exceeded requests get 429 with a Retry-After header.
IP allowlist
Per-key. Comma-separated CIDRs (e.g. 10.0.0.0/8) or
single IPs. Empty = no restriction. Production servers should
always pin to a known IP — a leaked secret then cannot be used from
an attacker's network.
Brute-force lockout
5 consecutive bad-secret attempts on the same public key lock that key for 1 hour. The lockout is per-key, not per-source.