---
title: "Authentication & API keys"
description: "Every pirouter request carries one API key. This page covers which header carries the key, how keys are created, expire and are revoked, and what a key cannot do today."
canonical: "https://pirouter.ai/docs/authentication"
last-updated: "2026-10-13"
lang: "en"
source: "content/docs/en/(start)/authentication.mdx"
---

# Authentication & API keys

Every pirouter request carries one API key. This page covers which header carries the key, how keys are created, expire and are revoked, and what a key cannot do today.

Every request needs a pirouter API key. The same key works on the endpoints of all three protocols: OpenAI, Anthropic and Gemini.

## Send the key with a request

The gateway reads the key in this order:

1. The `Authorization` header first. The standard form is `Authorization: Bearer $PIROUTER_API_KEY`. The bare key without the `Bearer ` prefix is accepted too.
2. The `X-Api-Key` header, when `Authorization` has no value.

The value must start with `sk-pi-`, and the prefix is case-sensitive. If `Authorization` holds a value that is not a pirouter key, the request is refused. The gateway does not go on to read `X-Api-Key`.

The three official SDKs send different headers by default:

| SDK              | Default header          | What to do                                                                                                                        |
| ---------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| OpenAI SDK       | `Authorization: Bearer` | Nothing                                                                                                                           |
| Anthropic SDK    | `x-api-key`             | Nothing                                                                                                                           |
| Google GenAI SDK | `x-goog-api-key`        | The gateway does not read this header. Add an `Authorization: Bearer` header when you create the client, or requests return `401` |

A request on the Anthropic protocol uses `x-api-key`:

cURL:

```bash
curl https://api.pirouter.ai/v1/messages \
  -H "x-api-key: $PIROUTER_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5.5",
    "max_tokens": 1024,
    "messages": [{ "role": "user", "content": "Hello!" }]
  }'
```

Python:

```python
import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://api.pirouter.ai",
    api_key=os.environ["PIROUTER_API_KEY"],
)

message = client.messages.create(
    model="anthropic/claude-sonnet-5.5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello!"}],
)
print(message.content[0].text)
```

TypeScript:

```ts
import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic({
  baseURL: 'https://api.pirouter.ai',
  apiKey: process.env.PIROUTER_API_KEY,
})

const message = await client.messages.create({
  model: 'anthropic/claude-sonnet-5.5',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Hello!' }],
})
console.log(message.content[0].text)
```

A request on the Gemini protocol sends the key in `Authorization`. With the Google GenAI SDK, add `Authorization: Bearer` through the `headers` of `http_options` (`httpOptions` in TypeScript) where you create the client, and keep passing the SDK's `api_key` parameter as usual:

cURL:

```bash
curl "https://api.pirouter.ai/v1beta/models/google/gemini-3.5-flash:generateContent" \
  -H "Authorization: Bearer $PIROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": [{ "parts": [{ "text": "Hello!" }] }]
  }'
```

Python:

```python
import os
from google import genai
from google.genai import types

client = genai.Client(
    api_key=os.environ["PIROUTER_API_KEY"],
    http_options=types.HttpOptions(
        base_url="https://api.pirouter.ai",
        headers={"Authorization": f"Bearer {os.environ['PIROUTER_API_KEY']}"},
    ),
)

response = client.models.generate_content(
    model="google/gemini-3.5-flash",
    contents="Hello!",
)
print(response.text)
```

TypeScript:

```ts
import { GoogleGenAI } from '@google/genai'

const ai = new GoogleGenAI({
  apiKey: process.env.PIROUTER_API_KEY,
  httpOptions: {
    baseUrl: 'https://api.pirouter.ai',
    headers: { Authorization: `Bearer ${process.env.PIROUTER_API_KEY}` },
  },
})

const response = await ai.models.generateContent({
  model: 'google/gemini-3.5-flash',
  contents: 'Hello!',
})
console.log(response.text)
```

With only the SDK's default header, requests return `401`.

## Create a key

1. **Open API Keys**

   Sign in to the console, open **API Keys** and click **Create key**.

   - [Open the console](https://app.pirouter.ai): app.pirouter.ai

   Only workspace owners can create, rename and revoke keys. Other members can only view the list. You are the owner of the personal workspace that was created when you signed up.

2. **Name it and pick an expiration**

   The name is 1 to 100 characters. There are five expiration options: Never, 30 days, 60 days, 90 days and 1 year. The default is Never.

3. **Save the key**

   You should see a key that starts with `sk-pi-`. It is shown this one time. Copy it into an environment variable or your secret manager:

   ```bash
   export PIROUTER_API_KEY="sk-pi-..."
   ```

   After you close the dialog, the list shows only the last four characters of the key.

## Expiration, renaming and revocation

- **Expiration**: once the expiration passes, the key stops working by itself. Requests return `401` with the error code `api_key_expired`. The expiration is set when the key is created and cannot be changed afterwards.
- **Renaming**: only the name changes. The key itself stays the same, and programs using it are not affected.
- **Revocation**: takes effect immediately and cannot be undone. Requests sent with the key afterwards return `401` with the error code `api_key_revoked`.

## If a key leaks

1. Revoke the key in **API Keys**.
2. Create a new key and replace the environment variable in your programs.
3. Open **Logs**, filter by API key, and check for requests you did not send while the key was exposed.

## When authentication fails

A failed authentication always returns `401`. The `error.code` in the response body tells you why:

| `error.code`        | Cause                                                      | What to do                                    |
| ------------------- | ---------------------------------------------------------- | --------------------------------------------- |
| `invalid_api_key`   | No key was sent, or the value does not start with `sk-pi-` | Check the header and the environment variable |
| `api_key_not_found` | The format is right, but no such key exists                | Check that the whole key was copied           |
| `api_key_revoked`   | The key was revoked                                        | Create a new key                              |
| `api_key_expired`   | The key is past its expiration                             | Create a new key                              |

The endpoints of all three protocols return the same error shape:

```json
{
  "error": {
    "message": "This API key is no longer active. It was revoked or has expired; create a new key in your pirouter console and use that instead.",
    "type": "authentication_error",
    "code": "api_key_revoked"
  }
}
```

Branch on `error.code` in your code. Do not match on the text of `message`.

## What keys cannot do today

A key is created with a name and an expiration, and nothing else. These do not exist today:

- **A spending limit per key.** One key can spend the whole balance of its workspace.
- **A permission scope per key.** A key cannot be restricted to certain models or endpoints.
- **A rate limit configured per key.**

When you need isolation, create one key per environment or service. You can then revoke each one separately and see each one's usage in Logs.
