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 |

One-time reveal

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.