Authentication
Every request carries your API key in a header. One key per account works on every endpoint and every model, in either of two header forms.
Header · OpenAI SDKs
Authorization: Bearer YOUR_API_KEY Header · Anthropic SDKs
x-api-key: YOUR_API_KEY Accepted headers
Both headers are read on every /v1 endpoint, whichever format the endpoint speaks. Send one of them.
| Header | Description |
|---|---|
Authorization: Bearer <key> | Checked first. Write Bearer with a capital B and one space before the key. OpenAI SDKs send this header. |
x-api-key: <key> | Read when the request has no Authorization: Bearer header. Anthropic SDKs send this header. |
With an OpenAI SDK, set the key and the base URL; the SDK sends Authorization: Bearer.
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com/v1"
)
response = client.chat.completions.create(
model="shannon-3",
messages=[
{"role": "user", "content": "Hello, Shannon!"}
]
)
print(response.choices[0].message.content) import OpenAI from 'openai';
const client = new OpenAI({
apiKey: 'YOUR_API_KEY',
baseURL: 'https://api.shannon-ai.com/v1'
});
const response = await client.chat.completions.create({
model: 'shannon-3',
messages: [
{ role: 'user', content: 'Hello, Shannon!' }
]
});
console.log(response.choices[0].message.content); curl -X POST "https://api.shannon-ai.com/v1/chat/completions" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "shannon-3",
"messages": [
{"role": "user", "content": "Hello, Shannon!"}
]
}' With an Anthropic SDK, set the key and the base URL without /v1; the SDK sends x-api-key.
import anthropic
client = anthropic.Anthropic(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com"
)
message = client.messages.create(
model="shannon-3",
max_tokens=1024,
messages=[
{"role": "user", "content": "Hello, Shannon!"}
]
)
print("".join(block.text for block in message.content if block.type == "text")) import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: 'YOUR_API_KEY',
baseURL: 'https://api.shannon-ai.com'
});
const message = await client.messages.create({
model: 'shannon-3',
max_tokens: 1024,
messages: [
{ role: 'user', content: 'Hello, Shannon!' }
]
});
console.log(message.content.filter((block) => block.type === 'text').map((block) => block.text).join('')); curl -X POST "https://api.shannon-ai.com/v1/messages" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "shannon-3",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello, Shannon!"}
]
}' Which endpoints need a key
| Endpoint | Key |
|---|---|
POST /v1/chat/completions | Required |
POST /v1/messages | Required |
POST /v1/responses | Required |
POST /v1/tokenize | Required |
POST /v1/messages/count_tokens | Required |
GET /v1/models | Not needed |
Your key
- A key is
sk-followed by 43 characters fromA–Z,a–z,0–9,-and_: 46 characters in total. - Every account has exactly one key. It is created the first time you open Keys & usage.
- The key gives full access to the API for your account: every endpoint, every model and the whole balance.
- A key has no expiry date. It works until you rotate or revoke it.
Rotate and revoke
You rotate and revoke the key on the Keys & usage page. Keys & usage
| Action | What happens |
|---|---|
| Rotate | A new key is issued and the old key stops working. The new key works at once. |
| Revoke | The key stops working and no new key is issued. Rotate to get a working key again. |
- Both take effect at once: the next request with the old key is answered with
401. Treat 60 seconds as the upper bound before the old key is refused everywhere. - Every client of the account uses the same key, so a rotation reaches all of them. Put the new key into your clients right after you rotate.
- Balance, usage and flood protection belong to the account, not to the key. A new key continues with the same balance and the same request count for the current minute.
Errors
Every authentication failure has status 401 and type authentication_error. The message tells the cases apart.
| Status | Type | Message | When |
|---|---|---|---|
401 | authentication_error | Missing authentication | The request has neither an Authorization: Bearer header nor an x-api-key header that starts with sk-. This is also the reply to bearer in lower case and to an Authorization value without Bearer. |
401 | authentication_error | Invalid API key | The key is not the current key of an account: it is mistyped, it was revoked, or it was replaced by a rotation. |
{
"error": {
"type": "authentication_error",
"message": "Invalid API key",
"code": "invalid_api_key",
"param": null
}
} {
"type": "error",
"error": {
"type": "authentication_error",
"message": "Invalid API key"
}
} - On every path except
/v1/messagestheerrorobject also holdscode: "invalid_api_key", for a missing key as well, andparam: null. - The key is checked before the body is read. A request without a valid key gets
401even when its body is invalid or too large. - An
Authorization: Bearervalue that does not start withsk-is not treated as an API key. It gets401with a message that the token is invalid or expired.
Browsers and CORS
- The API answers cross-origin browser requests only for a fixed list of origins. JavaScript on a page of another origin cannot read
/v1replies. - Call the API from your server and let your page talk to your server. That also keeps the key out of the page source.
- Server-side code and command-line tools are not affected: this rule is applied by browsers only.
- A sign-in on shannon-ai.com does not authenticate
/v1requests. Cookies are not read there; the key is the only credential. - For an allowed origin, a browser may send
content-type,authorization,x-api-key,anthropic-version,anthropic-beta,openai-betaandx-request-id, and may read the reply headerx-request-id.
Keeping the key safe
- Keep the key on a server. Do not put it into web pages, mobile apps or public repositories: whoever holds it can spend your whole balance.
- Load the key from an environment variable or a secret store instead of writing it into code.
- If the key may have leaked, rotate it on Keys & usage. The old key stops working at once.
- After a leak, read the request list on Keys & usage. It shows every request the key made in the last 30 days.