<!-- source: https://platform.scriptivox.com/docs/authentication -->
<!-- title: Authentication | Scriptivox API -->

# Authentication

Every Scriptivox API request is authenticated with an API key. There is no session login, no OAuth flow, and no cookie — a key is the only credential the API accepts.

**Base URL:** `https://api.scriptivox.com/v1`

---

## Get a key

Create one at [platform.scriptivox.com/keys](/keys). Keys look like `sk_live_` followed by 48 hexadecimal characters.

The full key value is shown **once**, at creation. Store it immediately — the dashboard keeps only a prefix afterwards so you can tell your keys apart, and there is no way to recover a lost key. Create a replacement and revoke the old one instead.

> **An API key is not a web subscription:** 
> Scriptivox is two products, billed separately. A subscription on [scriptivox.com](https://www.scriptivox.com) grants nothing to the API, and API usage draws down a prepaid balance you top up at [/billing](/billing) — $0.20 per hour of audio. Even on Pro, `POST /v1/transcribe` returns `402 ZERO_BALANCE` at $0.

## Send the key

Two headers are accepted. They are equivalent — pick one:

```http
Authorization: sk_live_a1b2c3...
```

```http
X-Api-Key: sk_live_a1b2c3...
```

A `Bearer ` prefix on `Authorization` is accepted but not required, so `Authorization: Bearer sk_live_…` works too. Header names are case-insensitive.

```bash
curl https://api.scriptivox.com/v1/balance \
  -H "Authorization: sk_live_YOUR_KEY"
```

If both headers are present, `X-Api-Key` wins.

## Limits on keys

Each account may hold **at most 5 active keys**. If you hit the ceiling, revoke an unused key before creating another.

Rate limits are enforced per key per endpoint, plus a per-IP cap across all endpoints. Every response — including the `401` you get with no key at all — carries `RateLimit-Policy`, so you can read the limits before you have a working key. See [Rate Limits](/docs/api-reference#rate-limits).

## Rotating a key

Keys have no expiry, so rotation is something you choose to do rather than something forced on you:

1. Create the new key at [/keys](/keys).
2. Deploy it. Both keys are live and both draw on the same account balance, so there is no cutover window to coordinate.
3. Revoke the old key.

Revocation takes effect immediately. The gateway caches *malformed* keys for five minutes to blunt brute-force attempts, but a well-formed `sk_live_` key that starts returning `401` is never cached — a transient backend blip must not brick a real customer's key for five minutes, so every request re-checks. The cost of that choice is one extra lookup per failure; the benefit is that revoking a leaked key is instant.

## When authentication fails

| Status | Code | What happened |
| --- | --- | --- |
| `401` | `INVALID_API_KEY` | No key, a malformed key, or a key that does not exist. |
| `401` | `API_KEY_REVOKED` | The key was valid and has since been revoked. |
| `402` | `ZERO_BALANCE` | The key is fine. The account balance is $0 — add credit at [/billing](/billing). |
| `429` | `RATE_LIMIT_EXCEEDED` | Valid key, too many requests. Honour `Retry-After`. |

Repeated authentication failures from one IP earn a progressive block — 60 seconds, then 5 minutes, then 30 minutes. The block returns `429 RATE_LIMIT_EXCEEDED` with a `Retry-After` header rather than another `401`, so a client retrying a bad key in a tight loop will start seeing 429s. Fix the key rather than retrying.

Every error body is the same shape, with a stable machine-readable code:

```json
{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "Invalid API key.",
    "docs_url": "https://platform.scriptivox.com/docs/api-reference#error-codes"
  }
}
```

The full list is in [Error Codes](/docs/api-reference#error-codes).

## Keeping a key secret

- **Never ship a key to a browser or a mobile app.** Anything a user can run, a user can read. Call the API from your own server and expose your own endpoint to the client.
- **Never commit one.** Read it from the environment: `SCRIPTIVOX_API_KEY` is the variable name the [CLI](/docs/cli) and the [MCP server](/docs/mcp) both use.
- **A leaked key spends real money.** Revoke it at [/keys](/keys) first, investigate second.

Webhook payloads are signed with HMAC-SHA256 keyed on your API key, so the key doubles as the shared secret for verifying callbacks — see [Webhooks](/docs/webhooks).
