Skip to content
On this page

Developers › Building on the API

API keys

Two sets of books, one kind of key, and what to do when one gets out.

Verified 6 November 2026Explain this to your team →

View as Markdown

A secret key authenticates every request. Send it as a bearer token:

bash
curl https://api.secureprocessing.app/v1/payment_intents \
  -H "Authorization: Bearer sk_test_..."

There is no publishable key and nothing you can safely put in a browser. Every call goes from your server to ours, because every call either moves money or reads it.

A payment intent carries a client_secret. It is deprecated and has no effect: nothing in the API accepts it, because the card is entered on the bank’s own page and there is no browser-side step for it to authorise. It is still returned so nothing that reads it breaks. Do not build on it.

Test and live

The prefix is the whole story. A sk_test_ key reaches test data and the bank’s sandbox; a sk_live_ key reaches real money. They do not see each other’s customers, payments, events or webhook endpoints, and a key cannot be pointed at the other set by any header or parameter.

PrefixReachesAvailable
sk_test_Test data, sandbox cardsImmediately
sk_live_Real cardholders, real settlementOnce your business is verified

Live keys are issued only after we have verified your business with the bank. Until then the dashboard will not create one, and asking for one through the API returns an error rather than a key that would fail on first use.

Keys are managed from the dashboard, not the API

Creating, rolling and revoking keys all live under Developers → API keys in the dashboard, and a request to those endpoints carrying an API key is refused with permission_error. That is on purpose: a credential that can mint more credentials is a credential whose theft has no floor, and it means revoking a leaked key is something the thief cannot undo.

Shown once

A key appears in full exactly once, in the response that creates it. We store only a hash, so we genuinely cannot show it again — not as a policy, as a fact. If you lose it, roll it.

Rolling, and revoking

Rolling issues a replacement and gives the old key an expiry rather than killing it outright. Both work until the old one expires — 24 hours by default, up to 72, or 0 for an immediate cutover. The overlap exists because revoking the instant a new key is issued breaks the running integration between issuing and deploying, which is a self-inflicted outage during exactly the operation people perform because they are worried.

Revoking is immediate and has no overlap. That is the right tool when a key is already in the wrong hands: a few minutes of failed requests is cheaper than anything the alternative buys. Roll when you are being careful; revoke when you are being robbed.

A secret key can create payments, issue refunds and read every payment you have ever taken. It belongs in your server’s secret store, never in a repository, a browser bundle, a mobile app or a log line. Our error reports redact anything that looks like one.

When a key is refused

A wrong, revoked or wrong-mode key returns 401 with authentication_error. An unauthenticated request to any /v1 path returns the same 401 even when the path does not exist, deliberately: nobody should be able to map the API one guess at a time without a key. With a valid key, an unknown path returns 404 and unknown_endpoint.

json
{
  "error": {
    "type": "authentication_error",
    "code": "api_key_invalid",
    "message": "No valid API key provided",
    "request_id": "req_naWpf7JTWdMBAori"
  }
}

Quote the request_id when you ask us about a request. Every response has one, including the ones that went right: it is the X-Request-Id response header, and on an error it is also in the body, as above — except on an idempotent replay, where the body is the original response and carries the original request’s id. You may send your own X-Request-Id — 1 to 64 letters, digits, _ or - — and we will use it and echo it back, so your logs and ours share one id.