---
id: docs.api-keys
title: "API keys"
canonical: https://docs.secureprocessing.app/api-keys
last_verified: 2026-11-06
---

# API keys

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

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.

> **Note:** 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.

| Prefix | Reaches | Available |
|---|---|---|
| `sk_test_` | Test data, sandbox cards | Immediately |
| `sk_live_` | Real cardholders, real settlement | Once 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.

> **Warning:** 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.
