URL: /docs/api/authentication

---
title: Authentication
description: Bearer API keys — format, plan resolution, per-plan key limits, and what a 401 means.
---

Every request carries an API key as a bearer token. There is no anonymous access to any endpoint on this API, and no other credential: the REST API is key-only. (The [MCP endpoint](/docs/mcp/authentication) accepts the same keys *and* OAuth.)

```http
Authorization: Bearer rvd_<prefix>_<secret>
```

## Create a key

Keys are free on **every** plan, including free. Create one at [getrevised.com/account](https://getrevised.com/account).

The secret half is shown once, at creation, and is stored only as `sha256(secret + pepper)` with a server-side pepper. Keep it somewhere you can read it back — Revised cannot.

```bash
curl "https://getrevised.com/api/v1/domains?limit=2" \
  -H "Authorization: Bearer rvd_yourprefix_yoursecret"
```

## Token format

A token is three parts joined by underscores.

| Part | Example | What it is |
| --- | --- | --- |
| `rvd` | `rvd` | A fixed, greppable marker, so a key pasted into a repository or a log is findable by secret scanners. |
| prefix | `a1b2c3d4e5f6` | 12 lowercase alphanumeric characters. Public, stored in the clear, and the only thing authentication looks the row up by. It appears in `GET /api/v1/me` as `key.prefix` and on the account page. |
| secret | 43 URL-safe characters | 32 bytes of entropy. Never stored; only `sha256(secret + pepper)` is. |

A token that is not shaped like this is rejected without a database lookup.

## A key carries no entitlement of its own

The key identifies the account. The **owner's current plan** is resolved behind it on every request and refreshed at least once a minute, so a subscription that lapses, upgrades or downgrades moves your rate limit, your reveal budget and your access to the `since` feed **without anything being reissued**. A lapsed subscription reads as `plan: "free"` on `GET /api/v1/me` while the key itself keeps working.

Two consequences worth designing for:

- Do not cache a plan. Read `GET /api/v1/me` when you need the current one.
- Do not treat a `402` as permanent. It may be an allowance that rolls over, or a plan that changed under the key.

## Keys per account

| Plan | Live keys |
| --- | --- |
| Free | 2 |
| Pro | 5 |
| Business | 10 |

Revoked keys never count against the ceiling. Source: `PLAN_LIMITS` in the application.

Separate keys for separate systems separate the rate-limit bookkeeping and nothing else. Holds, reveals and the reveal ledger are **account-scoped**: a hold taken by one key is visible to, extendable by and releasable by every other key on the account.

## The same key authenticates MCP

`https://getrevised.com/api/mcp` accepts the same `rvd_` bearer token, and REST calls and MCP calls come out of **one** per-minute counter. See [MCP authentication](/docs/mcp/authentication) for the OAuth alternative, which needs no key at all.

## When authentication fails

Missing, malformed, unknown, revoked and mistyped keys are all `401`:

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Provide your API key as `Authorization: Bearer rvd_...`. Keys are created at /account."
  }
}
```

A `401` also carries `WWW-Authenticate: Bearer realm="revised"`.

Missing and invalid keys share one message deliberately — the API does not confirm whether a prefix exists.

Failed authentications are counted against a separate budget keyed by client IP. Over it, the answer is `429` `rate_limited` with `Too many failed key attempts. Retry in Ns.`, raised before any lookup. Authenticated traffic never touches that guard; only a failure spends from it.

Do not retry a `401` with the same key. Check that:

- The header is `Authorization: Bearer <token>` — one space, one token. The scheme is compared case-insensitively.
- Nothing has trimmed or wrapped the secret. The full token is `rvd_` plus 12 characters plus an underscore plus the secret.
- The key has not been revoked on the [account page](https://getrevised.com/account).

## Handling keys

- Send keys from a server, not from a browser. The API sets `Access-Control-Allow-Origin: *` because there is no cookie or session to protect — a key on a page you do not control is the one thing that policy does expose.
- Keep the secret in an environment variable or a secret store. The examples throughout these docs read `$REVISED_API_KEY`.
- A key that has leaked is revoked on the account page; revoking frees the slot immediately.

See [Errors](/docs/api/errors) for the full status code table.
