---
title: Authentication
description: Authenticate every request with a Lovely Router API key, sent in whichever header your SDK already uses.
---

Every request that uses your balance needs an API key. One key works across the whole API: Claude, GPT and Gemini models, data tools, the browser agent and the MCP server.

## Get an API key

Create a key under [API keys](/app/keys) in the dashboard. A key looks like `lr-` followed by 40 letters and digits. It is shown once, when you create it. Lovely Router stores only a hash of the key, so a lost key can't be recovered: revoke it and create a new one.

Keep the key in an environment variable. Every example in these docs reads it from `$LOVELY_API_KEY`.

```bash
export LOVELY_API_KEY="lr-..."
```

To limit what a key can spend or call, see [API keys](/docs/account/api-keys).

## Send the key

Lovely Router accepts the key in any of the places the vendor APIs use. All four work on every endpoint, so keep whatever your SDK or tool sends by default.

| Where | Example | Sent by |
| --- | --- | --- |
| `Authorization` header | `Authorization: Bearer lr-...` | OpenAI SDK, Claude Code (`ANTHROPIC_AUTH_TOKEN`), data tools, browser agent, MCP clients |
| `x-api-key` header | `x-api-key: lr-...` | Anthropic SDK |
| `x-goog-api-key` header | `x-goog-api-key: lr-...` | Google Gen AI SDK, Gemini CLI |
| `key` query parameter | `?key=lr-...` | Gemini REST examples |

If a request carries more than one, the first match wins in this order: `Authorization: Bearer`, `x-api-key`, `x-goog-api-key`, `?key=`.

> [!TIP] Prefer headers
> A key in the query string ends up in proxy logs, shell history and browser history. Use `?key=` only for tools that can't send a header.

## Check that a key works

Counting tokens needs a valid key but costs nothing, which makes it a quick check. A `200` means the key is active and allowed to use the model.

```bash title="cURL"
curl https://lovelyrouter.com/v1/messages/count_tokens \
  -H "x-api-key: $LOVELY_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-5.5",
    "messages": [{"role": "user", "content": "ping"}]
  }'
```

```python title="Python"
import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://lovelyrouter.com",
    api_key=os.environ["LOVELY_API_KEY"],
)
count = client.messages.count_tokens(
    model="claude-sonnet-5.5",
    messages=[{"role": "user", "content": "ping"}],
)
print(count.input_tokens)
```

```js title="Node.js"
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({
  baseURL: 'https://lovelyrouter.com',
  apiKey: process.env.LOVELY_API_KEY,
});
const count = await client.messages.countTokens({
  model: 'claude-sonnet-5.5',
  messages: [{ role: 'user', content: 'ping' }],
});
console.log(count.input_tokens);
```

## Endpoints that don't need a key

Catalog endpoints are public, so you can browse models and tools before you sign up. A key sent to them is ignored, except by the planner, which uses it for a higher limit.

| Endpoint | Returns |
| --- | --- |
| `GET /v1/models` | Every available model with context size, prices and channel groups |
| `GET /v1beta/models` | The same list in Gemini format |
| `GET /v1/tools` | The data tool catalog |
| `GET /v1/tools/{id}` | One tool's input schema, example input and price |
| `POST /v1/tools/discover` | Tools matching a keyword query |
| `POST /v1/tools/suggest` | A step-by-step tool plan for a task. Without a key, all anonymous callers share 100 requests per minute; with a key you get 100 per minute for that key. |

Everything else needs a key: model requests (including token counting), tool runs, every `/v1/browser` endpoint, and running tools over MCP.

## Authentication errors

A missing, unknown, revoked or expired key returns `401`. The body uses the format of the API you called.

| Cause | Message |
| --- | --- |
| No key on a model request | `Missing API key` |
| Unknown or revoked key, or a suspended account | `Invalid or revoked API key` |
| Key past its expiry date | `This API key has expired` |
| No key, or an unknown or revoked key, on `/v1/tools` or `/v1/browser` | `Missing, invalid or revoked API key` |

```json title="Anthropic"
{"type": "error", "error": {"type": "authentication_error", "message": "Invalid or revoked API key"}}
```

```json title="OpenAI"
{"error": {"message": "Invalid or revoked API key", "type": "authentication_error", "code": "authentication_error"}}
```

```json title="Gemini"
{"error": {"code": 401, "message": "Invalid or revoked API key", "status": "UNAUTHENTICATED"}}
```

```json title="Tools and browser"
{"error": {"type": "authentication_error", "message": "Missing, invalid or revoked API key"}}
```

A valid key can still be refused for what it's allowed to do: `403` for a model outside its allow-list, `402` when its spend limit is reached, `429` when it hits its rate limit. See [What happens when a limit is hit](/docs/account/api-keys#what-happens-when-a-limit-is-hit) and [Errors](/docs/api/errors).

## Keep keys safe

- **Keep keys on the server.** Never put a key in a web page, mobile app or anything else you ship to users. The API accepts requests from any origin, so a key in front-end code can be copied and spent by anyone who opens it.
- **Load keys from the environment or a secrets manager.** Don't commit them. Add `.env` files to `.gitignore`.
- **Use one key per app and environment.** A leaked staging key then can't touch production, and [Logs](/app/logs) show which key made each request.
- **Cap what each key can do.** Set a spend limit, a model allow-list and an expiry date on keys used by agents, CI jobs or other people. See [API keys](/docs/account/api-keys).
- **Rotate keys regularly**, and right away when someone with access leaves. See [Rotate a key](/docs/account/api-keys#rotate-a-key).

> [!WARNING] If a key leaks
> Revoke it under [API keys](/app/keys) right away. Revocation applies to the next request. Then create a replacement and check [Logs](/app/logs) for traffic you don't recognise.
