CheapAI

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.

Default — behind a CDN
https://cheapai.io/v1
Direct — no CDN
https://api.cheapai.io/v1
Take the direct address when a request can stay silent for a long time before the first token — reasoning models on a big prompt, agent runs. The CDN cuts a connection that produced no bytes for about two minutes and the client sees a 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-.

bash
# OpenAI-style
Authorization: Bearer sk-...

# Anthropic-style
x-api-key: sk-...
POST/v1/chat/completions

OpenAI-compatible Chat Completions. Works for every model in the catalog (Claude, GPT, Gemini, …). Streaming via stream: true.

bash
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!"}
    ]
  }'
javascript
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
  }
}
POST/v1/messages

Anthropic-native Messages API. Use for Claude models when you need extended thinking, native tool_use blocks, prompt caching or any Claude-specific param.

bash
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!"}
    ]
  }'
javascript
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
  }
}
GET/v1/models

List models available under your key (OpenAI-style envelope).

bash
curl https://cheapai.io/v1/models \
  -H "Authorization: Bearer YOUR_API_KEY"
POST/v1/images/generations

Image generation. Billed per call, not per token — the price is on the models page.

bash
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.

Text-to-image only. grok-imagine ignores an input image entirely — it will not edit or restyle what you send, it will generate something new from the prompt. To edit an existing image use gemini-3.1-flash-image-preview through /v1/chat/completions with the image as a multimodal message part; it preserves the input. /v1/images/edits is not supported.
POST/v1beta/models/{model}:generateContent

Native Gemini API. Needed for google_search grounding — the OpenAI-compatible path has no equivalent.

bash
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.

Convert the tool to OpenAI function_declarations and grounding falls off — that is why this endpoint exists separately. If you only need chat, /v1/chat/completions handles Gemini models fine.
GET/v1/dashboard/billing/subscription

Balance and spend, in the OpenAI-compatible shape — so existing tooling reads it without changes.

bash
# всего доступно (остаток + уже потрачено), 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.40
hard_limit_usd is NOT your remaining balance — it is remaining plus already spent, inherited from the OpenAI field semantics. The remaining balance is hard_limit_usd − total_usage / 100. In the example above: 148.51 − 76.40 = $72.11.

Streaming

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.

HTTPMeaning
400Bad request — model name, params, schema.
401Missing or invalid API key.
402Balance is insufficient — top up to continue.
429Rate-limited by the upstream provider.
5xxUpstream 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.