Wisper AI is an OpenAI-compatible gateway. It exposes a single /v1 surface and routes each request to a pluggable backend, so you keep using the OpenAI request and response shapes you already know.
Works with the official OpenAI SDKs and anything that speaks Chat Completions.
Server-sent events stream tokens as they’re generated, with optional usage accounting.
Full function-calling support and selectable reasoning effort per request.
All endpoints live under a single base URL. Point your OpenAI client at it and set your key.
https://ai.wisper.sh/v1
Set the base URL to https://ai.wisper.sh/v1 and your wsk- key, then call chat/completions exactly as you would with OpenAI.
curl https://ai.wisper.sh/v1/chat/completions \
-H "Authorization: Bearer $WISPER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5.5","messages":[{"role":"user","content":"Hello!"}]}'
from openai import OpenAI
client = OpenAI(base_url="https://ai.wisper.sh/v1", api_key="wsk-your-key")
resp = client.chat.completions.create(
model="gpt-5.5",
messages=[{"role": "user", "content": "Hello!"}],
)
print(resp.choices[0].message.content)
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://ai.wisper.sh/v1",
apiKey: "wsk-your-key",
});
const resp = await client.chat.completions.create({
model: "gpt-5.5",
messages: [{ role: "user", content: "Hello!" }],
});
console.log(resp.choices[0].message.content);
Every /v1 request requires an API key passed as a bearer token. Keys are prefixed with wsk-; only a hash of each key is stored.
Authorization: Bearer wsk-your-key-here
Need a key? Keys are issued by the operator. A missing key returns 401; an invalid or revoked key returns 403.
Only /health is reachable without a key — useful for uptime checks.
Copy-paste recipes in your language of choice. Set WISPER_API_KEY (or paste your wsk- key) and point the base URL at https://ai.wisper.sh/v1.
A single request and response — the “hello world” of the API.
curl https://ai.wisper.sh/v1/chat/completions \
-H "Authorization: Bearer $WISPER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"messages": [{"role": "user", "content": "Write a haiku about the sea."}]
}'from openai import OpenAI
client = OpenAI(base_url="https://ai.wisper.sh/v1", api_key="wsk-your-key")
resp = client.chat.completions.create(
model="gpt-5.5",
messages=[{"role": "user", "content": "Write a haiku about the sea."}],
)
print(resp.choices[0].message.content)import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://ai.wisper.sh/v1",
apiKey: "wsk-your-key",
});
const resp = await client.chat.completions.create({
model: "gpt-5.5",
messages: [{ role: "user", content: "Write a haiku about the sea." }],
});
console.log(resp.choices[0].message.content);package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]any{
"model": "gpt-5.5",
"messages": []map[string]string{
{"role": "user", "content": "Write a haiku about the sea."},
},
})
req, _ := http.NewRequest("POST", "https://ai.wisper.sh/v1/chat/completions", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("WISPER_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}Read tokens as they arrive over server-sent events.
curl -N https://ai.wisper.sh/v1/chat/completions \
-H "Authorization: Bearer $WISPER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"stream": true,
"messages": [{"role": "user", "content": "Stream a short story."}]
}'from openai import OpenAI
client = OpenAI(base_url="https://ai.wisper.sh/v1", api_key="wsk-your-key")
stream = client.chat.completions.create(
model="gpt-5.5",
messages=[{"role": "user", "content": "Stream a short story."}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://ai.wisper.sh/v1", apiKey: "wsk-your-key" });
const stream = await client.chat.completions.create({
model: "gpt-5.5",
messages: [{ role: "user", content: "Stream a short story." }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
)
func main() {
body, _ := json.Marshal(map[string]any{
"model": "gpt-5.5",
"stream": true,
"messages": []map[string]string{{"role": "user", "content": "Stream a short story."}},
})
req, _ := http.NewRequest("POST", "https://ai.wisper.sh/v1/chat/completions", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("WISPER_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
sc := bufio.NewScanner(resp.Body)
for sc.Scan() {
line := sc.Text()
if !strings.HasPrefix(line, "data: ") {
continue
}
data := strings.TrimPrefix(line, "data: ")
if data == "[DONE]" {
break
}
var ev struct {
Choices []struct{ Delta struct{ Content string } }
}
if json.Unmarshal([]byte(data), &ev) == nil && len(ev.Choices) > 0 {
fmt.Print(ev.Choices[0].Delta.Content)
}
}
}Let the model call a function, run it, then send the result back for a final answer.
import json
from openai import OpenAI
client = OpenAI(base_url="https://ai.wisper.sh/v1", api_key="wsk-your-key")
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {"type": "object",
"properties": {"city": {"type": "string"}}, "required": ["city"]},
},
}]
messages = [{"role": "user", "content": "What's the weather in Paris?"}]
first = client.chat.completions.create(model="gpt-5.5", messages=messages, tools=tools)
call = first.choices[0].message.tool_calls[0]
args = json.loads(call.function.arguments)
result = f"18C and sunny in {args['city']}" # run your real function
messages.append(first.choices[0].message)
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
final = client.chat.completions.create(model="gpt-5.5", messages=messages, tools=tools)
print(final.choices[0].message.content)import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://ai.wisper.sh/v1", apiKey: "wsk-your-key" });
const tools = [{
type: "function",
function: {
name: "get_weather",
parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"] },
},
}];
const messages = [{ role: "user", content: "What's the weather in Paris?" }];
const first = await client.chat.completions.create({ model: "gpt-5.5", messages, tools });
const call = first.choices[0].message.tool_calls[0];
const args = JSON.parse(call.function.arguments);
const result = `18C and sunny in ${args.city}`; // run your real function
messages.push(first.choices[0].message);
messages.push({ role: "tool", tool_call_id: call.id, content: result });
const final = await client.chat.completions.create({ model: "gpt-5.5", messages, tools });
console.log(final.choices[0].message.content);# First call — the model responds with a tool_calls array
curl https://ai.wisper.sh/v1/chat/completions \
-H "Authorization: Bearer $WISPER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"messages": [{"role": "user", "content": "Weather in Paris?"}],
"tools": [{"type": "function", "function": {
"name": "get_weather",
"parameters": {"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}
}}]
}'The same frontier models, about 50% cheaper than buying them direct. Pay as you go, or subscribe to a plan and get monthly credits at a better rate.
USD per 1M tokens. Official OpenAI list price shown for comparison.
| Model | Input /1M | Output /1M | vs official |
|---|---|---|---|
gpt-5.5 | $2.50 | $15.00 | 50% off ($5 / $30) |
gpt-5.4 | $1.25 | $7.50 | 50% off ($2.50 / $15) |
gpt-5.4-mini | $0.375 | $2.25 | 50% off ($0.75 / $4.50) |
claude-opus-4-8 | $2.50 | $12.50 | 50% off ($5 / $25) |
claude-sonnet-4-6 | $1.50 | $7.50 | 50% off ($3 / $15) |
claude-haiku-4-5 | $0.50 | $2.50 | 50% off ($1 / $5) |
zai-org/GLM-5.2 | $0.70 | $2.20 | 50% off ($1.40 / $4.40) |
moonshotai/Kimi-K2.7-Code | $0.37 | $1.75 | 50% off ($0.74 / $3.50) |
input_tokens × rate + output_tokens × rate, rounded up, minimum 1 credit.| Plan | Price | Monthly credits | Value | Best for |
|---|---|---|---|---|
| Free | $0 | 50,000 | $5 | Trial & evaluation |
| Starter | $19/mo | 300,000 | $30 | Hobby & side projects |
| Pro | $79/mo | 1,500,000 | $150 | Indie devs & small apps |
| Scale | $299/mo | 6,000,000 | $600 | Production workloads |
Each paid plan includes more credit value than its price — the more you commit, the lower your effective rate.
A typical gpt-5.4 request (~1,500 input + 500 output tokens) costs about 57 credits. The Pro plan ($79/mo, 1,500,000 credits) covers roughly 26,000 such requests — usage that would run about $300/mo on OpenAI. Pay-as-you-go is 50% under OpenAI on every call; subscribing adds a credit bonus on top.
Run the official claude CLI on Wisper. Point two environment variables at the gateway and your coding sessions run on Claude, billed to your key's credits.
npm install -g @anthropic-ai/claude-codeEdit ~/.claude/settings.json (run claude once first to create the folder). Use your wsk- key in both places.
{
"env": {
"ANTHROPIC_BASE_URL": "https://ai.wisper.sh",
"ANTHROPIC_API_KEY": "wsk-your-key",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
},
"apiKeyHelper": "echo wsk-your-key"
}Prefer env vars? export ANTHROPIC_BASE_URL=https://ai.wisper.sh and export ANTHROPIC_API_KEY=wsk-your-key work too.
claudeClaude Code uses Claude models (Sonnet, Opus, Haiku) with full tool use and extended thinking — all native through Wisper. Usage is deducted from the key's credit balance.
Claude Code uses two models — a main one and a small/fast background one. Set both to models Wisper serves (run /model in the CLI, or set them in env):
"env": {
"ANTHROPIC_BASE_URL": "https://ai.wisper.sh",
"ANTHROPIC_API_KEY": "wsk-your-key",
"ANTHROPIC_MODEL": "claude-sonnet-4-6",
"ANTHROPIC_SMALL_FAST_MODEL": "claude-haiku-4-5"
}claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5) run natively — full tool use and extended thinking.gpt-5.5, gpt-5.4-mini, zai-org/GLM-5.2, …) works too — Wisper translates it to the Anthropic protocol automatically.“Settings Error · Invalid or malformed JSON” means the settings.json file itself isn't valid JSON — Wisper isn't reached yet. Check:
", never curly “smart quotes”. Some apps convert them on paste — the Copy button above always gives clean quotes.{ }.settings.json (not settings.json.txt), UTF-8 with no BOM. In Notepad pick “Save as type: All Files”.Run the official codex CLI on Wisper via the Responses API. Add a provider pointing at the gateway and authenticate with your wsk- key.
npm install -g @openai/codexPut your key in ~/.codex/auth.json:
{ "OPENAI_API_KEY": "wsk-your-key" }In ~/.codex/config.toml:
model_provider = "wisper"
model = "gpt-5.5"
model_reasoning_effort = "high"
disable_response_storage = true
preferred_auth_method = "apikey"
[model_providers.wisper]
name = "wisper"
base_url = "https://ai.wisper.sh/v1"
wire_api = "responses"cd your-project
codexmodel can be any OpenAI model Wisper serves — gpt-5.5, gpt-5.4, or gpt-5.4-mini. model_reasoning_effort controls thinking depth.
List models at runtime with GET /v1/models. Reasoning effort is selectable with the reasoning_effort parameter or a model-id suffix.
| Model | Default effort | Best for |
|---|---|---|
gpt-5.5 | medium | Flagship — the strongest general & coding model. |
gpt-5.4 | medium | Balanced quality and latency for everyday tasks. |
gpt-5.4-mini | medium | Fastest and lightest — high-volume, low-latency work. |
claude-opus-4-8 | — | Anthropic flagship — deep reasoning & coding. |
claude-sonnet-4-6 | — | Balanced Claude for everyday tasks. |
claude-haiku-4-5 | — | Fast, low-cost Claude. |
zai-org/GLM-5.2 | — | Open-weight reasoner — 1M context, strong value. |
moonshotai/Kimi-K2.7-Code | — | Agentic coding model — 256K context, multimodal. |
Most models can think before answering. Control the depth with reasoning_effort or a model suffix like gpt-5.5-high. See Reasoning for the levels, how each model family behaves, and worked examples.
POST /v1/chat/completions — generate a model response for a conversation.
| Field | Type | Description |
|---|---|---|
| model | string | Model id, optionally with an effort suffix. Required. |
| messages | array | Conversation so far. Roles: system, user, assistant, tool. Required. |
| stream | boolean | Stream the response as SSE chunks. Default false. |
| stream_options | object | Set {"include_usage": true} for a final usage chunk. |
| tools | array | Function tools the model may call. |
| tool_choice | string|object | auto (default), none, required, or a named function. |
| reasoning_effort | string | low · medium · high · xhigh. |
| temperature | number | Passed through when provided. |
| max_tokens | number | Upper bound on output tokens. |
{
"id": "chatcmpl-...",
"object": "chat.completion",
"model": "gpt-5.5",
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": "Hello there, friend" },
"finish_reason": "stop"
}],
"usage": { "prompt_tokens": 24, "completion_tokens": 22, "total_tokens": 46 }
}
| Endpoint | Method | Description |
|---|---|---|
/v1/models | GET | List available models. |
/v1/models/:id | GET | Retrieve one model. |
/health | GET | Service status (no key required). |
Some models think before they answer. That hidden chain-of-thought comes back separately as reasoning_content — the final answer is always in content. Show the thinking, log it, or ignore it; your choice.
Two equivalent ways — use whichever your client makes easy:
"reasoning_effort": "high" to the body.gpt-5.5-high or claude-sonnet-4-6-high.If you use both, the reasoning_effort parameter wins. minimal maps to low and max maps to xhigh.
| Level | Feel | Reach for it when… |
|---|---|---|
low | Fast, light thinking | Simple Q&A, formatting, classification, extraction. |
medium | Balanced (the default where supported) | Everyday tasks and general chat. |
high | Deeper, slower | Hard math, multi-step planning, tricky debugging. |
xhigh | Maximum depth | The hardest problems — most thinking tokens & latency. |
More effort = more thinking tokens = better answers on hard problems, but higher latency and cost. Start at medium or high and adjust.
| Family | Default | What effort does |
|---|---|---|
gpt-5.x | Always reasons (medium) | Sets how hard it thinks. |
claude-* | Off by default | Enables extended thinking at the chosen budget. |
GLM, Kimi | Reason natively | Effort is forwarded as a hint; thinking returns as reasoning_content. |
Fast and cheap — minimal thinking for an easy question.
curl https://ai.wisper.sh/v1/chat/completions \
-H "Authorization: Bearer $WISPER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"reasoning_effort": "low",
"messages": [{"role": "user", "content": "What is 12 × 12?"}]
}'Deep thinking for a hard problem — equivalently, set the model to gpt-5.5-high.
curl https://ai.wisper.sh/v1/chat/completions \
-H "Authorization: Bearer $WISPER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"reasoning_effort": "high",
"messages": [{"role": "user", "content": "Prove there are infinitely many primes."}]
}'The thinking is in reasoning_content; the answer is in content.
{
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"reasoning_content": "Suppose there were finitely many primes p1…pn. Consider N = p1·…·pn + 1…",
"content": "There are infinitely many primes. Proof: …"
},
"finish_reason": "stop"
}],
"usage": { "prompt_tokens": 18, "completion_tokens": 240, "total_tokens": 258 }
}When streaming, reasoning arrives first as delta.reasoning_content, then the answer as delta.content.
// thinking streams first…
data: {"choices":[{"delta":{"role":"assistant"}}]}
data: {"choices":[{"delta":{"reasoning_content":"Let me"}}]}
data: {"choices":[{"delta":{"reasoning_content":" think…"}}]}
// …then the answer
data: {"choices":[{"delta":{"content":"There are"}}]}
data: {"choices":[{"delta":{},"finish_reason":"stop"}]}
data: [DONE]To hide the model's thinking from end users, just render content and ignore reasoning_content. Reasoning tokens are billed as output tokens.
Set stream: true to receive chat.completion.chunk events as data: lines, terminated by data: [DONE].
curl -N https://ai.wisper.sh/v1/chat/completions \
-H "Authorization: Bearer $WISPER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5.5","stream":true,
"stream_options":{"include_usage":true},
"messages":[{"role":"user","content":"Count to 5."}]}'
// role chunk → content deltas → finish → usage → DONE
data: {"choices":[{"delta":{"role":"assistant"}}]}
data: {"choices":[{"delta":{"content":"1"}}]}
data: {"choices":[{"delta":{},"finish_reason":"stop"}]}
data: {"choices":[],"usage":{"total_tokens":24}}
data: [DONE]
The final usage chunk is only sent when stream_options.include_usage is true.
Pass tools with JSON-Schema parameters. When the model calls one, the response has finish_reason: "tool_calls" and a tool_calls array.
{
"model": "gpt-5.5",
"messages": [{"role": "user", "content": "Weather in Paris?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": { "type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"] }
}
}]
}
Echo the assistant message back, then append a {"role":"tool","tool_call_id":"…","content":"…"} message with your function’s output and call again.
Errors use the standard OpenAI envelope: { "error": { "message", "type", "code" } }. The HTTP status reflects the failure class.
| Status | Type | When |
|---|---|---|
| 401 | authentication_error | Missing API key. |
| 403 | authentication_error | Invalid or revoked key. |
| 400 | invalid_request_error | Malformed body or missing fields. |
| 404 | invalid_request_error | Unknown model (model_not_found). |
| 429 | rate_limit_error | Rate or usage limit reached. |
| 5xx | server_error | Upstream or gateway error. |
{
"error": {
"message": "The model `gpt-9` does not exist...",
"type": "invalid_request_error",
"code": "model_not_found",
"param": "model"
}
}