How the API works

View as Markdown
On this page

AI Creator API is a proxy between your tool and the model’s provider. It checks the key and the limits, reserves money on the balance, passes the request to the provider and returns its answer. The request body goes as is; only the model name changes when it is an alias.

Three protocols#

We answer over three protocols. Which one is needed is up to the tool, not you:

ProtocolRequestUsed by
Anthropic MessagesPOST /v1/messagesClaude Code, Anthropic SDK
OpenAI ResponsesPOST /v1/responsesCodex CLI, OpenAI SDK
OpenAI Chat CompletionsPOST /v1/chat/completionsCline, OpenCode, Aider, OpenAI SDK

We don’t translate one protocol into another. A request for a model on a protocol it lacks returns 400 model_not_available. Request and response formats are those of the official Anthropic and OpenAI APIs; every path and header is in the API reference.

The address for Anthropic clients is https://api.aicreator.app, and for OpenAI clients https://api.aicreator.app/v1. For Russia the same API has a second address, https://api-ru.aicreator.app: keys, paths and the balance are shared.

Model ids and aliases#

The model field takes only the ids of our own list; they are in Models and prices, and for your key they come from GET /v1/models. An id is matched letter for letter: another spelling of the same model gets 400 model_not_available.

Four short names that Claude Code sends are resolved on our side:

AliasModel
opusclaude-opus-5-5
sonnetclaude-sonnet-5-5
haikuclaude-haiku-5-5
fableclaude-fable-5-1

A request with an alias is counted and charged as a request to the model it resolved to.

What happens to a request#

Before sending it to the provider we check:

  • the key, or else 401;
  • the key’s and the service’s limits, or else 429;
  • that the model is on this protocol and allowed for the key, or else 400 or 403;
  • that the balance can hold the reserve for the request, or else 402.

A refusal at this step costs nothing and does not reach the dashboard’s request log. Then the request goes to the provider, the answer returns to you as it came, and the balance is charged by actual tokens. The answer to a request that reached the provider carries an X-Billing-Id header, which finds the request in the usage export. Refusal codes are in Errors and limits.

Streaming#

With "stream": true the answer comes as SSE. We pass the provider’s stream through byte for byte: the same events in the same order.

  • If the model is silent for more than 15 seconds we send a keepalive: event: ping on Anthropic and an SSE comment : ping on the OpenAI protocols.
  • In a Chat Completions stream we always turn on stream_options.include_usage: the last chunk has an empty choices: [] and the token usage. Your own client should check that choices is not empty.
  • One request lasts 30 minutes at most.

Why long answers must stream#

A request without streaming waits for the whole answer in silence: while the model works, nothing travels over the connection. Cloudflare stands in front of our API, and when no answer comes for more than about two minutes it closes the connection with code 524 while the model is still working. The address for Russia goes through the same Cloudflare, so the rule holds there too.

The limit does not apply to a stream: data or keepalives keep flowing.For long answers and long thinking turn on "stream": true. Claude Code and Codex stream by themselves.

Reserve and charge#

  1. Before a request we reserve an estimated amount on the balance. If the balance falls short, the answer is 402 insufficient_balance and the request is not sent to the provider.
  2. After the answer we charge by the actual tokens from the provider’s reply. The rest of the reserve returns to the balance at once; a charge is never above the reserve.

The reserve is larger than the request’s price by design. Input is estimated from the body size: bytes divided by 3, plus 8,192 tokens of margin. Output is taken from max_tokens, or from the model’s maximum without it. 20% is added on top. So on a small balance set max_tokens yourself.

A request that is refused or fails before generation starts costs nothing: the whole reserve returns. If generation breaks off midway, what the model managed to generate is charged. In the rare case of a failure on our side the reserve returns after about 45 minutes. Details are in Billing and usage.

Whose errors#

Errors come in two kinds. Ours, when we stopped the request; they share one JSON shape with an error.code field. Provider errors come through as is, in their protocol’s format, together with the retry-after and x-should-retry headers, so tools recognise them and retry just as with the official API.

What we store#

  • About every request that reached the provider, the numbers: model, key, protocol, input, output and cache tokens, the price, the status, the latency and the time to first token. That is the request log of the dashboard.
  • We don’t store the text of prompts and answers, and we don’t write it to logs.
  • Of a key we keep only a hash and its first characters; the full key does not reach the logs.

This describes our own servers. For the model to answer, the request body goes to its provider as is.

Your own code#

The official SDKs work unchanged: swap the base URL and the key. An example with the OpenAI SDK:

Python
from openai import OpenAI

client = OpenAI(base_url="https://api.aicreator.app/v1", api_key="sk-aic-…")
reply = client.chat.completions.create(
    model="gpt-6-astra",
    messages=[{"role": "user", "content": "Hello"}],
)
print(reply.choices[0].message.content)
Node.js
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.aicreator.app/v1", apiKey: process.env.AIC_API_KEY });
const reply = await client.chat.completions.create({
  model: "gpt-6-astra",
  messages: [{ role: "user", content: "Hello" }],
});
console.log(reply.choices[0].message.content);