---
title: "How the API works: protocols and billing — AI Creator API"
description: "How AI Creator API works: three protocols, model ids and aliases, streaming, the reserve and the charge on the balance, and what we store about requests."
url: https://llm.aicreator.app/en/docs/how-it-works/
lang: en
updated: 2026-11-10
---

# How the API works

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:

| Protocol                | Request                     | Used by                            |
| ----------------------- | --------------------------- | ---------------------------------- |
| Anthropic Messages      | `POST /v1/messages`         | Claude Code, Anthropic SDK         |
| OpenAI Responses        | `POST /v1/responses`        | Codex CLI, OpenAI SDK              |
| OpenAI Chat Completions | `POST /v1/chat/completions` | Cline, 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](https://llm.aicreator.app/en/docs/api/).

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](https://llm.aicreator.app/en/docs/models/), 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:

| Alias    | Model               |
| -------- | ------------------- |
| `opus`   | `claude-opus-5-5`   |
| `sonnet` | `claude-sonnet-5-5` |
| `haiku`  | `claude-haiku-5-5`  |
| `fable`  | `claude-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](https://llm.aicreator.app/en/docs/errors/).

## 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](https://llm.aicreator.app/en/docs/billing/#charging).

## 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)
```

```javascript title="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);
```
