Authentication
All API requests require authentication via an API key passed as a Bearer token in the standard Authorization HTTP header.
Bearer token
Include your API key in every request:
Code
Keys come in two formats, distinguished by prefix:
| Prefix | Workspace mode | Use for |
|---|---|---|
sk_live_<64-hex> | live | Production traffic. Real money, real invoices. |
sk_test_<64-hex> | sandbox | Sandbox testing. No real charges; isolated from live data. |
Each key is bound to one organization and one workspace. The organization ID and workspace ID are derived from the key.
Treat keys as secrets. Never commit them to source control or expose them in browser-side code.
Authentication errors
When authentication fails, the API returns an application/problem+json error document (per RFC 9457). See Errors for the full envelope shape.
| HTTP status | code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Authorization header missing, scheme is not Bearer, token is empty, key not found, expired or revoked. |
| 403 | FORBIDDEN | Key has an IP allowlist and the request IP is not in it. |
| 403 | ORGANIZATION_SIGN_IN_HELD | A hold on the organization stops its integrations. |
| 403 | ORGANIZATION_CLOSED | The organization has been closed; its API keys are refused. |
| 500 | INTERNAL_ERROR | The key lookup itself failed (database/infrastructure error). |
These checks run before any endpoint logic, so these statuses can come back from any endpoint.
Managing API keys
Keys are created, listed, rotated, and revoked through the API key endpoints. The plaintext key value is only returned once, at creation or rotation time - store it immediately. See API keys for choosing a key's access.
List keys
Code
Returns metadata only (id, key_prefix, last4, key_type, name, access, scopes, allowed_ips, expires_at, status, created_at, last_used_at, …). The plaintext key is never retrievable after creation.
Create a key
Code
Request body:
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | required | Human-readable label, at most 80 characters. |
access_preset | "READ_ONLY" | "FULL_ACCESS" | - | A named grant expanded against the permissions you hold. Exactly one of access_preset and scopes is required. |
scopes | string[] | - | Explicit permission codes, each one you hold (GET /v1/permissions lists them). Exactly one of access_preset and scopes is required; an empty list is not all-access. |
key_type | "sk_live" | "sk_test" | the workspace's mode | Omit it. A value that disagrees with the workspace's mode is refused (400). |
allowed_ips | string[] | [] (no restriction) | CIDRs or single IPs. Requests from outside return 403. |
expires_at | RFC 3339 timestamp | omitted (non-expiring) | Must be in the future. |
Response (201 Created) returns the plaintext key in the key field. This is the only time it appears.
Code
Rotate a key
Code
Rotates one key: issues a new key with exactly that key's name, scopes, IP allow-list and expiry, and puts that key alone on a 24-hour grace expiry. Both work during the grace window. Every scope the key carries must be one you hold (403 otherwise), and a key already expiring within the grace window is refused with 409. Response (201) carries id, new_key (plaintext, only chance to capture it), last4, old_key_expiry (RFC 3339), and a human message.
Revoke a key
Code
Response (200) returns { "data": { "status": "revoked" } }. Returns 404 if the id is unknown or the key was already revoked.
Workspace context
Every key is bound to a single workspace. Workspaces have a mode field that drives behaviour:
live- real billing. Production keys (sk_live_...) belong here.sandbox- isolated testing. Sandbox keys (sk_test_...) belong here. Sandbox test artifacts (test clocks, payment simulations) only work in sandbox.
The workspace and its mode are resolved from the key automatically. To work across workspaces, use separate keys.