Documentation
Everything you need to plug CheapAI into your tooling — setup guides for popular agents, API reference, FAQ.
Base URL
There are two addresses, and they differ in one thing: whether a CDN sits in front. Everything else — keys, models, prices, request format — is identical.
524, even though the answer was already on its way. The direct address has no such limit. It also helps where the CDN itself is unreachable. For everything else the default address is better: faster routing and attack protection.Claude Code is the one exception to the /v1 rule: it appends the path itself, so give it https://api.cheapai.io (or https://cheapai.io) without the suffix.
Authentication
Every request to /v1/* must carry a bearer token (for OpenAI-compatible endpoints) or an x-api-key header (for Anthropic endpoints). The same key works for both — it's the string from the «Keys» section, starting with sk-.
# OpenAI-style
Authorization: Bearer sk-...
# Anthropic-style
x-api-key: sk-.../v1/chat/completionsOpenAI-compatible Chat Completions. Works for every model in the catalog (Claude, GPT, Gemini, …). Streaming via stream: true.
curl https://cheapai.io/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [
{"role": "user", "content": "Hi!"}
]
}'import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.CHEAPAI_KEY,
baseURL: "https://cheapai.io/v1",
});
const res = await client.chat.completions.create({
model: "claude-sonnet-4-6",
messages: [{ role: "user", content: "Hi!" }],
});
console.log(res.choices[0].message);Response shape
{
"id": "chatcmpl-...",
"object": "chat.completion",
"model": "claude-sonnet-4-6",
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": "..." },
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 42,
"total_tokens": 52
}
}/v1/messagesAnthropic-native Messages API. Use for Claude models when you need extended thinking, native tool_use blocks, prompt caching or any Claude-specific param.
curl https://cheapai.io/v1/messages \
-H "x-api-key: YOUR_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hi!"}
]
}'import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: process.env.CHEAPAI_KEY,
baseURL: "https://cheapai.io",
});
const res = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hi!" }],
});
console.log(res.content);Response shape
{
"id": "msg_...",
"type": "message",
"role": "assistant",
"content": [{ "type": "text", "text": "..." }],
"model": "claude-sonnet-4-6",
"stop_reason": "end_turn",
"usage": {
"input_tokens": 10,
"output_tokens": 42,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0
}
}/v1/modelsList models available under your key (OpenAI-style envelope).
curl https://cheapai.io/v1/models \
-H "Authorization: Bearer YOUR_API_KEY"/v1/images/generationsImage generation. Billed per call, not per token — the price is on the models page.
curl https://cheapai.io/v1/images/generations \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "grok-imagine-image",
"prompt": "a lighthouse at dusk, long exposure",
"n": 1
}'The response is {"data":[{"url":…}]} or b64_json, depending on the model. Extra parameters some SDKs send (size, quality, style) are accepted and ignored.
/v1beta/models/{model}:generateContentNative Gemini API. Needed for google_search grounding — the OpenAI-compatible path has no equivalent.
curl https://cheapai.io/v1beta/models/gemini-3.1-pro-high:generateContent \
-H "x-goog-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [{"parts": [{"text": "What changed in the news today?"}]}],
"tools": [{"google_search": {}}]
}'Authentication here uses x-goog-api-key, as Google SDKs expect — the same key as everywhere else. Streaming is :streamGenerateContent. Grounded answers come back with a groundingMetadata block listing the sources.
/v1/dashboard/billing/subscriptionBalance and spend, in the OpenAI-compatible shape — so existing tooling reads it without changes.
# всего доступно (остаток + уже потрачено), USD
curl https://cheapai.io/v1/dashboard/billing/subscription \
-H "Authorization: Bearer YOUR_API_KEY"
# → {"hard_limit_usd": 148.51, "access_until": 0, …}
# потрачено, в сотых долях доллара
curl https://cheapai.io/v1/dashboard/billing/usage \
-H "Authorization: Bearer YOUR_API_KEY"
# → {"total_usage": 7639.53} // = $76.40Streaming
Pass "stream": true in the body and the response is a server-sent-event stream. For /v1/chat/completions the format is OpenAI SSE (data: {…} chunks terminated by data: [DONE]). For /v1/messages the format is Anthropic SSE (typed events: message_start, content_block_delta, message_stop).
Errors
Errors follow the upstream provider format. HTTP status reflects the failure class.
| HTTP | Meaning |
|---|---|
| 400 | Bad request — model name, params, schema. |
| 401 | Missing or invalid API key. |
| 402 | Balance is insufficient — top up to continue. |
| 429 | Rate-limited by the upstream provider. |
| 5xx | Upstream outage — request is automatically retried across fallback channels. |
Timeouts and streaming
The one hard limit is how long a model may stay silent BEFORE it starts answering — about 90 seconds. Keepalive pings do not help here: they only start once the provider has responded. Cloudflare in front of us cuts at ~125 seconds on the same basis, so the gateway limit is the one you hit first.
Send stream: true — with it a long answer never gets cut. Once tokens start flowing the timeout no longer applies, and during pauses in the middle of a reply the gateway sends keepalive pings every 60 seconds, so the connection survives even if the model goes quiet for a while mid-answer. A ten-minute generation completes fine.
Raise the timeout in your own client too — many SDKs default to 30-60 seconds, which is stricter than ours. For Claude Code the relevant variables are API_TIMEOUT_MS and CLAUDE_STREAM_FIRST_BYTE_TIMEOUT_MS.
Reasoning models are the usual reason for a long silence: they think before emitting anything. If you routinely hit the ceiling on one, a lighter model in the same family usually starts answering much sooner.