Quick Start
Make a first request: get your key, send one message and read the reply. The API speaks the OpenAI and Anthropic formats, so the SDK you already use works after you change its base URL and key.
1. Get your key
Every account has one API key. Sign in, open Keys & usage and copy it. The key works on every endpoint and every model. Keys & usage
2. Point your client at the API
All endpoints live under one base URL. An OpenAI SDK takes it with /v1 at the end; an Anthropic SDK takes it without, because it adds /v1/messages itself.
| Client | Base URL |
|---|---|
| OpenAI SDKs and OpenAI-compatible clients | https://api.shannon-ai.com/v1 |
| Anthropic SDKs | https://api.shannon-ai.com |
Three endpoints produce text. They reach the same models; choose the one whose format your code already uses.
| Endpoint | Format |
|---|---|
POST /v1/chat/completions | OpenAI Chat Completions |
POST /v1/messages | Anthropic Messages |
POST /v1/responses | OpenAI Responses |
3. Send a first request
Send one user message to shannon-3 on the Chat Completions endpoint. Replace YOUR_API_KEY with your key.
POST https://api.shannon-ai.com/v1/chat/completions
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!"}
],
max_tokens=1024
)
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!' }
],
max_tokens: 1024
});
console.log(response.choices[0].message.content); package main
import (
"context"
"fmt"
openai "github.com/sashabaranov/go-openai"
)
func main() {
config := openai.DefaultConfig("YOUR_API_KEY")
config.BaseURL = "https://api.shannon-ai.com/v1"
client := openai.NewClientWithConfig(config)
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: "shannon-3",
Messages: []openai.ChatCompletionMessage{
{Role: "user", Content: "Hello, Shannon!"},
},
MaxTokens: 1024,
},
)
if err != nil {
panic(err)
}
fmt.Println(resp.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!"}
],
"max_tokens": 1024
}' | Field | Type | Default | Description |
|---|---|---|---|
model | string | The id of the model that answers. The ids are listed on Models & pricing. | |
messages | array | The conversation so far: objects with role and content. The last user message is the prompt. Required. | |
max_tokens | integer | 4096 | The output budget of the request, from 1 to 65,536. The request reserves this amount from your balance and is charged for what it really used. |
stream | boolean | false | true sends the answer piece by piece while it is written. |
The request body is JSON and needs the header Content-Type: application/json. The full list of fields is on the endpoint page. Chat Completions
The reply
A call without stream returns one JSON object when the answer is complete. The texts and numbers below are examples.
{
"id": "chatcmpl-9b2f6c1e4a7d4f0f8a3c5d2e1b6a7c90",
"object": "chat.completion",
"created": 1791590400,
"model": "shannon-3",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! What would you like to work on?",
"reasoning_content": "A greeting. A short, friendly reply fits."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 1386,
"completion_tokens": 74,
"total_tokens": 1460
}
} | Field | Type | Description |
|---|---|---|
id | string | chatcmpl- followed by 32 hexadecimal characters. |
object | string | Always chat.completion. |
created | integer | The time of the reply in Unix seconds. |
model | string | The published id of the model that answered, also when you sent an alias or another spelling. |
choices | array | Always one entry, with index 0. |
choices[0].message.role | string | Always assistant. |
choices[0].message.content | string | null | The answer. It can be null when the reply carries tool_calls. |
choices[0].message.reasoning_content | string | null | The reasoning of the model before its answer, or null when there is none. |
choices[0].message.tool_calls | array | Present only when the model calls one of the tools you declared. |
choices[0].finish_reason | string | stop for an answer, tool_calls when the model calls a tool. |
usage.prompt_tokens | integer | The tokens the model read for this answer. On shannon-2-* and the Shannon 3 family this can be more than the text you sent. |
usage.completion_tokens | integer | The tokens the model wrote. |
usage.total_tokens | integer | The total. The charge of a Shannon model is computed from this number. |
-
usageholds token counts, not money. What the call took from your balance is shown per request on Keys & usage; how it is computed is on Limits and balance. Limits and balance - Every reply, also an error and a stream, has the header
x-request-id. If your request carries anx-request-idheader, its value comes back; otherwise the value is 12 hexadecimal characters. Keep it in your logs: it names this one request.
The same call as a stream
Set stream to true to read the answer while it is written. Everything else in the request stays the same.
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com/v1"
)
stream = client.chat.completions.create(
model="shannon-3",
messages=[
{"role": "user", "content": "Hello, Shannon!"}
],
max_tokens=1024,
stream=True
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True) import OpenAI from 'openai';
const client = new OpenAI({
apiKey: 'YOUR_API_KEY',
baseURL: 'https://api.shannon-ai.com/v1'
});
const stream = await client.chat.completions.create({
model: 'shannon-3',
messages: [
{ role: 'user', content: 'Hello, Shannon!' }
],
max_tokens: 1024,
stream: true
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
} curl -N -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!"}
],
"max_tokens": 1024,
"stream": true
}' Each frame of the stream is one line that starts with data: and holds a JSON object. One frame, printed on several lines:
{
"id": "chatcmpl-9b2f6c1e4a7d4f0f8a3c5d2e1b6a7c90",
"object": "chat.completion.chunk",
"created": 1791590400,
"model": "shannon-3",
"choices": [
{
"index": 0,
"delta": {
"content": "Hello! "
},
"finish_reason": null
}
]
} | Frames, in order | delta | finish_reason |
|---|---|---|
| While the model reasons. Zero or more frames. | {"reasoning_content": "…"} | null |
| The answer, piece by piece. Zero or more frames. | {"content": "…"} | null |
The last JSON frame. It also carries usage; you do not have to ask for it. | {} | "stop" |
The end of the stream: the line data: [DONE]. |
- The reply has status
200andcontent-type: text/event-stream. Every frame is a linedata: <json>followed by an empty line. - A line
: pingarrives every 15 seconds. It is a comment that keeps the connection open; SSE clients skip it. - A failure after the stream has opened arrives as a frame
data: {"error": {…}}. The status stays200, so check the frames.
If the call fails
An error is a JSON object with an error that holds type and message. These are the replies a first call most often meets.
| Status | Type | Message | What it means |
|---|---|---|---|
401 | authentication_error | Missing authentication | No key reached the API. Send Authorization: Bearer followed by your key. |
401 | authentication_error | Invalid API key | The key is not the current key of an account. Copy it again from Keys & usage. |
400 | invalid_request_error | unknown model: <id> | model is not a published id. Use an id from Models & pricing. |
422 | invalid_request_error | Failed to deserialize the JSON body into the target type… | A field has the wrong JSON type, or messages is missing. |
429 | rate_limit_error | Quota exceeded. Upgrade your plan at shannon-ai.com/plan | The output budget of the request does not fit into your balance. |
Where to go next
- Models & pricing — Pick a model: ids, context windows and prices.
- Chat Completions — Every request field, reply field and stream event of the endpoint you called.
- Streaming — The frames of a stream, keep-alive lines and errors inside a stream.
- Function Calling — Let the model call your functions.
- Limits and balance — What a request reserves and costs, and how many requests you may send.
- API Playground — Try requests in the browser with your key.