# Authentication

> How Soca API keys work: Bearer secret keys with sk_test_ and sk_live_ prefixes, strict mode scoping, key hygiene, 401 behavior, and per-endpoint rate limits.

Part of the Soca documentation (https://soca.finance/docs). Human version: https://soca.finance/docs/developers/authentication
Every request to the Soca API is authenticated with a **secret key** sent as a Bearer token:

```bash
curl https://soca.to/v1/payments \
  -H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"
```

Create and revoke keys in **Dashboard → Developers**. A key identifies your merchant account and its mode. There is nothing else to configure.

## Test and live keys

Keys come in two prefixes: `sk_test_` and `sk_live_`. The prefix is the mode. Everything a key creates and reads is scoped to that key's merchant **and** mode. Test invoices, payments, customers, meters, and events are fully isolated from live ones, and there is no way to reach across. A create request whose key mode does not match the environment it is sent to is rejected with `400 mode_mismatch`.

## Key hygiene

- **Server-side only.** A secret key grants full API access to your account. Never ship it in a browser, mobile app, or public repository. Keep it in server environment variables.
- **Shown once.** The full key value is displayed a single time at creation. Soca stores only a hash. It can never be shown again. If you lose it, create a new key.
- **Revocable.** Revoke any key instantly from **Dashboard → Developers**. Requests with a revoked key fail with `401 unauthorized` immediately.
- **Rotate by overlap.** Create the new key, deploy it, then revoke the old one with no downtime.

## 401 behavior

A missing, malformed, invalid, or revoked key returns `401` with the standard error envelope:

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or revoked API key."
  }
}
```

The header must be exactly `Authorization: Bearer sk_test_...` or `Authorization: Bearer sk_live_...`. Anything else (a missing `Bearer` prefix, an unrecognized token shape, an empty header) is treated as missing and gets the same `401 unauthorized` response.

## Rate limits

Limits are applied per caller over a sliding 60-second window, bucketed by endpoint class. When you exceed one, the API returns `429` with code `rate_limited` and a `retry-after` header (in seconds).

| Endpoints | Limit |
| --- | --- |
| `GET /v1/{resource}` list endpoints (payments, subscriptions, invoices, events, meters) | 120 requests / min |
| `POST /v1/invoices`, `POST /v1/plans`, `POST /v1/credit_grants`, `POST /v1/entitlement_grants` | 60 requests / min |
| `POST /v1/credit_debits` | 120 requests / min |
| `POST /v1/meter-events` (usage ingestion) | 600 requests / min |
| `GET /v1/customers/{id}/entitlements/{feature_key}` (access checks) | 600 requests / min |
| `GET /v1/customers/{id}/credit_balance` | 240 requests / min |
| `POST /api/mcp` (MCP server) | 120 requests / min |

> The high-volume paths, usage ingestion and entitlement checks, get 5x the standard read limit on purpose, so metering a busy product never competes with your other API traffic. Batching meter events (up to 500 per request) stretches the ingestion budget further; see the [API reference](/docs/developers/api-reference).

On `429`, wait for the `retry-after` interval and retry. Practical handling advice is in [Errors and testing](/docs/developers/errors-and-testing).
