Authentication
Soren Pay uses workspace-scoped API keys prefixed apk_. Every authenticated
request carries one in the Authorization: Bearer … header.
Header format
curl https://api.sorenpay.com/api/treasury \
-H "authorization: Bearer apk_<your key>"Token lifecycle
| Action | Where |
|---|---|
| Create | Dashboard → Developers → API keys → "Create key" |
| Reveal | Once on creation. Store immediately. |
| List | GET /api/auth/keys |
| Rotate | Create a new key → switch traffic → revoke the old one |
| Revoke | DELETE /api/auth/keys?id=… or dashboard |
The full token is shown only at creation. If you lose it, you must create a new key and rotate.
Scopes
Every key carries an explicit list of scopes, and every endpoint checks the
one it needs (403 missing_scope:<scope> otherwise). * can no longer be
issued (keys minted before that still work until revoked — rotate them), and a
key with no scopes can do nothing. A write: scope does not imply the matching
read: scope: a key that quotes and then originates payments needs both
read:treasury and write:treasury. Scope each key to its exact role — an
agent's key typically needs read:agents + write:agents, plus read:cards
if it inspects its cards.
| Scope | What it grants |
|---|---|
| read:treasury | Balances, ledger, ramp sessions, intl payments, quotes |
| write:treasury | Deposits, on/off-ramp sessions, originate intl payments |
| read:cards | List cards + lookup by id, list cardholders (DOB / SSN last-4 are never returned), physical cards |
| write:cards | Mint, update limits, freeze + terminate cards; register cardholders |
| read:agents | List agents + view intent log |
| write:agents | Register agents + submit intents |
| read:checkout | View checkout sessions |
| write:checkout | Create checkout sessions |
| read:api_keys | List this workspace's keys |
| write:api_keys | Create/revoke other keys |
| read:webhooks / write:webhooks | Reserved for outbound webhook endpoints (no endpoint uses them yet) |
Rate limits
The default per-key limit is 100 requests / minute sliding window. Hit the
ceiling and you'll get HTTP 429 with x-ratelimit-reset-ms telling you when to retry.
For higher limits, contact us — we have per-key overrides in the database.
Idempotency
All POST and PATCH endpoints accept an Idempotency-Key header. Use a UUIDv4:
curl -X POST https://api.sorenpay.com/api/cards \
-H "authorization: Bearer apk_<your key>" \
-H "idempotency-key: $(uuidgen)" \
-H "content-type: application/json" \
-d '{...}'Replays within 24h return the cached response. Conflicting bodies on the same
key return HTTP 422 idempotency_key_mismatch so you can detect bugs.
KYB gating
In live mode, money-movement routes (cards, intl payments, ramp) require
the workspace's kyb_status === 'approved'. Sandbox skips this gate so you can
build against the full API surface before keys arrive.
Webhook signing (inbound)
Every webhook we receive carries a provider-specific signature header. We verify
HMAC-SHA256 over the raw body with a ±5min replay window. Dedup is enforced
by a unique (provider, event_id) index on webhooks_inbound.
Webhook signing (outbound)
When you register an outbound endpoint we generate a signing secret. Each
delivery carries Soren-Signature: t=<unix>,v1=<hmac> so you can verify on
your side. See Notifications.