# Authentication

Every `/v1` route requires a Bearer key.

```http
Authorization: Bearer fds_live_<kid>_<secret>
```

The value is the word `Bearer`, one space, then the key. Do not send the key twice. Do not put `Bearer ` inside the key.

Public (no auth):

- `GET https://api.fulldatasets.com/health`
- `GET https://api.fulldatasets.com/openapi.json`

MCP uses the same header on `https://mcp.fulldatasets.com/mcp`.

## Key format

```
fds_live_<kid>_<secret>
```

| Part | Meaning |
|---|---|
| `fds_live` | Environment prefix (v1 is live only) |
| `kid` | Key id. Used for rate limits and revocation lookup |
| `secret` | Secret. Hashed at rest; treat it like a password |

Keys are issued by hand. [Request one](https://fulldatasets.com/#signup). There is no self-serve dashboard in v1.

## Failures

| Status | When |
|---|---|
| `401` | Missing header, malformed key, unknown `kid`, or revoked |
| `429` | More than **120 requests / 60 seconds** for this `kid` |

Error body:

```json
{ "error": { "code": "rate_limited", "message": "…" } }
```

See [Errors](/docs/errors).

## Usage

Each authenticated `/v1` call is metered asynchronously (API → queue → Postgres). Do not treat usage rows as a synchronous response field. `/health` and `/openapi.json` are not billed.

## MCP

Cursor (and any streamable-HTTP client):

```json
{
  "mcpServers": {
    "fulldatasets": {
      "url": "https://mcp.fulldatasets.com/mcp",
      "headers": {
        "Authorization": "Bearer fds_live_<kid>_<secret>"
      }
    }
  }
}
```

If initialize fails with 401, the header is wrong — not the tool names.
