Authentication - Kontorion Billing API
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, workspace ID, workspace mode, and scope set are all derived from the key - you never pass them in the request.
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, key revoked, or organization suspended. |
| 403 | FORBIDDEN | Key has an IP allowlist and the request IP is not in it. |
| 500 | INTERNAL_ERROR | The key lookup itself failed (database/infrastructure error). |
The middleware short-circuits before any handler runs, 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.
List keys
Code
Returns metadata only (id, prefix, type, name, scopes, allowed_ips, expires_at, created_at). The plaintext key is never retrievable after creation.
Create a key
Code
Request body (all optional except name):
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | required | Human-readable label. |
key_type | "sk_live" | "sk_test" | matches workspace mode | Override only if you know what you're doing. |
scopes | string[] | [] (full access) | Restrict the key to a subset of permission codes. |
allowed_ips | string[] | [] (no restriction) | CIDRs or single IPs. Requests from outside return 403. |
expires_at | RFC 3339 timestamp | null | null (non-expiring) | Optional automatic expiry. |
Response (201 Created) returns the plaintext key in the key field. This is the only time it appears.
Code
Rotate keys
Code
Generates a new key for the workspace and stamps a 24-hour expiry on existing keys. Both work during the grace window. Response (201) carries new_key (plaintext, only chance to capture it), 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. Test artifacts (test clocks, payment simulations) only work in sandbox.
The workspace and its mode are resolved from the key automatically - clients never pass a workspace ID in requests. To work across workspaces, use separate keys.
Test clocks (sandbox only)
In sandbox workspaces, test_clocks simulate the passage of time so you can exercise renewal, dunning, and trial-expiration logic without waiting. Outside sandbox, the endpoints return 403 FORBIDDEN.
Code
Request body:
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | "Default Clock" | Optional label. |
frozen_at | RFC 3339 timestamp | null | omitted | Initial pinned time. |
Response (201) returns the clock with fields id, workspace_id, name, frozen_at, status (active | advancing | completed), created_at, updated_at.
Code
The frozen_at field is required on advance. The advance triggers any time-dependent jobs (invoice runs, dunning steps, trial transitions) that would have fired between the previous and the new pinned time.
Other test-clock endpoints: GET /v1/test-clocks, GET /v1/test-clocks/{id}, DELETE /v1/test-clocks/{id}.
Payment simulations (sandbox only)
Pin specific outcomes onto a payment method so charges against it return that outcome instead of routing to a real gateway. Sandbox-only; live workspaces return 403 FORBIDDEN.
Code
| Field | Required | Notes |
|---|---|---|
payment_method_id | yes | UUID of an existing sandbox payment method. |
outcome | yes | One of: success, decline, insufficient_funds, expired_card, fraud, processing_error, network_error. |
Other endpoints: GET /v1/test-payment-simulations, DELETE /v1/test-payment-simulations/{id}.